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

时间:2011-11-29 02:56:34

标签: xml xslt

我有一个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 个答案:

答案 0 :(得分:10)

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:copyxsl:copy-ofxsl:for-each是错误的xsl模板设计的标志 - xsl:templatexsl: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

你应该learn more about this XSLT pattern ,因为它非常通用,使模板创作更加乏味,而且你的模板更不易碎。

答案 1 :(得分:1)

  

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

简短回答:使用<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>条指令,但仅限于复制元素,我们希望它们的后代保持原始顺序。