переставить узлы xml, включая подузлы, с помощью xslt - PullRequest
5 голосов
/ 29 ноября 2011

У меня есть XML-документ, теперь я хочу перевести его в другой XML-документ с тем же содержанием, но с другим порядком элементов.

Исходный XML-документ, такой как:

<?xml version = "1.0" encoding = "UTF-8"?>  
<order xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" >  
 <ship>  
    <zipcode>78712</zipcode>  
    <street>1234 Main Street</street>  
    <country>CN</country>    
    <city>Beijing</city>  
 </ship>   
 <items>     
    <quantity>1</quantity>     
    <itemno>1234</itemno>  
 </items>     
 <items>     
    <quantity>3</quantity>    
    <itemno>1235</itemno>    
 </items>    
 <price>456</price>  
 <customer>Tom Hill</customer>    
</order>  

Ожидаемыйвыводить XML-документ, например:

<?xml version = "1.0" encoding = "UTF-8"?>  
<order xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" >  
 <customer>Tom Hill</customer>    
 <ship>  
    <street>1234 Main Street</street>  
    <city>Beijing</city>  
    <zipcode>78712</zipcode>  
    <country>CN</country>    
 </ship>    
 <items>     
    <itemno>1234</itemno>    
    <quantity>1</quantity>     
 </items>     
 <items>     
    <itemno>1235</itemno>    
    <quantity>3</quantity>    
 </items>    
 <price>456</price>  
</order> 

Я использовал следующий документ xslt для его перевода.

<?xml version="1.0"?>  
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">  
<xsl:template match="/order">  
 <xsl:copy>  
  <xsl:copy-of select="customer" />  
  <xsl:copy-of select="ship" >  
  <xsl:call-template name="TShip" />  
  </xsl:copy-of>  
  <xsl:copy-of select="items">  
  <xsl:call-template name="TItems" />  
  </xsl:copy-of>  
 <xsl:copy-of select="price" />  
 </xsl:copy>  
</xsl:template>  

<xsl:template name="TShip">  
 <xsl:copy>  
  <xsl:copy-of select="street" />  
  <xsl:copy-of select="city" />  
  <xsl:copy-of select="zipcode" />  
  <xsl:copy-of select="country" />  
 </xsl:copy>  
</xsl:template>  

<xsl:template name="TItems">  
 <xsl:for-each select="items">  
  <xsl:copy>  
   <xsl:copy-of select="itemno" />  
   <xsl:copy-of select="quantity" />  
  </xsl:copy>  
 </xsl:for-each>  
</xsl:template>  

</xsl:stylesheet>  

Однако переведенный результат не является моим ожидаемым.Переведенный результат xml:

<?xml version = "1.0" encoding = "UTF-8"?>  
<order xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" >  
 <customer>Tom Hill</customer>    
 <ship>  
    <zipcode>78712</zipcode>  
    <street>1234 Main Street</street>  
    <country>CN</country>    
    <city>Beijing</city>    
 </ship>    
 <items>     
    <quantity>1</quantity>     
    <itemno>1234</itemno>    
 </items>     
 <items>     
    <quantity>3</quantity>    
    <itemno>1235</itemno>   
 </items>    
 <price>456</price>  
</order>  

Он только что сделал узлы первого уровня в ожидаемом порядке.Все подузлы хранятся в исходном порядке.Как я могу сделать заказ всех узлов, как я ожидал?

Ответы [ 2 ]

10 голосов
/ 29 ноября 2011

xsl:copy-of копирует все дочерние узлы, а дочерние узлы не оцениваются.

Таким образом, ваши шаблоны TShip и TItems даже никогда не оцениваются. <xsl:copy-of select="ship"> копирует все <ship>...</ship>.

Эта модификация вашего шаблона продемонстрирует, что ваши шаблоны TShip и TItems не вызываются.

<?xml version="1.0"?>  
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">  
<xsl:template match="/order">  
 <xsl:copy>  
  <xsl:copy-of select="customer" />
    <xsl:copy-of select="ship">
  <xsl:call-template name="TShip" />  
</xsl:copy-of>
  <xsl:copy-of select="items">  
  <xsl:call-template name="TItems" />  
  </xsl:copy-of>  
 <xsl:copy-of select="price" />  
 </xsl:copy>  
</xsl:template>  

<xsl:template name="TShip">  
 <xsl:copy>  
  <test>TShip called</test>
  <xsl:copy-of select="street" />  
  <xsl:copy-of select="city" />  
  <xsl:copy-of select="zipcode" />  
  <xsl:copy-of select="country" />  
 </xsl:copy>  
</xsl:template>  

<xsl:template name="TItems">  
 <xsl:for-each select="items">  
  <xsl:copy> 
  <test>TItems called</test>
   <xsl:copy-of select="itemno" />  
   <xsl:copy-of select="quantity" />  
  </xsl:copy>  
 </xsl:for-each>  
</xsl:template>  

</xsl:stylesheet>

Обратите внимание, что вывод не содержит добавленные мной элементы <test>.

Вместо этого нужно рекурсивное неявное копирование. Обычно xsl:copy, xsl:copy-of и xsl:for-each являются признаком плохого дизайна шаблона xsl - очень мало проблем, которые xsl:template и xsl:apply-template с преобразованием идентичности не справляются лучше.

Вот как бы я это сделал:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output encoding="UTF-8" indent="yes" method="xml" />

    <xsl:template match="order">
        <xsl:copy>
            <!-- copy all attributes; maybe you don't want this -->
            <xsl:apply-templates select="@*" />
            <!-- copy some elements in a specific order  -->
            <xsl:apply-templates select="customer" />
            <xsl:apply-templates select="ship" />
            <xsl:apply-templates select="items" />
            <xsl:apply-templates select="price" />
            <!-- now copy any other children that we haven't explicitly reordered; again, possibly this is not what you want -->
            <xsl:apply-templates select="*[not(self::customer or self::ship or self::items or self::price)]"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="ship">
        <xsl:copy>
            <xsl:apply-templates select="@*" />
            <xsl:apply-templates select="street" />
            <xsl:apply-templates select="city" />
            <xsl:apply-templates select="zipcode" />
            <xsl:apply-templates select="country" />
            <xsl:apply-templates select="*[not(self::street or self::city or self::zipcode or self::country)]"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="items">
        <xsl:copy>
            <xsl:apply-templates select="@*" />
            <xsl:apply-templates select="itemno" />
            <xsl:apply-templates select="quantity" />
            <xsl:apply-templates select="*[not(self::itemno or self::quantity)]"/>
        </xsl:copy>
    </xsl:template>

    <!-- this is the identity transform: it copies everything that isn't matched by a more specific template -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Обратите внимание, сколько меньше предположений делает этот шаблонный дизайн относительно структуры вашего исходного XML. Это также намного проще изменить: например, если вы хотите заставить замолчать или переименовать конкретный элемент, который может сам иметь детей, вы просто добавляете новый xsl:template, который соответствует этому элементу, делаете все, что вам нужно, и xsl:apply-templates на детей.

Вам следует узнать больше об этом шаблоне XSLT , потому что он очень универсален и делает создание шаблонов гораздо менее утомительным, а ваши шаблоны - гораздо менее хрупкими.

1 голос
/ 29 ноября 2011

Как я могу сделать порядок всех узлов, как я ожидал?

Краткий ответ : Используя <xsl:apply-templates/> и <xsl:template> вместо <xsl:copy-of>


Вот полное преобразование :

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="order">
   <xsl:copy>
    <xsl:apply-templates select="customer"/>
    <xsl:apply-templates select="*[not(self::customer)]"/>
   </xsl:copy>
 </xsl:template>

 <xsl:template match="ship">
  <xsl:copy>
   <xsl:apply-templates select="street"/>
   <xsl:apply-templates select="city"/>
   <xsl:apply-templates select="zipcode"/>
   <xsl:apply-templates select="country"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="items">
  <xsl:copy>
   <xsl:apply-templates select="itemno"/>
   <xsl:apply-templates select="quantity"/>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

когда это преобразование применяется к предоставленному документу XML :

<order xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" >
    <ship>
        <zipcode>78712</zipcode>
        <street>1234 Main Street</street>
        <country>CN</country>
        <city>Beijing</city>
    </ship>
    <items>
        <quantity>1</quantity>
        <itemno>1234</itemno>
    </items>
    <items>
        <quantity>3</quantity>
        <itemno>1235</itemno>
    </items>
    <price>456</price>
    <customer>Tom Hill</customer>
</order>

желаемый, правильный результат выдается :

<order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <customer>Tom Hill</customer>
   <ship>
      <street>1234 Main Street</street>
      <city>Beijing</city>
      <zipcode>78712</zipcode>
      <country>CN</country>
   </ship>
   <items>
      <itemno>1234</itemno>
      <quantity>1</quantity>
   </items>
   <items>
      <itemno>1235</itemno>
      <quantity>3</quantity>
   </items>
   <price>456</price>
</order>

Объяснение

<xsl:copy-of select="someElement"/>

копирует все поддерево с корнем на someElement в точности как есть (и если бы у нас была инструкция, которая переставляет потомков, как эта инструкция узнала бы порядок, который нам нужен ???). *

Чтобы изменить порядок любых родственных элементов, мы должны указать новый требуемый порядок.

Это можно сделать, написав последовательность <xsl:apply-templates> инструкций, каждая из которых выбирает нужный элемент - в нужном порядке. Мы могли бы написать <xsl:copy-of> инструкции, но только для копирования элементов, потомки которых мы хотим сохранить в их первоначальном порядке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...