2011-05-13 277 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