开发者

XSLT 2.0 - Nesting Elements In Output From Non-Nested Input

I'm working on a style sheet that outputs in a hierarchical from an input file with none. Every element of the input file is a sibling of one another, 开发者_开发技巧and each "header" element denotes a different section.

Sample input file (the actual sections under each header are about 10 times longer than this):

<Header>  
     Header1
</Header>
<Sub1>
     Sub1 first
</Sub1>
<Sub1>
     Sub1 second
</Sub1>
<Sub2>
     Sub2 first, Sub1 second
</Sub2>
<Sub1>
     Sub1 third
</Sub1>
<Sub2>
     Sub2 first, Sub1 third
</Sub2>

<Header>
     Header2
</Header>
Etc...

And the output of the above input should look like:

<Header>
     Header1
     <Step>
         Sub1 first
     </Step>
     <Step>
         Sub1 second
         <Step>
             Sub2 first, Sub1 second
         </Step>
     </Step>
     <Step>
         Sub1 third
         <Step>
             Sub2 first, Sub1 third
         </Step>
     </Step>
</Header>

<Header>
    Header2
    Etc.....
</Header>

Right now, I'm able to get the output up to the "Sub2 first, sub1 second". When I know that the next element is not another Sub2 or deeper subelement (Sub3), then I break from my Sub2 template, returning back into Sub1 template. And from here, my position variable still has the position of "Sub1 second". I have positioning information processed each time a template is called - so I have all the current position in document order for "Sub2 first, sub1 second" but once I break from the template back into Sub1's template, I lose that information. I can't seem to get the current position in document order to determine what the real next element is. A modifiable global parameter or variable would be ideal, but I know that's not possible in XSLT.

I'm not sure how to go about accomplishing this. Any suggestions would be great!


This XSLT 2.0 transformation:

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

 <xsl:template match="/*">
  <t>
   <xsl:apply-templates select="*[1]">
    <xsl:with-param name="pScope" select="*"/>
    <xsl:with-param name="pElemName" select="name(*[1])"/>
   </xsl:apply-templates>
  </t>
 </xsl:template>

 <xsl:template match="*">
  <xsl:param name="pScope"/>
  <xsl:param name="pElemName" select="'Step'"/>

    <xsl:for-each-group select="$pScope"
        group-starting-with="*[name()= name($pScope[1])]">
       <xsl:element name="{$pElemName}">
         <xsl:value-of select="."/>
         <xsl:apply-templates select="current-group()[2]">
          <xsl:with-param name="pScope" select=
           "current-group()[position() > 1]"/>
         </xsl:apply-templates>
       </xsl:element>
    </xsl:for-each-group>
 </xsl:template>
</xsl:stylesheet>

when applied on this XML document (based on the provided, but wrapped into a top element and added one more header with different names):

<t>
    <Header>Header1</Header>
    <Sub1>Sub1 first</Sub1>
    <Sub1>Sub1 second</Sub1>
    <Sub2>Sub2 first, Sub1 second</Sub2>
    <Sub1>Sub1 third</Sub1>
    <Sub2>Sub2 first, Sub1 third</Sub2>
    <Header>Header2</Header>
    <x>Sub1 first</x>
    <x>Sub1 second</x>
    <y>Sub2 first, Sub1 second</y>
    <x>Sub1 third</x>
    <z>Sub2 first, Sub1 third</z>
</t>

produces the wanted, correct output:

<t>
   <Header>Header1<Step>Sub1 first</Step>
      <Step>Sub1 second<Step>Sub2 first, Sub1 second</Step>
      </Step>
      <Step>Sub1 third<Step>Sub2 first, Sub1 third</Step>
      </Step>
   </Header>
   <Header>Header2<Step>Sub1 first</Step>
      <Step>Sub1 second<Step>Sub2 first, Sub1 second</Step>
      </Step>
      <Step>Sub1 third<Step>Sub2 first, Sub1 third</Step>
      </Step>
   </Header>
</t>

Explanation:

  1. Using <xsl:for-each-group> with the group-starting-with attribute.

  2. Fine-grained processing -- templates are applied on the second element of the current-group() and the scope for the next grouping is passed as parameter.


Just for fun an XSLT 1.0 stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="*">
        <xsl:param name="pNames" select="'|'"/>
        <xsl:if test="not(contains($pNames,concat('|',name(),'|')))">
            <xsl:variable name="vNext" select="following-sibling::*[1]"/>
            <xsl:copy>
                <xsl:apply-templates select="node()[1]"/>
                <xsl:apply-templates select="$vNext">
                    <xsl:with-param name="pNames"
                                    select="concat($pNames,name(),'|')"/>
                </xsl:apply-templates>
            </xsl:copy>
            <xsl:apply-templates select="$vNext" mode="search">
                <xsl:with-param name="pNames" select="$pNames"/>
                <xsl:with-param name="pSearch" select="name()"/>
            </xsl:apply-templates>
        </xsl:if>
    </xsl:template>
    <xsl:template match="*" mode="search">
        <xsl:param name="pNames"/>
        <xsl:param name="pSearch"/>
        <xsl:if test="not(contains($pNames,concat('|',name(),'|')))">
            <xsl:choose>
                <xsl:when test="name()=$pSearch">
                    <xsl:apply-templates select=".">
                        <xsl:with-param name="pNames" select="$pNames"/>
                    </xsl:apply-templates>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:apply-templates select="following-sibling::*[1]"
                                         mode="search">
                        <xsl:with-param name="pNames" select="$pNames"/>
                        <xsl:with-param name="pSearch" select="$pSearch"/>
                    </xsl:apply-templates>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

With welformed input:

<root>
    <Header>Header1</Header>
    <Sub1>Sub1 first</Sub1>
    <Sub1>Sub1 second</Sub1>
    <Sub2>Sub2 first, Sub1 second</Sub2>
    <Sub1>Sub1 third</Sub1>
    <Sub2>Sub2 first, Sub1 third</Sub2>
    <Header>Header2</Header>
</root>

Output:

<root>
    <Header>Header1
        <Sub1>Sub1 first</Sub1>
        <Sub1>Sub1 second
            <Sub2>Sub2 first, Sub1 second</Sub2>
        </Sub1>
        <Sub1>Sub1 third
            <Sub2>Sub2 first, Sub1 third</Sub2>
        </Sub1>
    </Header>
    <Header>Header2</Header>
</root>

With the name replacement and the new requeriment of one level with several names, this stylesheet:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:r="rules"
 exclude-result-prefixes="r">
    <r:r m="|Header|">Header</r:r>
    <r:r m="|Sub1|">Step</r:r>
    <r:r m="|Sub2|">Step</r:r>
    <xsl:variable name="vRules" select="document('')/*/r:r"/>
    <xsl:template match="*">
        <xsl:param name="pNames" select="'|'"/>
        <xsl:variable name="vName" select="concat('|',name(),'|')"/>
        <xsl:if test="not(contains($pNames,$vName))">
            <xsl:variable name="vNext" select="following-sibling::*[1]"/>
            <xsl:variable name="vMatch"
                          select="$vRules[contains(@m,$vName)]"/>
            <xsl:variable name="vReplace"
                 select="concat($vMatch,name((.)[not($vMatch)]))"/>
            <xsl:variable name="vSearch"
                          select="concat($vMatch/@m,$vName)"/>
            <xsl:element name="{$vReplace}">
                <xsl:apply-templates select="node()[1]"/>
                <xsl:apply-templates select="$vNext">
                    <xsl:with-param name="pNames"
                                    select="concat($pNames,$vSearch)"/>
                </xsl:apply-templates>
            </xsl:element>
            <xsl:apply-templates select="$vNext" mode="search">
                <xsl:with-param name="pNames" select="$pNames"/>
                <xsl:with-param name="pSearch" select="$vSearch"/>
            </xsl:apply-templates>
        </xsl:if>
    </xsl:template>
    <xsl:template match="*" mode="search">
        <xsl:param name="pNames"/>
        <xsl:param name="pSearch"/>
        <xsl:variable name="vName" select="concat('|',name(),'|')"/>
        <xsl:choose>
            <xsl:when test="contains($pNames,$vName)"/>
            <xsl:when test="contains($pSearch,$vName)">
                <xsl:apply-templates select=".">
                    <xsl:with-param name="pNames" select="$pNames"/>
                </xsl:apply-templates>
            </xsl:when>
            <xsl:otherwise>
                <xsl:apply-templates select="following-sibling::*[1]"
                                         mode="search">
                    <xsl:with-param name="pNames" select="$pNames"/>
                    <xsl:with-param name="pSearch" select="$pSearch"/>
                </xsl:apply-templates>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

Output:

<root>
    <Header>Header1
        <Step>Sub1 first</Step>
        <Step>Sub1 second
            <Step>Sub2 first, Sub1 second</Step>
        </Step>
        <Step>Sub1 third
            <Step>Sub2 first, Sub1 third</Step>
        </Step>
    </Header>
    <Header>Header2</Header>
</root>

Note: Synonyms for same level should be in r:r/@m


Your input xml isn't really an xml so I added a root node. I'd advise against using xslt to do this kind of processing. But if you must - here's something to get you started.

Btw. Do your subsections have any kind of Id?

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/Root">
    <Root>
      <xsl:apply-templates select="Header"/>
    </Root>
  </xsl:template>

  <xsl:template match="Header">
    <Header>
      <xsl:copy-of select="text()"/>
      <xsl:variable name="current" select="."/>
      <xsl:apply-templates select="/Root/Sub1[preceding-sibling::Header[1]/. = $current]"/>
    </Header>
  </xsl:template>

  <xsl:template match="Sub1">
    <Step>
      <xsl:copy-of select="text()"/>
      <xsl:variable name="current" select="."/>
      <xsl:apply-templates select="/Root/Sub2[preceding-sibling::Sub1[1]/. = $current]"/>
    </Step>
  </xsl:template>

  <xsl:template match="Sub2">
    <Step>
      <xsl:copy-of select="text()"/>
      <!-- todo -->
    </Step>
  </xsl:template>

</xsl:stylesheet>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜