XSLT 1.0 - 将兄弟节点与子节点合并到新的复合节点中

发布于 2024-11-06 22:17:51 字数 2900 浏览 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 文档有更多节点,我需要将它们与电话号码和电子邮件地址合并,并且我有两个以上的 @types

另请注意:我已经计算了任何给定 @type 的最大节点数,因此通过我的示例文档,我知道

的最大数量> 单个 @type 的节点为三个(三个 节点与 @type=other = 三个
; 节点与 @name=otherX)。

I had a tough time formulating the question title. Maybe the example will make more sense.

Suppose I have an XML document that looks like this from system A:

<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>

And I have to fit these into a structure like this so they can be used in system 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>

There can be any number (from 0 to infinity, as far as I know) of email addresses of each type. There can also be any number of phone numbers of each type, and the number of phone numbers of one type does not have to match the number of email addresses of the same type.

The email addresses and phone numbers in the first document aren't really related to each other, except that they are entered in the order they were added to system A.

I have to pair the emails and phone numbers up by type to fit into system B, and I would like to pair them so that the first phone number of type X is paired with the first email address of type X and so that no phone number of type X is paired with an email of a type other than X.

Since I have to pair them up, and since the order they were entered into the system is the closest I'll get to finding a relationship between the pairs, I would like to order them this way. I'll have to tell the users to go over the results, to make sure they make sense, but I have to pair them - no choice.

To complicate matters, my actual XML document has more nodes that I'll need to merge with phone_numbers and email_addresses, and I have more than two @types.

One other note: I'm already calculating the maximum number of nodes with any given @type, so with my example docs, I know that the maximum number of <address> nodes of a single @type is three (three <email_address> nodes with @type=other = three <address> nodes with @name=otherX).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

听风念你 2024-11-13 22:17:51

此转换相当简单(只有 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>

解释

  1. type属性的所有不同值都收集在$vallTypes 变量,使用 Muenchian 方法

  2. 对于上面 1. 中找到的每个不同值,将输出

    元素,如下所示。

  3. name 属性由当前 type 和当前 position() 的串联值生成.

  4. 在变量中捕获两个节点集:一个包含具有此特定 type 属性值的所有 phone_number 元素,另一个包含所有具有此特定 type 属性值的 email_address 元素。

  5. 对于这两个节点集中较长的一个元素的每个元素,将使用一个元素或(如果可能的话,来自两个节点集的一对元素)生成(省略 < code>type attribute`)在最终输出中。

This transformation is quite simpler (only 3 templates and no modes):

<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>

when applied on the provided XML document (and any document with the described properties):

<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>

the wanted, correct result is produced:

<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>

Explanation:

  1. All different values of the type attribute are collected in the $vallTypes variable, using the Muenchian method for grouping.

  2. For every distinct value found in 1. above, an <address> element is output as follows.

  3. A name attribute is generated with value the concatenation of the current type and the current position().

  4. Two nodesets are captured in variables: one containing all phone_number elements that has this specific value of their type attribute, and another containing all email_address elements that has this specific value of their type attribute.

  5. For every element of the longer of these two node-sets one element or (if possible a pair of elements from the two node-sets) is/are used to be generated (omitting the type attribute`) in the final output.

思念满溢 2024-11-13 22:17:51

此样式表:

<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">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_address type="test">g@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>
        <address name="test1">
            <email_address>g@other</email_address>
        </address>
    </addresses>
</root>

说明:

  • 共有三组: 1) 按类型列出的所有联系信息; 2) 按类型划分的所有电话号码; 3) 按类型划分的所有电子邮件地址
  • 第一组用于获取每种类型的第一次出现
  • 然后我们遍历每个电话号码,与同一位置的任何电子邮件地址配对
  • 最后,我们计算所有电子邮件地址没有对应的电话号码

This stylesheet:

<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>

On this input:

<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_address type="test">g@other</email_address>
    </email_addresses>
</root>

Produces:

<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>
        <address name="test1">
            <email_address>g@other</email_address>
        </address>
    </addresses>
</root>

Explanation:

  • There are three groups: 1) all contact info by type; 2) all phone numbers by type; 3) all email addresses by type
  • The first group is used to get the first occurrence of each type
  • Then we go through each of the phone numbers, pairing with any email address in the same position
  • Finally, we account for all of the email addresses that did not have a corresponding phone number
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文