XSLT 2.0 - 使用分组来嵌套元素

时间:2011-02-22 19:09:11

标签: xslt grouping elements

我正在开发一个样式表,它以几乎没有输入文件的分层格式输出。每个部分都有一个非常平坦的层次结构,所以我一直在使用一种建议给我的分组方法 - 它按照第一个节点名称对每个集合进行分组,从而在平面部分中形成一个很好的层次结构。这种方法效果很好 - 我只需要修改它以考虑我想跳过的元素。

示例输入文件(注意:每个部分有多个Header元素):

<Root>
    <VolumeName>Volume 1 </VolumeName>
    <Section>
        <SectionName> Section1 </SectionName>
        <Title> Title1 </Title>
        <Header> NameOfDocument1 </Header>
        <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>
    </Section>

    <Section>
        <SectionName> Section2 </SectionName>
        <Title> Title2 </Title>
        <Header> Header2 </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>
    </Section>
</Root>

示例输入代码的输出应如下所示:

<Volume1>
    <Section1 Number="Section1" Name="NameOfDocument1" Title="Title1">
        <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> 
        </Header1>
    </Section1>

    <Section2 Number="Section2" Name="concat('NameOfDocument','2')" Title="Title2">
            <Step>
                Sub1 first
            </Step>
            <Step>
                Sub1 second
                <Step>
                    Sub2 first, Sub1 second
                </Step>
            </Step>
            <Step>
                Sub1 third
                <Step>
                    Sub2 first, Sub1 third
                </Step>
             </Step> 
    </Section2>
</Volume1>

感谢Dimitrie Novatchev,我现在有一些代码可以处理Section元素中的扁平部分。我有一个模板匹配的Section元素,然后我声明一个元素并从SectionName,Title,有时候Header获取信息,以填充元素将被调用的内容及其属性。我想跳过SectionName,Title,有时候Header,我不知道如何修改Dimitrie的代码。任何建议将不胜感激!谢谢!

Dimitrie的分组代码:

<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="/*">
        <Root>
            <xsl:apply-templates select="*[1]">
                <xsl:with-param name="pScope" select="*"/>
                <xsl:with-param name="pElemName" select="name(*[1])"/>
            </xsl:apply-templates>
         </Root>
    </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>

2 个答案:

答案 0 :(得分:1)

我会坚持使用细粒度的遍历方法。这个XSLT 1.0样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <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:variable name="vName">
                <xsl:apply-templates select="." mode="name"/>
            </xsl:variable>
            <xsl:element name="{$vName}">
                <xsl:apply-templates select="node()[1]"/>
                <xsl:apply-templates select="$vNext">
                    <xsl:with-param name="pNames"
                                    select="concat($pNames,name(),'|')"/>
                </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="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:template match="SectionName|Title|Header[1]">
        <xsl:variable name="vName">
            <xsl:apply-templates select="." mode="name"/>
        </xsl:variable>
        <xsl:attribute name="{$vName}">
            <xsl:value-of select="."/>
        </xsl:attribute>
        <xsl:apply-templates select="following-sibling::*[1]"/>
    </xsl:template>
    <xsl:template match="SectionName" mode="name">Number</xsl:template>
    <xsl:template match="Title" mode="name">Title</xsl:template>
    <xsl:template match="Header[1]" mode="name">Name</xsl:template>
    <xsl:template match="VolumeName|Section|Header" mode="name">
        <xsl:value-of select="translate((.|SectionName)[last()],' ','')"/>
    </xsl:template>
    <xsl:template match="Sub1|Sub2" mode="name">Step</xsl:template>
    <xsl:template match="*" mode="name">
        <xsl:value-of select="name()"/>
    </xsl:template>
    <xsl:template match="VolumeName/text()|Header/text()"/>
</xsl:stylesheet>

输出:

<Root>
    <Volume1>
        <Section1 Number=" Section1 " Title=" Title1 " 
                  Name=" NameOfDocument1 ">
            <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>
            </Header1>
        </Section1>
        <Section2 Number=" Section2 " Title=" Title2 " Name=" 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>
        </Section2>
    </Volume1>
</Root>

注意:因为计算出的名称比映射更复杂,所以我使用了模式匹配方法。

编辑:仅剥离空白文本节点(感谢@ Dimitre的评论),所以现在也在Saxon中显示正确的结果。

答案 1 :(得分:1)

以下是我的答案或最初问题的改编,以产生现在想要的结果:

<xsl:stylesheet version="2.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="/*">
  <Root>
   <xsl:element name="{translate(VolumeName,' ','')}">
    <xsl:apply-templates/>
   </xsl:element>
  </Root>
 </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:template match="VolumeName"/>

 <xsl:template match="Section">
  <xsl:element name=
      "{normalize-space(SectionName)}">
   <xsl:attribute name="Number"
        select="normalize-space(SectionName)"/>
   <xsl:attribute name="Name" select=
   "concat('NameOfDocument',
           count(preceding-sibling::Section)+1)"/>
   <xsl:attribute name="Title"
       select="normalize-space(Title)"/>"

   <xsl:variable name="vOutput">
    <xsl:apply-templates select="*[1]">
     <xsl:with-param name="pScope"
        select="Header[last()]/following-sibling::*"/>
     <xsl:with-param name="pElemName" select=
      "(normalize-space(Header[2]), 'Step')[last()]"/>
    </xsl:apply-templates>
   </xsl:variable>

   <xsl:choose>
    <xsl:when test="Header[2]">
     <xsl:element name="{normalize-space(Header[2])}">
      <xsl:sequence select="$vOutput"/>
     </xsl:element>
    </xsl:when>
    <xsl:otherwise>
      <xsl:sequence select="$vOutput"/>
    </xsl:otherwise>
   </xsl:choose>
  </xsl:element>
 </xsl:template>
 </xsl:stylesheet>

在提供的XML文档上应用此转换时:

<Root>
    <VolumeName>Volume 1 </VolumeName>
    <Section>
        <SectionName> Section1 </SectionName>
        <Title> Title1 </Title>
        <Header> NameOfDocument1 </Header>
        <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>
    </Section>
    <Section>
        <SectionName> Section2 </SectionName>
        <Title> Title2 </Title>
        <Header> Header2 </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>
    </Section>
</Root>

产生了想要的结果:

<Root>
    <Volume1>
        <Section1 Number="Section1" Name="NameOfDocument1" Title="Title1">"

            <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>
            </Header1>
        </Section1>
        <Section2 Number="Section2" Name="NameOfDocument2" Title="Title2">"

            <Step> Sub1 first </Step>
            <Step> Sub1 second 
                <Step> Sub2 first, Sub1 second </Step></Step>
            <Step> Sub1 third 
                <Step> Sub2 first, Sub1 third </Step></Step>
        </Section2>
    </Volume1>
</Root>