通过xslt重新排列包括子节点的xml节点

时间:2022-10-31 11:39:27

I have a xml document, now i want to translate it to another xml document with same content but different element orders.

我有一个xml文档,现在我想将它翻译成另一个具有相同内容但不同元素顺序的xml文档。

The original xml document like:

原始的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>  

The expected output xml document like:

预期的输出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> 

I used following xslt document to translate it.

我使用以下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>  

However, the translated result is not my expected. Translated result xml:

但是,翻译的结果不是我的预期。翻译结果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>  

It just made the first level nodes in expected order. All sub-nodes are kept in original order. How can i make the order of all nodes as my expected ?

它只是按预期顺序制作了第一级节点。所有子节点都保持原始顺序。如何使所有节点的顺序符合我的预期?

2 个解决方案

#1


10  

xsl:copy-of copies all child nodes as well and child nodes of it are not evaluated.

xsl:copy-of拷贝所有子节点以及它的子节点都不被评估。

So your TShip and TItems templates are never even being evaluated. <xsl:copy-of select="ship"> copies all of <ship>...</ship>.

因此,您的TShip和TItems模板甚至从未被评估过。 复制 ... 的全部内容。 :copy-of>

This modification to your template will demonstrate that your TShip and TItems templates are not being called.

对模板的此修改将证明您的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>

Notice that the output does not contain the <test> elements I added.

请注意,输出不包含我添加的 元素。

What you need to do instead is recursive implicit copying. Usually xsl:copy, xsl:copy-of and xsl:for-each are a sign of bad xsl template design--there are very few problems which xsl:template and xsl:apply-template with an identity transform do not handle better.

你需要做的是递归隐式复制。通常xsl:copy,xsl:copy-of和xsl:for-each是错误的xsl模板设计的标志 - xsl:template和xsl:apply-template与身份转换的处理方式很少有问题。

This is how I would do it:

我就是这样做的:

<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>

Notice how many fewer assumptions this template design makes about the structure of your source XML. It is also much easier to change: for example, if you want to silence or rename a particular element that may itself have children, you just add a new xsl:template that matches that element, do whatever you need to do, and xsl:apply-templates on the children.

请注意,此模板设计对源XML结构的假设少了很多。它也更容易改变:例如,如果你想沉默或重命名一个本身可能有子元素的特定元素,你只需添加一个与该元素匹配的新xsl:模板,做你需要做的任何事情,以及xsl:对孩子们应用模板。

You should learn more about this XSLT pattern because it is very versatile and makes template authoring much less tedious and your templates much less brittle.

您应该了解有关此XSLT模式的更多信息,因为它非常通用,并且使模板创作更加繁琐,并且您的模板更不易碎。

#2


1  

How can i make the order of all nodes as my expected ?

如何使所有节点的顺序符合我的预期?

The short answer: By using <xsl:apply-templates/> and <xsl:template> instead of <xsl:copy-of>

简短回答:使用 代替 :copy-of> :template>


Here is a complete transformation:

这是一个完整的转变:

<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>

when this transformation is applied on the provided XML document:

当此转换应用于提供的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>

the wanted, correct result is produced:

产生了想要的正确结果:

<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>

Explanation:

说明:

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

copies the whole subtree rooted by someElement exactly as is (and if we had an instruction that rearranges the descendants, how would this instruction know the order that we want???).

完全复制以someElement为根的整个子树(如果我们有一个重新排列后代的指令,那么这个指令将如何知道我们想要的顺序???)。

In order to change the order of any siblings - elements, we must specify the new, wanted order.

为了改变任何兄弟姐妹的顺序 - 元素,我们必须指定新的,想要的顺序。

This can be done by writing a sequence of <xsl:apply-templates> instructions, each selecting the desired element -- in the desired order. We could write <xsl:copy-of> instructions, but only for copying elements, whose descendents we want to remain in their original order.

这可以通过编写一系列 指令来完成,每个指令按所需顺序选择所需的元素。我们可以编写 指令,但仅用于复制元素,其后代我们希望保持原始顺序。 :copy-of> :apply-templates>

#1


10  

xsl:copy-of copies all child nodes as well and child nodes of it are not evaluated.

xsl:copy-of拷贝所有子节点以及它的子节点都不被评估。

So your TShip and TItems templates are never even being evaluated. <xsl:copy-of select="ship"> copies all of <ship>...</ship>.

因此,您的TShip和TItems模板甚至从未被评估过。 复制 ... 的全部内容。 :copy-of>

This modification to your template will demonstrate that your TShip and TItems templates are not being called.

对模板的此修改将证明您的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>

Notice that the output does not contain the <test> elements I added.

请注意,输出不包含我添加的 元素。

What you need to do instead is recursive implicit copying. Usually xsl:copy, xsl:copy-of and xsl:for-each are a sign of bad xsl template design--there are very few problems which xsl:template and xsl:apply-template with an identity transform do not handle better.

你需要做的是递归隐式复制。通常xsl:copy,xsl:copy-of和xsl:for-each是错误的xsl模板设计的标志 - xsl:template和xsl:apply-template与身份转换的处理方式很少有问题。

This is how I would do it:

我就是这样做的:

<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>

Notice how many fewer assumptions this template design makes about the structure of your source XML. It is also much easier to change: for example, if you want to silence or rename a particular element that may itself have children, you just add a new xsl:template that matches that element, do whatever you need to do, and xsl:apply-templates on the children.

请注意,此模板设计对源XML结构的假设少了很多。它也更容易改变:例如,如果你想沉默或重命名一个本身可能有子元素的特定元素,你只需添加一个与该元素匹配的新xsl:模板,做你需要做的任何事情,以及xsl:对孩子们应用模板。

You should learn more about this XSLT pattern because it is very versatile and makes template authoring much less tedious and your templates much less brittle.

您应该了解有关此XSLT模式的更多信息,因为它非常通用,并且使模板创作更加繁琐,并且您的模板更不易碎。

#2


1  

How can i make the order of all nodes as my expected ?

如何使所有节点的顺序符合我的预期?

The short answer: By using <xsl:apply-templates/> and <xsl:template> instead of <xsl:copy-of>

简短回答:使用 代替 :copy-of> :template>


Here is a complete transformation:

这是一个完整的转变:

<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>

when this transformation is applied on the provided XML document:

当此转换应用于提供的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>

the wanted, correct result is produced:

产生了想要的正确结果:

<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>

Explanation:

说明:

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

copies the whole subtree rooted by someElement exactly as is (and if we had an instruction that rearranges the descendants, how would this instruction know the order that we want???).

完全复制以someElement为根的整个子树(如果我们有一个重新排列后代的指令,那么这个指令将如何知道我们想要的顺序???)。

In order to change the order of any siblings - elements, we must specify the new, wanted order.

为了改变任何兄弟姐妹的顺序 - 元素,我们必须指定新的,想要的顺序。

This can be done by writing a sequence of <xsl:apply-templates> instructions, each selecting the desired element -- in the desired order. We could write <xsl:copy-of> instructions, but only for copying elements, whose descendents we want to remain in their original order.

这可以通过编写一系列 指令来完成,每个指令按所需顺序选择所需的元素。我们可以编写 指令,但仅用于复制元素,其后代我们希望保持原始顺序。 :copy-of> :apply-templates>