2011-05-13 173 views
4

我做了一個艱難的時期制定的問題標題。也許這個例子會更有意義。XSLT 1.0 - 合併與子節點的兄弟節點爲新的複合節點

假設我有一個看起來像這樣的系統中的一個XML文檔:

<root> 
    <phone_numbers> 
     <phone_number type="work">123-WORK</phone_number> 
     <phone_number type="home">456-HOME</phone_number> 
     <phone_number type="work">789-WORK</phone_number> 
     <phone_number type="other">012-OTHER</phone_number> 
    </phone_numbers> 
    <email_addresses> 
     <email_address type="home">[email protected]</email_address> 
     <email_address type="other">[email protected]</email_address> 
     <email_address type="home">[email protected]</email_address> 
     <email_address type="work">[email protected]</email_address> 
     <email_address type="other">[email protected]</email_address> 
     <email_address type="other">[email protected]</email_address> 
    </email_addresses> 
</root> 

而且我必須適應這些成這樣,使他們可以在系統B中使用的結構:

<root> 
    <addresses> 
     <address name="work1"> 
      <phone_number>123-WORK</phone_number> 
      <email_address>[email protected]</email_address> 
     </address> 
     <address name="work2"> 
      <phone_number>789-WORK</phone_number> 
     </address> 
     <address name="other1"> 
      <phone_number>012-OTHER</phone_number> 
      <email_address>[email protected]</email_address> 
     </address> 
     <address name="other2"> 
      <email_address>[email protected]</email_address> 
     </address> 
     <address name="other3"> 
      <email_address>[email protected]</email_address> 
     </address> 
     <address name="home1"> 
      <phone_number>456-HOME</phone_number> 
      <email_address>[email protected]</email_address> 
     </address> 
     <address name="home2"> 
      <email_address>[email protected]</email_address> 
     </address> 
    </addresses> 
</root> 

可以有任何數(從0到無窮大,因爲據我所知),每種類型的電子郵件地址。此外,還可以是任意數量的每種類型的電話號碼,和一種類型的電話號碼的數量不必匹配相同類型的電子郵件地址的數量。

第一文檔中的電子郵件地址和電話號碼是不是真的彼此相關,但它們在它們添加到系統A的順序輸入

我要配對的電子郵件和電話數字增長型以適應系統B,我想它們配對,使X型的第一個電話號碼是搭配X型的第一個電子郵件地址等X型的沒有電話號碼是搭配的電子郵件比十,其他類型的

因爲我有配對起來,由於他們的順序被輸入到系統中,我會去尋找對之間的關​​系最密切,我想訂購他們THI的方式。我必須告訴用戶去查看結果,確保它們有意義,但我必須將它們配對 - 別無選擇。

使事情複雜化,我的實際XML文檔有更多的節點,我需要與phone_numbers和email_addresses合併,並且我有兩個以上的@types

另外一個注意:我已經計算節點的最大數量與任何給定的@type,所以我的例子文檔,我知道一個@type<address>節點的最大數目爲3(三級<email_address>@type=other節點=與@name=otherX 3個<address>節點)。

+0

類型的順序是否重要? 「other」類型在第二個「email_address」或第四個「phone_number」之前不會出現,但該類型在輸出中是第二個。 – 2011-05-13 20:45:27

+0

類型的訂購無關緊要。 – Tex 2011-05-13 21:51:08

+0

好問題,+1。看到我的解決方案相當簡單的解決方案:) – 2011-05-14 02:28:42

回答

1

這種轉變是相當簡單的(只有3個模板和無模式):

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

<xsl:key name="kTypeByVal" match="@type" use="."/> 

<xsl:key name="kPhNumByType" match="phone_number" 
    use="@type"/> 

<xsl:key name="kAddrByType" match="email_address" 
    use="@type"/> 

<xsl:variable name="vallTypes" select= 
"/*/*/*/@type 
      [generate-id() 
      = 
      generate-id(key('kTypeByVal',.)[1]) 
      ]"/> 

<xsl:template match="/"> 
    <root> 
    <addresses> 
    <xsl:apply-templates select="$vallTypes"/> 
    </addresses> 
    </root> 
</xsl:template> 

<xsl:template match="@type"> 
    <xsl:variable name="vcurType" select="."/> 
    <xsl:variable name="vPhoneNums" select="key('kPhNumByType',.)"/> 
    <xsl:variable name="vAddresses" select="key('kAddrByType',.)"/> 

    <xsl:variable name="vLonger" select= 
    "$vPhoneNums[count($vPhoneNums) > count($vAddresses)] 
    | 
    $vAddresses[not(count($vPhoneNums) > count($vAddresses))] 
    "/> 

    <xsl:for-each select="$vLonger"> 
    <xsl:variable name="vPos" select="position()"/> 
    <address name="{$vcurType}{$vPos}"> 
    <xsl:apply-templates select="$vPhoneNums[position()=$vPos]"/> 
    <xsl:apply-templates select="$vAddresses[position()=$vPos]"/> 
    </address> 
    </xsl:for-each> 
</xsl:template> 

<xsl:template match="phone_number|email_address"> 
    <xsl:copy> 
    <xsl:copy-of select="node()"/> 
    </xsl:copy> 
</xsl:template> 
</xsl:stylesheet> 

當所提供的XML文檔(和所描述的特性的任何文件)施加:

<root> 
    <phone_numbers> 
     <phone_number type="work">123-WORK</phone_number> 
     <phone_number type="home">456-HOME</phone_number> 
     <phone_number type="work">789-WORK</phone_number> 
     <phone_number type="other">012-OTHER</phone_number> 
    </phone_numbers> 
    <email_addresses> 
     <email_address type="home">[email protected]</email_address> 
     <email_address type="other">[email protected]</email_address> 
     <email_address type="home">[email protected]</email_address> 
     <email_address type="work">[email protected]</email_address> 
     <email_address type="other">[email protected]</email_address> 
     <email_address type="other">[email protected]</email_address> 
    </email_addresses> 
</root> 

有用,正確的結果產生

<root> 
    <addresses> 
     <address name="work1"> 
     <phone_number>123-WORK</phone_number> 
     <email_address>[email protected]</email_address> 
     </address> 
     <address name="work2"> 
     <phone_number>789-WORK</phone_number> 
     </address> 
     <address name="home1"> 
     <phone_number>456-HOME</phone_number> 
     <email_address>[email protected]</email_address> 
     </address> 
     <address name="home2"> 
     <email_address>[email protected]</email_address> 
     </address> 
     <address name="other1"> 
     <phone_number>012-OTHER</phone_number> 
     <email_address>[email protected]</email_address> 
     </address> 
     <address name="other2"> 
     <email_address>[email protected]</email_address> 
     </address> 
     <address name="other3"> 
     <email_address>[email protected]</email_address> 
     </address> 
    </addresses> 
</root> 

說明

  1. type屬性的所有不同值在$vallTypes變量中被收集,使用Muenchian方法進行分組。

  2. 對於上面1.中找到的每個不同值,<address>元素輸出如下。

  3. name屬性是與值產生的電流type和當前position()的級聯

  4. 兩個節點集在變量捕獲:含具有其type屬性的該特定值的所有元素phone_number一個,和另一種含具有其type屬性的該特定值的所有email_address元素。

  5. 對於這兩個節點集的一個元件或的較長的每一個元素(如果可能的話一對從所述兩個節點集的元素)被用來/要產生(省略type attribute`)在最後的輸出中。

+0

這工作就像一個魅力。正如你可以想象的,我的示例文檔非常簡化,但是我能夠修改你的工作樣式表來轉換實際的(實際上更復雜的)數據。 – Tex 2011-05-16 23:40:10

+0

@Tex:不客氣。 – 2011-05-17 01:40:52

1

這個樣式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:key name="byType" match="/root/*/*" use="@type" /> 
    <xsl:key name="phoneByType" match="phone_numbers/phone_number" 
     use="@type" /> 
    <xsl:key name="emailByType" match="email_addresses/email_address" 
     use="@type" /> 
    <xsl:template match="/"> 
     <root> 
      <addresses> 
       <xsl:apply-templates /> 
      </addresses> 
     </root> 
    </xsl:template> 
    <xsl:template match="/root/*/*" /> 
    <xsl:template 
     match="/root/*/*[generate-id()=generate-id(key('byType', @type)[1])]"> 
     <xsl:apply-templates select="key('phoneByType', @type)" 
      mode="wrap" /> 
     <xsl:apply-templates 
      select="key('emailByType', @type) 
       [position() > count(key('phoneByType', @type))]" 
      mode="wrap" /> 
    </xsl:template> 
    <xsl:template match="phone_numbers/phone_number" mode="wrap"> 
     <xsl:variable name="pos" select="position()" /> 
     <address name="{concat(@type, $pos)}"> 
      <xsl:apply-templates select="." mode="out" /> 
      <xsl:apply-templates select="key('emailByType', @type)[$pos]" 
       mode="out" /> 
     </address> 
    </xsl:template> 
    <xsl:template match="email_addresses/email_address" mode="wrap"> 
     <address 
      name="{concat(@type, 
          position() + count(key('phoneByType', @type)))}"> 
      <xsl:apply-templates select="." mode="out" /> 
     </address> 
    </xsl:template> 
    <xsl:template match="/root/*/*" mode="out"> 
     <xsl:copy> 
      <xsl:apply-templates /> 
     </xsl:copy> 
    </xsl:template> 
</xsl:stylesheet> 

在此輸入:

<root> 
    <phone_numbers> 
     <phone_number type="work">123-WORK</phone_number> 
     <phone_number type="home">456-HOME</phone_number> 
     <phone_number type="work">789-WORK</phone_number> 
     <phone_number type="other">012-OTHER</phone_number> 
    </phone_numbers> 
    <email_addresses> 
     <email_address type="home">[email protected]</email_address> 
     <email_address type="other">[email protected]</email_address> 
     <email_address type="home">[email protected]</email_address> 
     <email_address type="work">[email protected]</email_address> 
     <email_address type="other">[email protected]</email_address> 
     <email_address type="other">[email protected]</email_address> 
     <email_address type="test">[email protected]</email_address> 
    </email_addresses> 
</root> 

產地:

<root> 
    <addresses> 
     <address name="work1"> 
      <phone_number>123-WORK</phone_number> 
      <email_address>[email protected]</email_address> 
     </address> 
     <address name="work2"> 
      <phone_number>789-WORK</phone_number> 
     </address> 
     <address name="home1"> 
      <phone_number>456-HOME</phone_number> 
      <email_address>[email protected]</email_address> 
     </address> 
     <address name="home2"> 
      <email_address>[email protected]</email_address> 
     </address> 
     <address name="other1"> 
      <phone_number>012-OTHER</phone_number> 
      <email_address>[email protected]</email_address> 
     </address> 
     <address name="other2"> 
      <email_address>[email protected]</email_address> 
     </address> 
     <address name="other3"> 
      <email_address>[email protected]</email_address> 
     </address> 
     <address name="test1"> 
      <email_address>[email protected]</email_address> 
     </address> 
    </addresses> 
</root> 

說明:

  • 有三個組:1)所有聯繫信息的類型; 2)所有類型的電話號碼; 3)按類型
  • 第一組用於通過每一個電話號碼,讓每種類型的
  • 然後我們去的第一次出現在同一位置的所有電子郵件地址,配對使用任何電子郵件地址
  • 最後,我們佔所有電子郵件地址不具有相應的電話號碼
+0

這看起來很有希望。我會在這個週末把它放在一邊,讓你知道。謝謝! – Tex 2011-05-13 21:54:27

+0

如果你在那之前下降,我想重申,我有兩個以上的節點(準確地說是4),我有三種以上的類型(4)。我仍然試圖圍繞着你的樣式表,所以我不確定這個差異有多大。 – Tex 2011-05-13 22:06:57

+0

@tex - 這將處理新的'type'屬性而不做任何修改 - 注意我添加了一個名爲'test'的演示來證明這一點 - 但它需要更改以支持新的聯繫人元素(電話號碼和電子郵件地址除外) )。 – 2011-05-15 21:13:19