XSLT 将 XML 两部分之间的数据有条件地合并到第二部分

XSLT merging data between two parts of XML conditionally into the second part

对于我在不眠之夜中苦苦挣扎的以下问题,我将不胜感激(XSLT 代码)。

消息的两部分(Message1 和 Message2)都包含各种 ID 的人工数据。 Message1 可能包含相同 id 的相同 human id 的数据和附加段 <action>(值 'New' 或 'Old')。对于 Message2 中的所有人类 ID,如果 'same' 块与 action='New' 一起存在,则需要检查 Message1。如果是,则需要将相关节点的 <action>New</action> 复制到 Message2 相关节点中(或者 Message1 的整个相关节点需要在结构的正确位置替换 Message2 中的 'same' 节点) .期望的结果是基于这些条件合并到 Message2 中,如下所示。仅用于动作 New 且仅需要合并相同的块。

源测试用例是这样的:

    <Messages>
       <Message1>
          <Response>
             <CE>
                <id>1</id>
                <human>
                   <name>Frank</name>
                   <human_information>
                      <action>New</action>
                      <title>Doctor</title>
                   </human_information>
                   <phone>
                      <action>Old</action>
                      <phone_number>1234567</phone_number>
                   </phone>
                </human>
             </CE>
          </Response>
          <Response>      
             <CE>
                <id>2</id>
                <human>
                   <name>Bob</name>
                   <human_information>
                      <action>New</action>
                      <title>Artist</title>
                   </human_information>
                   <phone>
                      <action>Old</action>
                      <phone_number>13579</phone_number>
                   </phone>
                </human>
             </CE>
          </Response>
          <Response>      
             <CE>
                <id>3</id>
                <human>
                   <name>Alice</name>
                   <human_information>
                      <action>Old</action>
                      <title>Designer</title>
                   </human_information>
                   <phone>
                      <action>New</action>
                      <phone_number>9876543</phone_number>
                   </phone>
                </human>
             </CE>
          </Response>            
       </Message1>
       <Message2>
          <Response>      
             <CE>
                <id>2</id>
                <human>
                   <name>Bob</name>
                   <human_information>
                      <title>Artist</title>
                   </human_information>
                   <phone>
                      <phone_number>13579</phone_number>
                   </phone>
                   <phone>
                      <phone_number>24680</phone_number>
                   </phone>               
                </human>
             </CE>
          </Response>
          <Response>      
             <CE>
                <id>3</id>
                <human>
                   <name>Alice</name>
                   <human_information>
                      <title>Designer</title>
                   </human_information>
                   <phone>
                      <phone_number>9876543</phone_number>
                   </phone>
                   <phone>
                      <phone_number>0909090</phone_number>
                   </phone>               
                </human>
             </CE>
          </Response>
        </Message2>  
    </Messages>

想要的测试结果应该是这样的:

      <Response>      
         <CE>
            <id>2</id>
            <human>
               <name>Bob</name>
               <human_information>
                 <action>New</action>               
                  <title>Artist</title>
               </human_information>
               <phone>
                  <phone_number>13579</phone_number>
               </phone>
               <phone>
                  <phone_number>24680</phone_number>
               </phone>               
            </human>
         </CE>
         <CE>
            <id>3</id>
            <human>
               <name>Alice</name>
               <human_information>
                  <action>New</action>               
                  <title>Designer</title>
               </human_information>
               <phone>
                  <action>New</action>               
                  <phone_number>9876543</phone_number>
               </phone>
               <phone>
                  <phone_number>0909090</phone_number>
               </phone>               
            </human>
         </CE>
      </Response>

这是使用 Altova 2018 的 XSLT 3 尝试:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs"
    version="3.0">

    <xsl:mode on-no-match="shallow-copy"/>

    <xsl:key name="map" match="Message1/Response/CE//*[action = 'New']" composite="yes">
        <xsl:sequence select="ancestor::CE/id"/>
        <xsl:sequence select="node-name()"/>
        <xsl:variable name="pos" as="xs:integer">
            <xsl:number/>
        </xsl:variable>
        <xsl:sequence select="$pos"/>
    </xsl:key>

    <xsl:template match="Messages">
        <xsl:apply-templates select="Message2"/>
    </xsl:template>

    <xsl:template match="Message2">
        <Response>
            <xsl:apply-templates select="Response/*"/>
        </Response>
    </xsl:template>

    <xsl:template match="Message2/Response/CE//*[key('map', (ancestor::CE/id, node-name(), count((., preceding-sibling::*[node-name() = node-name(current())]))))]">
        <xsl:copy-of select="key('map', (ancestor::CE/id, node-name(), count((., preceding-sibling::*[node-name() = node-name(current())]))))"/>
    </xsl:template>

</xsl:stylesheet>

不幸的是,Saxon 9.8.0.14 给了我关于密钥 "A sequence of more than one item is not allowed as the @use attribute of xsl:key" 的警告,然后也反对将密钥与 "Key definition is circular" 一起使用,这是我在 Saxon 支持论坛上提出的问题分别导致错误报告 https://saxonica.plan.io/issues/3861

由于 Saxon 的问题似乎是由在 xsl:key 声明中使用序列构造函数引起的,我重写了使用 use 表达式的方法,排除了 xsl:number 转换为函数:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="xs map mf"
    version="3.0">

    <xsl:strip-space elements="*"/>
    <xsl:output indent="yes"/>

    <xsl:mode on-no-match="shallow-copy"/>

    <xsl:function name="mf:number" as="xs:integer">
        <xsl:param name="element" as="element()"/>
        <xsl:for-each select="$element">
            <xsl:number/>
        </xsl:for-each>
    </xsl:function>

    <xsl:key name="map" match="Message1/Response/CE//*[action = 'New']" composite="yes" use="ancestor::CE/id, node-name(), mf:number(.)"/>

    <xsl:template match="Messages">
        <xsl:apply-templates select="Message2"/>
    </xsl:template>

    <xsl:template match="Message2">
        <Response>
            <xsl:apply-templates select="Response/*"/>
        </Response>
    </xsl:template>

    <xsl:template match="Message2/Response/CE//*[key('map', (ancestor::CE/id, node-name(), mf:number(.)))]">
        <xsl:copy-of select="key('map', (ancestor::CE/id, node-name(), mf:number(.)))"/>
    </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/nc4NzQx/5

尝试使用 XSLT 2 解决该问题会导致长键表达式

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

  <xsl:key name="map" 
    match="Message1/Response/CE//*[action = 'New']"
    use="concat(ancestor::CE/id, '|', node-name(.), '|', count((., preceding-sibling::*[node-name(.) = node-name(current())])))"/>

    <xsl:template match="Messages">
        <xsl:apply-templates select="Message2"/>
    </xsl:template>

    <xsl:template match="Message2">
        <Response>
            <xsl:apply-templates select="Response/*"/>
        </Response>
    </xsl:template>

  <xsl:template match="Message2/Response/CE//*[key('map', concat(ancestor::CE/id, '|', node-name(.), '|', count((., preceding-sibling::*[node-name(.) = node-name(current())]))))]">
      <xsl:copy-of select="key('map', concat(ancestor::CE/id, '|', node-name(.), '|', count((., preceding-sibling::*[node-name(.) = node-name(current())]))))"/>
  </xsl:template>

</xsl:transform>

但希望能解决 http://xsltransform.hikmatu.com/pPgCcoA 中的问题,即使是旧版本的 Saxon。