【问题标题】:wrap sibling nodes based on on attribute根据属性包装兄弟节点
【发布时间】:2010-10-04 11:30:47
【问题描述】:

使用 XSLT,我如何包装具有相同属性值的兄弟姐妹。

假设我需要将一个或多个 <amendment/> 与它们所属的 <chapter/> 包装起来。 从此:

<section>
      <heading>some heading text</heading>
      <amendment num='1' chapter='1'>
            <foo/>
      </amendment>
      <amendment num='2' chapter='1'>
            <bar/>
      </amendment>
      <amendment num='3' chapter='2'>
            <baz/>
      </amendment>
      <heading>some heading text</heading>
      <amendment num='4' chapter='3'>
            <baz/>
      </amendment>
</section>

进入这个:

<section>
      <heading>some heading text</heading>
      <chapter num="1">
            <amendment num='1'>
                  <foo/>
            </amendment>
            <amendment num='2'>
                  <bar/>
            </amendment>
      </chapter>
      <chapter num="2">
            <amendment num='3'>
                  <baz/>
            </amendment>
      </chapter>
      <heading>some heading text</heading>
      <chapter num="3">
            <amendment num='4'>
                  <baz/>
            </amendment>
      </chapter>
</section>

注意 1:修改总是按源 XML 中的章节排序。

注意 2:我使用 PHP5 和 XSLT 1.0

【问题讨论】:

  • 您的输入 XML 是否在最后一个 amendment 标记上缺少 `chapter num="3"`?
  • 您打算使用 XSLT 1 还是 2?使用 2 更容易
  • 我已更新问题以回答这些问题。
  • 好问题 (+1)。请参阅我对纯推送式 XSLT 1.0 解决方案的回答。 :)

标签: xml xslt


【解决方案1】:

这个样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="kAmendementByChapter" match="amendment" use="@chapter"/>
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="amendment[count(.|key('kAmendementByChapter',
                                               @chapter)[1])=1]">
        <chapter num="{@chapter}">
            <xsl:apply-templates select="key('kAmendementByChapter',@chapter)"
                                 mode="copy"/>
        </chapter>
    </xsl:template>
    <xsl:template match="amendment"/>
    <xsl:template match="amendment" mode="copy">
        <xsl:call-template name="identity"/>
    </xsl:template>
    <xsl:template match="@chapter"/>
</xsl:stylesheet>

输出:

<section>
    <heading>some heading text</heading>
    <chapter num="1">
        <amendment num="1">
            <foo></foo>
        </amendment>
        <amendment num="2">
            <bar></bar>
        </amendment>
    </chapter>
    <chapter num="2">
        <amendment num="3">
            <baz></baz>
        </amendment>
    </chapter>
    <heading>some heading text</heading>
    <chapter num="3">
        <amendment num="4">
            <baz></baz>
        </amendment>
    </chapter>
</section>

注意:复制所有(indentity rule),在@chapter上分组。

【讨论】:

  • @Alejandro:+1 以获得最佳答案!
【解决方案2】:

如果您使用的是 XSLT 1,则可以像这样使用 Muenchian 分组方法:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="chapter" use="@chapter" match="amendment" />

  <xsl:template match="section">
    <xsl:copy>
      <xsl:apply-templates select="heading | amendment[generate-id() = generate-id(key('chapter',@chapter)[1])]" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="amendment">
    <xsl:element name="chapter">
      <xsl:attribute name="num">
        <xsl:value-of select="@chapter" />
      </xsl:attribute>
      <xsl:apply-templates select="key('chapter', @chapter)" mode="withoutchapter"/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="amendment" mode="withoutchapter">
    <xsl:copy>
      <xsl:apply-templates  select="@*[(name() != 'chapter')] | node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

这里有两个“修改”模板——第一个(没有模式)仅由章节模板调用,该修改是第一次出现该章节的修改。它创建一个章节元素,并在其中调用带有该章节的每个 amendment 标记上的第二个模板。

这里有两个警告;首先,任何没有章节的修改都将从输出中删除。

其次,如果两个修改标签之间有一个标题,修改标签仍然会被分组,标题会出现在分组之后。

所以,如果您这样做(为清楚起见,请缩写):

<amendment num='1' chapter='1' />
<heading>heading text</heading>
<amendment num='2' chapter='1' />

它会输出:

<chapter num='1'>
  <amendment num='1' />
  <amendment num='2' />
</chapter>
<heading>heading text</heading>

【讨论】:

    【解决方案3】:

    因为您的修改是按章节排序的,所以您可以不使用键,而只需查看紧随其后和前面的元素。以下应该有效:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
    <xsl:template match="amendment[not(@chapter = preceding-sibling::amendment[1]/@chapter)]">
      <chapter num="{@chapter}">
        <xsl:variable name="chapter" select="@chapter"/>
          <amendment num="{@num}">
            <xsl:apply-templates/>
          </amendment>
        <xsl:apply-templates select="following-sibling::amendment[1][@chapter = $chapter]">
          <xsl:with-param name="chapter" select="@chapter"/>
        </xsl:apply-templates>
      </chapter>
    </xsl:template>
    
    <xsl:template match="amendment">
      <xsl:param name="chapter"/>
      <xsl:if test="$chapter">
          <amendment num="{@num}">
            <xsl:apply-templates/>
          </amendment>
          <xsl:apply-templates select="following-sibling::amendment[1][@chapter = $chapter]">
            <xsl:with-param name="chapter" select="$chapter"/>
          </xsl:apply-templates>
      </xsl:if>
    </xsl:template>
    
      <xsl:template match="@* | node()">
        <xsl:copy>
          <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
      </xsl:template>
    
    </xsl:stylesheet>
    

    【讨论】:

      【解决方案4】:

      感谢 @Alejandro 和 @Flynn1179 在此解决方案的初始版本中发现错误 - 现在已更正!

      这种转变

      <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="kbyChapter" match="amendment"
           use="@chapter"/>
      
       <xsl:template match="node()" name="identity">
           <xsl:copy>
             <xsl:apply-templates select="@*"/>
             <xsl:apply-templates select="node()[1]"/>
           </xsl:copy>
           <xsl:apply-templates select="following-sibling::node()[1]"/>
       </xsl:template>
      
       <xsl:template match="amendment"/>
       <xsl:template match=
       "amendment[not(@chapter=preceding-sibling::amendment[1]/@chapter)]">
        <chapter num="{@chapter}">
         <xsl:apply-templates select="key('kbyChapter',@chapter)" mode="copy"/>
        </chapter>
        <xsl:apply-templates select=
          "key('kbyChapter',@chapter)[last()]/following-sibling::node()[1]"/>
       </xsl:template>
      
       <xsl:template match="*" mode="copy">
        <xsl:copy>
         <xsl:apply-templates select="@*|node()[1]"/>
        </xsl:copy>
       </xsl:template>
      
       <xsl:template match="@*">
        <xsl:copy-of select="."/>
       </xsl:template>
      
       <xsl:template match="@chapter"/>
      </xsl:stylesheet>
      

      应用于提供的 XML 文档时(对最后一个 amendment 进行更正):

      <section>
            <heading>some heading text</heading>
            <amendment num='1' chapter='1'>
                  <foo/>
            </amendment>
            <amendment num='2' chapter='1'>
                  <bar/>
            </amendment>
            <amendment num='3' chapter='2'>
                  <baz/>
            </amendment>
            <heading>some heading text</heading>
            <amendment num='4' chapter='3'>
                  <baz/>
            </amendment>
      </section>
      

      产生想要的正确结果

      <section>
          <heading>some heading text</heading>
          <chapter num="1">
              <amendment num="1">
                  <foo/>
              </amendment>
              <amendment num="2">
                  <bar/>
              </amendment>
          </chapter>
          <chapter num="2">
              <amendment num="3">
                  <baz/>
              </amendment>
          </chapter>
          <heading>some heading text</heading>
          <chapter num="3">
              <amendment num="4">
                  <baz/>
              </amendment>
          </chapter>
      </section>
      

      【讨论】:

      • 那些章节元素应该这样嵌套吗?
      • 我认为 Flynn1179 是对的。对于细粒度遍历,您需要关闭规则,例如 &lt;xsl:template match="*[last()]" mode="copy"&gt;
      • @Alejandro, @Flynn1179:感谢您注意到这一点。现在修好了。当我解决这个问题时,我的约会几乎迟到了——结果显示:)
      • +1 因为我喜欢在组外获得以下内容的表达方式。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-29
      • 2011-11-24
      • 2021-12-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多