包装XML节点组

时间:2010-09-06 14:24:46

标签: php xml xslt

我正在使用PHP5,我需要以下列形式转换XML:

<list>
    <item label="(1)">some text</item>
    <item label="(2)">
        <anotherNode>some text</anotherNode
        <item label="a">some text</item>
        <item label="b">some text</item>          
    </item>
</list>

这样的事情:

<list>
    <item label="(1)">some text</item>
    <item label="(2)">
        <anotherNode>some text</anotherNode>
        <list> <!-- opening new wrapper node-->
            <item label="a">some text</item>
            <item label="b">some text</item>
        </list> <!-- closing new wrapper node-->
    </item>
</list> 

正如您在上面所看到的,我需要为任何未被'list'节点包装的'item'节点添加一个包装器节点。

将源xml转换为目标xml有哪些可能的解决方案?

更新:

注1:任何单个或一组<item>节点如果尚未包装,则需要由<list>节点包装。

注2:需要维护内容的顺序。

注3: 如果<item>之前和之后有<anotherNode>个节点。 它应该改变这个:

<list>
    <item label="(1)">some text</item>
    <item label="(2)">
        <item label="a">some text</item>
        <item label="b">some text</item>          
        <anotherNode>some text</anotherNode>
        <item label="c">some text</item>
        <item label="d">some text</item>          
    </item>
</list>

进入这个:

<list>
    <item label="(1)">some text</item>
    <item label="(2)">
        <list> <!-- opening new wrapper node-->
            <item label="a">some text</item>
            <item label="b">some text</item>          
        </list> <!-- closing new wrapper node-->
        <anotherNode>some text</anotherNode>
        <list> <!-- opening new wrapper node-->
            <item label="c">some text</item>
            <item label="d">some text</item>
        </list> <!-- closing new wrapper node-->
    </item>
</list>

谢谢,

3 个答案:

答案 0 :(得分:4)

此转化

<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()|@*" name="identity">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="item/item[1]">
  <list>
   <xsl:apply-templates mode="copy"
    select=".| following-sibling::item"/>
  </list>
 </xsl:template>

 <xsl:template match="item" mode="copy">
  <xsl:call-template name="identity"/>
 </xsl:template>

 <xsl:template match="item/item[not(position()=1)]"/>
</xsl:stylesheet>

应用于提供的XML文档

<list>
    <item label="(1)">some text</item>
    <item label="(2)">
        <anotherNode>some text</anotherNode>
        <item label="a">some text</item>
        <item label="b">some text</item>
    </item>
</list>

生成想要的正确结果

<list>
   <item label="(1)">some text</item>
   <item label="(2)">
      <anotherNode>some text</anotherNode>
      <list>
         <item label="a">some text</item>
         <item label="b">some text</item>
      </list>
   </item>
</list>

请注意

  1. 使用和覆盖身份规则

  2. 对某些元素的压制。

  3. 使用不同的模式处理某些元素

  4. <强>更新

    OP增加了额外的要求:

    如果在item之前和之后有anothernode个元素,那么每个item元素组必须包含在单独的list

    <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:key name="kfollnonitem" match="item"
      use="generate-id(preceding-sibling::*[not(self::item)][1])"/>
    
     <xsl:key name="kprecnonitem" match="item"
      use="generate-id(following-sibling::*[not(self::item)][1])"/>
    
     <xsl:template match="node()|@*" name="identity">
      <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
     </xsl:template>
    
     <xsl:template match="*[not(self::list)]/item[1]">
      <list>
       <xsl:apply-templates mode="copy"
        select="key('kprecnonitem',
                     generate-id(following-sibling::*[not(self::item)][1])
                     )"/>
      </list>
     </xsl:template>
    
     <xsl:template match=
      "*[not(self::list) and item]/*[not(self::item)]">
      <xsl:call-template name="identity"/>
    
      <list>
        <xsl:apply-templates mode="copy"
         select="key('kfollnonitem', generate-id())"/>
      </list>
     </xsl:template>
    
     <xsl:template match="item" mode="copy">
      <xsl:call-template name="identity"/>
     </xsl:template>
    
     <xsl:template match="item/item[not(position()=1)]"/>
    </xsl:stylesheet>
    

    对XML文档执行此转换时

    <list>
        <item label="(1)">some text</item>
        <item label="(2)">
            <item label="a">some text</item>
            <item label="b">some text</item>
            <anotherNode>some text</anotherNode>
            <item label="c">some text</item>
            <item label="d">some text</item>
        </item>
    </list>
    

    产生了想要的正确结果

    <list>
       <item label="(1)">some text</item>
       <item label="(2)">
          <list>
             <item label="a">some text</item>
             <item label="b">some text</item>
          </list>
          <anotherNode>some text</anotherNode>
          <list>
             <item label="c">some text</item>
             <item label="d">some text</item>
          </list>
       </item>
    </list>
    

答案 1 :(得分:3)

此样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="@*|node()" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()[1]" />
        </xsl:copy>
        <xsl:apply-templates select="following-sibling::node()[1]" />
    </xsl:template>
    <xsl:template match="*[not(self::list)]
                          /item[not(preceding-sibling::*[1][self::item])]">
        <list>
            <xsl:call-template name="identity"/>
        </list>
        <xsl:apply-templates select="following-sibling::node()
                                      [not(self::item)][1]" />
    </xsl:template>
    <xsl:template match="*[not(self::list)]
                          /item[not(following-sibling::*[1][self::item])]">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()[1]" />
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

输出:

<list>
    <item label="(1)">some text</item>
    <item label="(2)">
        <anotherNode>some text</anotherNode>
        <list>
            <item label="a">some text</item>
            <item label="b">some text</item>
        </list>
    </item>
</list>

此外,此样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="kItemByFirstSibling"
             match="item[preceding-sibling::*[1][self::item]]"
             use="generate-id(preceding-sibling::item
                               [not(preceding-sibling::*[1][self::item])][1])"/>
    <xsl:template match="@*|node()" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>
    <xsl:template match="*[not(self::list)]/item"/>
    <xsl:template match="*[not(self::list)]
                          /item[not(preceding-sibling::*[1][self::item])]"
                  priority="1">
        <list>
            <xsl:for-each select=".|key('kItemByFirstSibling',generate-id())">
                <xsl:call-template name="identity"/>
            </xsl:for-each>
        </list>
    </xsl:template>
</xsl:stylesheet>

注意:第一个样式表使用最细粒度的横向(它将在第一个item之后包装任何节点)。第二个样式表完全递归的身份转换。

编辑:使用新输入解决新的requeriment,两个样式表输出:

<list>
    <item label="(1)">some text</item>
    <item label="(2)">
        <list>
            <item label="a">some text</item>
            <item label="b">some text</item>
        </list>
        <anotherNode>some text</anotherNode>
        <list>
            <item label="c">some text</item>
            <item label="d">some text</item>
        </list>
    </item>
</list>

答案 2 :(得分:0)

您未在原始问题中解决此问题,因此可能不需要。但是如果输入有多个需要被包装的<item>元素序列,那么它们被其他兄弟元素彼此分开,例如:

<list>
    <item label="(1)">some text</item>
    <item label="(2)">
        <item label="a">some text</item>
        <item label="b">some text</item>          
        <anotherNode>some text</anotherNode>
        <item label="c">some text</item>
        <item label="d">some text</item>          
    </item>
</list>
我相信,先前的答案会将<item>个元素混为一谈,改变它们的顺序:

<list>
    <item label="(1)">some text</item>
    <item label="(2)">
        <list> <!-- opening new wrapper node-->
            <item label="a">some text</item>
            <item label="b">some text</item>          
            <item label="c">some text</item>
            <item label="d">some text</item>
        </list> <!-- closing new wrapper node-->
        <anotherNode>some text</anotherNode>
    </item>
</list> 

你想要吗,或者你想分开包装它们,像这样吗?

<list>
    <item label="(1)">some text</item>
    <item label="(2)">
        <list> <!-- opening new wrapper node-->
            <item label="a">some text</item>
            <item label="b">some text</item>          
        </list> <!-- closing new wrapper node-->
        <anotherNode>some text</anotherNode>
        <list> <!-- opening new wrapper node-->
            <item label="c">some text</item>
            <item label="d">some text</item>
        </list> <!-- closing new wrapper node-->
    </item>
</list> 

如果是后者,最简单的方法是使用XSLT 2.0 <xsl:for-each-group group-adjacent="name()" />构造。我不知道PHP 5是否有XSLT 2.0可用,但是如果你可以使用这样的东西,请参阅this good article