xml – XSLT 1.0 – 将具有子节点的兄弟节点合并到新的复合节点中

我很难制定问题标题.也许这个例子会更有意义.

假设我有一个从系统A看起来像这样的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">a@home</email_address>
        <email_address type="other">b@other</email_address>
        <email_address type="home">c@home</email_address>
        <email_address type="work">d@work</email_address>
        <email_address type="other">e@other</email_address>
        <email_address type="other">f@other</email_address>
    </email_addresses>
</root>

我必须将这些结合到这样的结构中,以便它们可以在系统B中使用:

<root>
    <addresses>
        <address name="work1">
            <phone_number>123-WORK</phone_number>
            <email_address>d@work</email_address>
        </address>
        <address name="work2">
            <phone_number>789-WORK</phone_number>
        </address>
        <address name="other1">
            <phone_number>012-OTHER</phone_number>
            <email_address>b@other</email_address>
        </address>
        <address name="other2">
            <email_address>e@other</email_address>
        </address>
        <address name="other3">
            <email_address>f@other</email_address>
        </address>
        <address name="home1">
            <phone_number>456-HOME</phone_number>
            <email_address>a@home</email_address>
        </address>
        <address name="home2">
            <email_address>c@home</email_address>
        </address>
    </addresses>
</root>

每种类型的电子邮件地址都可以有任何数字(从0到无穷大,据我所知).每种类型的电话号码也可以是任意数量,并且一种类型的电话号码的数量不必与相同类型的电子邮件地址的数量相匹配.

第一个文档中的电子邮件地址和电话号码彼此并不真正相关,只是它们按照它们添加到系统A的顺序输入.

我必须按类型配对电子邮件和电话号码以适应系统B,我想将它们配对,以便第一个类型X的电话号码与第一个类型X的电子邮件地址配对,这样就没有电话号码类型X的类型与X以外的类型的电子邮件配对.

因为我必须将它们配对,并且由于它们被输入到系统中的顺序是我最接近找到对之间的关​​系,我想以这种方式命令它们.我必须告诉用户查看结果,确保它们有意义,但我必须配对它们 – 别无选择.

更复杂的是,我的实际XML文档有更多的节点,我需要与phone_numbers和email_addresses合并,而且我有两个以上的@types.

另一个注意事项:我已经在计算任何给定@type的节点的最大数量,因此在我的示例文档中,我知道< address>的最大数量.单个@type的节点是三个(三个< email_address>节点,其中@type = other =三个< address>节点,其中@ name = otherX).

最佳答案 这种转换非常简单(只有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">a@home</email_address>
        <email_address type="other">b@other</email_address>
        <email_address type="home">c@home</email_address>
        <email_address type="work">d@work</email_address>
        <email_address type="other">e@other</email_address>
        <email_address type="other">f@other</email_address>
    </email_addresses>
</root>

产生了想要的正确结果:

<root>
   <addresses>
      <address name="work1">
         <phone_number>123-WORK</phone_number>
         <email_address>d@work</email_address>
      </address>
      <address name="work2">
         <phone_number>789-WORK</phone_number>
      </address>
      <address name="home1">
         <phone_number>456-HOME</phone_number>
         <email_address>a@home</email_address>
      </address>
      <address name="home2">
         <email_address>c@home</email_address>
      </address>
      <address name="other1">
         <phone_number>012-OTHER</phone_number>
         <email_address>b@other</email_address>
      </address>
      <address name="other2">
         <email_address>e@other</email_address>
      </address>
      <address name="other3">
         <email_address>f@other</email_address>
      </address>
   </addresses>
</root>

说明:

>使用Muenchian方法进行分组,在$vallTypes变量中收集type属性的所有不同值.
>对于上面1.中找到的每个不同的值, 元素输出如下.
>生成name属性,其值为当前类型和当前位置()的串联.
>在变量中捕获两个节点集:一个包含具有其type属性的特定值的所有phone_number元素,另一个包含具有其type属性的特定值的所有email_address元素.
>对于这两个节点集中较长的每个元素,在最终输出中使用一个元素或(如果可能的话,来自两个节点集的一对元素)生成(省略类型属性`).

点赞