Wrap group of XML nodes

前端 未结 3 878
梦如初夏
梦如初夏 2021-01-19 07:17

Im working with PHP5, and I need to transform XML in the following form:


    some text
    

        
相关标签:
3条回答
  • 2021-01-19 08:03

    This stylesheet:

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

    Output:

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

    Also, this stylesheet:

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

    Note: First stylesheet use most fine grained transversal (it will wrap any node after first item). Second stylesheet full recursive identity transform.

    Edit: Addressing new requeriment, with new input, both stylesheets output:

    <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>
    
    0 讨论(0)
  • 2021-01-19 08:09

    You didn't address this in the original question, so it may not be required. But if the input has multiple sequences of <item> elements that need to be wrapped, that are separated from each other by other sibling elements, e.g.:

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

    the earlier answers will, I believe, lump the <item> elements together, changing their order:

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

    Do you want that, or do you want to wrap them separately, like this?

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

    If the latter, it will probably be easiest to use an XSLT 2.0 <xsl:for-each-group group-adjacent="name()" /> construction. I don't know whether PHP 5 has XSLT 2.0 available, but if you can use such a thing, see this good article.

    0 讨论(0)
  • 2021-01-19 08:12

    This 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()|@*" 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>
    

    when applied on the provided XML document:

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

    produces the wanted, correct result:

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

    Do note:

    1. The use and overriding of the Identity rule.

    2. The suppression of certain elements.

    3. The processing of certain elements using a different mode.

    Update:

    The OP has added additional requirements:

    "In case there are item elements before anothernode and after it, then each such group of item elements must be enclosed in a separate 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>
    

    when this transformation is performed against he following XML document:

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

    the wanted, correct result is produced:

    <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>
    
    0 讨论(0)
提交回复
热议问题