用于合并具有相同名称和属性值的同级元素的XSLT 2.0解决方案

时间:2012-09-30 16:17:44

标签: xslt xslt-2.0

我正在寻找一个可以转变的解决方案

<p>
<hi rend="bold">aa</hi>
<hi rend="bold">bb</hi>
<hi rend="bold">cc</hi>
Perhaps some text.
<hi rend="italic">dd</hi>
<hi rend="italic">ee</hi>
Some more text.
<hi rend="italic">ff</hi>
<hi rend="italic">gg</hi>
Foo.
</p>

<p>
<hi rend="bold">aabbcc</hi>
Perhaps some text.
<hi rend="italic">ddee</hi>
Perhaps some text.
<hi rend="italic">ffgg</hi>
Foo. 
</p>

但我的解决方案应该_不硬编码元素和属性值的名称(斜体,粗体)。 XSLT应该真正连接具有相同名称和相同属性值的所有兄弟元素。其他一切都应保持不变。

我已经看过那些已经存在的解决方案,但它们似乎都没有满足我的所有要求。

如果有人为此提供了方便的XSLT样式表,我将非常感激。

3 个答案:

答案 0 :(得分:6)

此XSLT 2.0样式表将使用公共rend属性合并相邻元素。

<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="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()" />
  </xsl:copy>
</xsl:template>

<xsl:template match="*[*/@rend]">
  <xsl:copy>
    <xsl:apply-templates select="@*" />
    <xsl:for-each-group select="node()" group-adjacent="
       if (self::*/@rend) then
           concat( namespace-uri(), '|', local-name(), '|', @rend)
         else
           ''">
      <xsl:choose>
        <xsl:when test="current-grouping-key()" >
          <xsl:for-each select="current-group()[1]">
            <xsl:copy>
              <xsl:apply-templates select="@* | current-group()/node()" />
            </xsl:copy>
          </xsl:for-each>
        </xsl:when>
        <xsl:otherwise>
         <xsl:apply-templates select="current-group()" />
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each-group>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

这种解决方案优于Martin的优点是:

  • 这将合并所有父元素,而不仅仅是p元素。
  • 更快。合并是通过单个xsl完成的:for-each而不是两个嵌套的xsl:for-each
  • head merge-able元素的非rend属性被复制到输出。

另请注意:

  • xsl:strip-space指令完全避免了为了确定具有公共名称和rend属性值的“相邻”元素而被排除的纯白色空间节点的测试。因此,xsl:for-each指令非常简单易读。
  • 作为组相邻属性值的替代方法,您可以改为使用......

    <xsl:for-each-group select="node()" group-adjacent="
       string-join(for $x in self::*/@rend return
         concat( namespace-uri(), '|', local-name(), '|', @rend),'')">
    

    使用您个人认为更具可读性的表格。

答案 1 :(得分:1)

该属性的名称(例如rend)是否已知?在那种情况下,我认为你想要

<xsl:template match="p">
  <xsl:copy>
    <xsl:for-each-group select="*" group-adjacent="concat(node-name(.), '|', @rend)">
      <xsl:element name="{name()}" namespace="{namespace-uri()}">
         <xsl:copy-of select="@rend"/>
         <xsl:apply-templates select="current-group()/node()"/>
      </xsl:element>
     </xsl:for-each-group>
  </xsl:copy>
</xsl:template>

[编辑] 如果在元素之间可能存在包含内容的文本节点,正如您在输入的编辑中所示,那么您需要像示例一样嵌套到分组

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="2.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xs">

<xsl:template match="p">
  <xsl:copy>
    <xsl:for-each-group select="node() except text()[not(normalize-space())]" group-adjacent="boolean(self::*)">
      <xsl:choose>
        <xsl:when test="current-grouping-key()">
          <xsl:for-each-group select="current-group()" group-by="concat(node-name(.), '|', @rend)">
            <xsl:element name="{name()}" namespace="{namespace-uri()}">
               <xsl:copy-of select="@rend"/>
               <xsl:apply-templates select="current-group()/node()"/>
            </xsl:element>
          </xsl:for-each-group>
        </xsl:when>
        <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
        </xsl:otherwise>
      </xsl:choose>
     </xsl:for-each-group>
  </xsl:copy>
</xsl:template>

</xsl:stylesheet>

答案 2 :(得分:1)

如果有偶然的访问者出现并想知道是否有针对此问题的XSLT 1.0解决方案,我提供以下内容。 请注意,我并没有试图减少肖恩和马丁的正确答案;我只是提供一些味道。

当这个XSLT 1.0解决方案:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output omit-xml-declaration="no" indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:key
     name="kFollowing" 
     match="hi" 
     use="concat(@rend, '+', generate-id(following-sibling::text()[1]))" />

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

  <xsl:template match="/*">
    <p>
      <xsl:apply-templates 
        select="
          hi[generate-id() = 
             generate-id(
           key('kFollowing', 
             concat(@rend, '+', generate-id(following-sibling::text()[1])))[1])]" />
    </p>
  </xsl:template>

  <xsl:template match="hi">
    <xsl:copy>
      <xsl:apply-templates 
        select="@*|key('kFollowing', 
          concat(@rend, '+', generate-id(following-sibling::text()[1])))/text()" />
    </xsl:copy>
    <xsl:apply-templates select="following-sibling::text()[1]" />
  </xsl:template>

</xsl:stylesheet>

...适用于OP的原始XML:

<p>
<hi rend="bold">aa</hi>
<hi rend="bold">bb</hi>
<hi rend="bold">cc</hi>
Perhaps some text.
<hi rend="italic">dd</hi>
<hi rend="italic">ee</hi>
Some more text.
<hi rend="italic">ff</hi>
<hi rend="italic">gg</hi>
Foo.
</p>

...产生了所需的结果:

<p>
<hi rend="bold">aabbcc</hi>
Perhaps some text.
<hi rend="italic">ddee</hi>
Perhaps some text.
<hi rend="italic">ffgg</hi>
Foo. 
</p>