2009-11-05 74 views
4

如何使用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"/>(不同的屬性值)
+1

該問題看起來有點不確定。在「元素」情況下,您在id屬性上匹配,但在子元素上匹配標記名本身。子子元素呢? – 2009-11-05 20:18:45

+0

感謝您的評論,我添加了一個更明確的元素何時合併的定義。我希望這回答了你的問題。 – Chris 2009-11-05 20:42:31

回答

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> 

棘手的部分是相同元素定義;按名稱對屬性進行索引對於驗證所有屬性的實例是強制性的。

+0

我用較短的代碼編輯;我想這是有史以來最短的代碼... – Erlock 2009-11-09 09:36:34

+0

這正是我想要的。 – Chris 2009-11-09 17:12:50

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> 

這是關閉我的頭頂,所以它可能需要一點調整。

爲了使這個工作遞歸是一個更多的問題。當您在第一個Element標記中處理SubElement2時,您需要解析第二個Element中的SubElement2。這將變得相當複雜,以處理任意深度。
我不知道你的具體用例,但簡單的答案可能是反覆運行上述變換,直到結果與輸入相同。

擴展if語句以便爲同名元素激發應該很容易。

1

如果您使用的是XSLT2,則應該可以使用分組工具。下面是一個早期的教程:

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

這是以後的一個由誰生產優秀教程一組寫着:

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

如果僅限於XSLT1這是可能的,但更難。

如果你還停留嘗試戴夫Pawson的常見問題解答:http://www.dpawson.co.uk/xsl/sect2/N4486.html