4

我正在寻找一种解决方案

<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 样式表,我将不胜感激。

4

3 回答 3

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 完成的
  • 头部可合并元素的非渲染属性被复制到输出。

另请注意:

  • xsl:strip-space 指令完全消除了对纯空白节点的测试,该测试是为了确定具有通用名称和rend 属性值的“相邻”元素而排除的。因此 xsl:for-each 指令相当简单易读。
  • 作为 group-adjacent 属性值的替代方案,您可以使用...

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

    使用您个人认为更具可读性的任何形式。

于 2012-10-01T12:46:04.983 回答
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>
于 2012-09-30T16:30:09.007 回答
1

In case a casual visitor should come along and wonder if there is an XSLT 1.0 solution for this problem, I offer the following. Note that I am not trying to diminish from Sean and Martin's correct answers; I am merely offering some flavor.

When this XSLT 1.0 solution:

<?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>

...is applied to the OP's original 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>

...the desired result is produced:

<p>
<hi rend="bold">aabbcc</hi>
Perhaps some text.
<hi rend="italic">ddee</hi>
Perhaps some text.
<hi rend="italic">ffgg</hi>
Foo. 
</p>
于 2012-10-06T15:43:23.080 回答