在XSLT中递归地组合相同的兄弟元素

时间:2009-11-05 19:29:02

标签: xml xslt

如何使用XSLT将具有相同名称和相同属性的所有同级元素合并到单个元素中?转换也应递归地应用于正在合并的元素的子元素。这是源文件:

<?xml version="1.0"?>
<Root>
  <Element id="UniqueId1">
    <SubElement1/>
    <SubElement2>
      <LeafElement1/>
    </SubElement2>
  </Element>
  <Element id="UniqueId1">
    <SubElement2>
      <LeafElement1/>
      <LeafElement2/>
    </SubElement2>
    <SubElement3/>
  </Element>
  <Element id="UniqueId2">
    <SubElement1/>
    <SubElement4/>
  </Element>    
</Root>

应该转换为:

<?xml version="1.0"?>
<Root>
  <Element id="UniqueId1">
    <SubElement1/>
    <SubElement2>
      <LeafElement1/>
      <LeafElement2/>
    </SubElement2>
    <SubElement3/>
  </Element>
  <Element id="UniqueId2">
    <SubElement1/>
    <SubElement4/>
  </Element>    
</Root>

具有相同名称和属性的任何元素都合并为一个元素。然后,他们的孩子接受检查。如果其中任何一个具有相同的名称和相同的属性,则将它们组合在一起。此转换以递归方式应用于所有元素。

编辑:为了澄清,所有这些条件必须为两个要合并的元素。

  • 它们具有相同的元素名称
  • 它们具有相同的属性
  • 每个对应属性的值相同
  • 他们是兄弟姐妹(递归应用,因此在考虑他们的孩子之前,任何相同的父元素都会被合并和组合)

这些元素是相同的,应该合并:

  • <Item/><Item/>(同名,相同的属性)
  • <Item Attr="foo"/><Item Attr="foo"/>(同名,相同的属性)

这些元素不相同,不应合并:

  • <Item/><SubItem/>(姓名不同)
  • <Item Attr="foo"/><Item/>(不同属性)
  • <Item Attr="foo"/><Item Attr="bar"/>(不同的属性值)

3 个答案:

答案 0 :(得分:3)

我能想到的最简单的方法是在遇到具有该ID的第一个元素时解析具有相同ID的所有元素。看起来像这样:

<xsl:variable name="curID" select="@id"/>
<xsl:if test="count(preceding-sibling::*[@id=$curID])=0">
  <xsl:copy>
    <xsl:apply-templates select="@*"/>
    <xsl:for-each select="following-sibling::*[@id=$curID]">
      <xsl:apply-templates select="@*"/>
    </xsl:for-each>
    <xsl:apply-templates select="node()"/>
    <xsl:for-each select="following-sibling::*[@id=$curID]">
      <xsl:apply-templates select="node()"/>
    </xsl:for-each>
  </xsl:copy>
</xsl:if>

这是我的头脑,所以可能需要一些调整。

为了让这个工作起来,recursivly是一个更大的问题。在第一个Element标记中处理SubElement2时,需要解析第二个Element中的SubElement2。这将使得处理任意深度变得相当复杂 我不知道你的特定用例,但简单的答案可能是重复运行上面的转换,直到结果与输入相同。

扩展if语句以激发具有相同名称的元素应该很容易。

答案 1 :(得分:3)

这应该做的工作:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" indent="yes"/>
  <xsl:key name="atts-by-name" match="@*" use="name()"/>
  <xsl:template match="Root">
    <xsl:copy>
      <xsl:call-template name="merge">
        <xsl:with-param name="elements" select="*"/>
      </xsl:call-template>
    </xsl:copy>
  </xsl:template>
  <xsl:template name="merge">
    <xsl:param name="elements"/>
    <xsl:for-each select="$elements">
      <xsl:variable name="same-elements" select="$elements[name()=name(current()) and count(@*)=count(current()/@*) and count(@*[. = key('atts-by-name',name())[generate-id(..)=generate-id(current())]])=count(@*)]"/>
      <xsl:if test="generate-id($same-elements[1]) = generate-id()">
        <xsl:copy>
          <xsl:copy-of select="@*"/>
          <xsl:call-template name="merge">
            <xsl:with-param name="elements" select="$same-elements/*"/>
          </xsl:call-template>
        </xsl:copy>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

棘手的部分是 same-elements 的定义;按名称索引属性对于验证所有属性的真实性是必需的。

答案 2 :(得分:1)

如果您使用的是XSLT2,您应该可以使用分组工具。这是一个早期教程:

http://www.xml.com/pub/a/2003/11/05/tr.html

以下是一个由制作优秀教程的小组撰写的文章:

http://www.zvon.org/xxl/XSL-Ref/Tutorials/index.html

如果你被限制在XSLT1,那么它可能会更难。

如果你仍然被困,请尝试Dave Pawson的常见问题:http://www.dpawson.co.uk/xsl/sect2/N4486.html