【问题标题】:How to fold by a tag a group of selected (neighbor) tags with XSLT1?如何使用 XSLT1 按标签折叠一组选定(邻居)标签?
【发布时间】:2013-08-19 10:06:07
【问题描述】:

我有一组必须包含在新元素中的顺序节点。示例:

  <root>
    <c>cccc</c>
    <a gr="g1">aaaa</a>    <b gr="g1">1111</b>
    <a gr="g2">bbbb</a>   <b gr="g2">2222</b>
  </root>

必须用 fold 标记括起来,结果(在 XSLT 之后)为:

  <root>
    <c>cccc</c>
    <fold><a gr="g1">aaaa</a>    <b gr="g1">1111</b></fold>
    <fold><a gr="g2">bbbb</a>   <b gr="g2">2222</b></fold>
  </root>

所以,我有一个“分组标签”(@gr)但不知道如何生成正确的折叠标签。


我正在尝试使用this question 或this other one 的线索...但是我有一个“分组标签”,所以我知道我的解决方案不需要使用key() 函数。

我的非通用解决方案是:

   <xsl:template match="/">
       <root>
       <xsl:copy-of select="root/c"/>
       <fold><xsl:for-each select="//*[@gr='g1']">
             <xsl:copy-of select="."/>
       </xsl:for-each></fold>

       <fold><xsl:for-each select="//*[@gr='g2']">
             <xsl:copy-of select="."/>
       </xsl:for-each></fold>

       </root>
   </xsl:template>

我需要一个通用解决方案 (!),循环所有 @gr 并处理(身份)所有没有 @gr 的上下文...也许使用 身份转换。

另一个(未来的)问题是递归地执行此操作,折叠折叠。

【问题讨论】:

  • 每个组的gr 值在root 中是否总是不同的,如果不是,那么如果你有&lt;root&gt;&lt;a gr="g1"&gt;a1&lt;/a&gt;&lt;b gr="g1"&gt;b1&lt;/b&gt; &lt;a gr="g2"&gt;a2&lt;/a&gt;&lt;b gr="g2"&gt;b2&lt;/b&gt; &lt;a gr="g1"&gt;a3&lt;/a&gt;&lt;b gr="g1"&gt;b3&lt;/b&gt;&lt;/root&gt;,那么应该给你三个fold 元素(a1/b1,a2 /b2, a3/b3) 还是两个 (a1/b1/a3/b3, a2/b2)?
  • 你可以使用“二”假设,但是在我的真正问题中,@gr 是一个不重复的序列,也就是说,如果一个序列停止它就不会再出现(下一组将是 g3,g2 之后不可能是 g1)。
  • 好的,这个例子是你完整的 XML 文档还是你有几个不同的 root 元素需要单独“折叠”?
  • 我编辑了...并将“邻居”添加到问题标题中。关于“折叠的折叠”现在不是问题,但我想我将来会遇到这个问题。

标签: xslt fold


【解决方案1】:

在 XSLT 1.0 中,处理这类事情的标准技术称为 Muenchian 分组,它涉及使用定义节点应如何分组的 key 以及使用 generate-id 的技巧仅提取每个组中的第一个节点作为整个组的代理。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:strip-space elements="*" />
  <xsl:output indent="yes" />
  <xsl:key name="elementsByGr" match="*[@gr]" use="@gr" />

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

  <!-- match the first element with each @gr value -->
  <xsl:template match="*[@gr][generate-id() =
         generate-id(key('elementsByGr', @gr)[1])]" priority="2">
    <fold>
      <xsl:for-each select="key('elementsByGr', @gr)">
        <xsl:call-template name="identity" />
      </xsl:for-each>
    </fold>
  </xsl:template>

  <!-- ignore subsequent ones in template matching, they're handled within
       the first element template -->
  <xsl:template match="*[@gr]" priority="1" />
</xsl:stylesheet>

这实现了您所追求的分组,但就像您的非通用解决方案一样,它不会保留 a 和 b 元素之间的缩进和空白文本节点,即它会给你

<root>
  <c>cccc</c>
  <fold>
    <a gr="g1">aaaa</a>
    <b gr="g1">1111</b>
  </fold>
  <fold>
    <a gr="g2">bbbb</a>
    <b gr="g2">2222</b>
  </fold>
</root>

请注意,如果您能够使用 XSLT 2.0,那么整个事情就变成了一个 for-each-group:

<xsl:template match="root">
  <xsl:for-each-group select="*" group-adjacent="@gr">
    <xsl:choose>
      <!-- wrap each group in a fold -->
      <xsl:when test="@gr">
        <fold><xsl:copy-of select="current-group()" /></fold>
      </xsl:when>
      <!-- or just copy as-is for elements that don't have a @gr -->
      <xsl:otherwise>
        <xsl:copy-of select="current-group()" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each-group>
</xsl:template>

【讨论】:

  • 很好的解释和优雅的解决方案!
  • @PeterKrauss 我也添加了一个可能的 XSLT 2.0 解决方案,以进行比较。
  • 是的,XSLT2 是如此简单和直观... 我讨厌 XSLT1 (!!),但是 LibXML2 和 PHP 只提供 XSLT1...
  • PS:我在 this new question 的未来问题上领先于你。
猜你喜欢
  • 1970-01-01
  • 2012-04-18
  • 1970-01-01
  • 2014-02-25
  • 2017-07-28
  • 1970-01-01
  • 1970-01-01
  • 2011-11-01
  • 1970-01-01
相关资源
最近更新 更多