使用键值上的XSLT匹配合并两个XML流

时间:2010-09-03 15:22:36

标签: xslt

我想知道在最终再次转换之前如何合并2个XML流。

这两个流是

输入1

<Response>
    <Instrument>
        <Date value="2010-09-02">
            <Quantity>10</Quantity>
        </Date>
        <DXLID>1</DXLID>
    </Instrument>
    <Instrument TICKER="APPL" />
    <SF></SF>
    <Instrument>
        <Date value="2010-09-02">
            <Quantity>20</Quantity>
        </Date>
        <DXLID>2</DXLID>
    </Instrument>
    <Instrument TICKER="APPL" />
    <SF></SF>
</Response>

输入2

<Response>
    <IM>
        <Instrument>
            <Date value="2010-09-02">
                <SAF>1</SAF>
                <SAR>2</SAR>
            </Date>
            <DXLID>1</DXLID>
        </Instrument>
        <Instrument>
            <Date value="2010-09-02">
                <SAF>1</SAF>
                <SAR>2</SAR>
            </Date>
            <DXLID>3</DXLID>
        </Instrument>
    </IM>
</Response>

所需输出

<Response>
    <All>
        <Instrument>
            <Date value="2010-09-02">
                <SAF>1</SAF>
                <SAR>2</SAR>
                <Quantity>10</Quantity>
            </Date>
            <DXLID>1</DXLID>
        </Instrument>
        <Instrument>
            <Date value="2010-09-02">
                <Quantity>20</Quantity>
            </Date>
            <DXLID>2</DXLID>
        </Instrument>
        <Instrument>
            <Date value="2010-09-02">
                <SAF>1</SAF>
                <SAR>2</SAR>
            </Date>
            <DXLID>3</DXLID>
        </Instrument>
    </All>
</Response>

合并需要基于DXLID节点值与value节点的Date属性之间的匹配。

另请注意,合并需要双向合并。

2 个答案:

答案 0 :(得分:2)

此样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="kInstrumentByDateAndDXLID" match="Instrument"
             use="concat(Date/@value,'++',DXLID)"/>
    <xsl:variable name="vSource1"
               select="document('Doc1.xml')/Response/Instrument[Date][DXLID]"/>
    <xsl:variable name="vSource2"
               select="document('Doc2.xml')/Response/IM/Instrument"/>
    <xsl:template match="node()|@*" name="identity">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/">
        <Response>
            <All>
                <xsl:apply-templates select="$vSource1|$vSource2">
                    <xsl:sort select="DXLID"/>
                </xsl:apply-templates>
            </All>
        </Response>
    </xsl:template>
    <xsl:template match="Date/*[last()]">
        <xsl:call-template name="identity"/>
        <xsl:if test="count(../..|$vSource1)=count($vSource1)">
            <xsl:variable name="vKey"
                          select="concat(../@value,'++',../../DXLID)"/>
            <xsl:for-each select="$vSource2[last()]">
                <xsl:apply-templates
                 select="key('kInstrumentByDateAndDXLID',$vKey)/Date/*"/>
            </xsl:for-each>
        </xsl:if>
    </xsl:template>
    <xsl:template match="Instrument">
        <xsl:if test="count(.|$vSource1)=count($vSource1) or
                      not($vSource1[key('kInstrumentByDateAndDXLID',
                                        concat(current()/Date/@value,'++',
                                               current()/DXLID))])">
            <xsl:call-template name="identity"/>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

输出:

<Response>
    <All>
        <Instrument>
            <Date value="2010-09-02">
                <Quantity>10</Quantity>
                <SAF>1</SAF>
                <SAR>2</SAR>
            </Date>
            <DXLID>1</DXLID>
        </Instrument>
        <Instrument>
            <Date value="2010-09-02">
                <Quantity>20</Quantity>
            </Date>
            <DXLID>2</DXLID>
        </Instrument>
        <Instrument>
            <Date value="2010-09-02">
                <SAF>1</SAF>
                <SAR>2</SAR>
            </Date>
            <DXLID>3</DXLID>
        </Instrument>
    </All>
</Response>

注意:使用apply-templates可以立即运行合并和第二步转换。此外,将fn:document用于多个输入源,并将XPath测试用于包含:count($node|$node-set)=count($node-set)

修改:与键相同。看起来MSXSL4有一个错误,这就是为什么我正在使用$vSource2[last()]代替$vSource2[1]

答案 1 :(得分:0)

此转换(包括整个第二个文档 - 仅为方便起见):

<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="kDateByValAndId" match="Date"
  use="concat(@value, '+', ../DXLID)"/>

 <xsl:variable name="vDoc1" select="/"/>

 <xsl:variable name="vrtfDoc2">
    <Response>
        <IM>
            <Instrument>
                <Date value="2010-09-02">
                    <SAF>1</SAF>
                    <SAR>2</SAR>
                </Date>
                <DXLID>1</DXLID>
            </Instrument>
            <Instrument>
                <Date value="2010-09-02">
                    <SAF>1</SAF>
                    <SAR>2</SAR>
                </Date>
                <DXLID>3</DXLID>
            </Instrument>
        </IM>
    </Response>
 </xsl:variable>

 <xsl:variable name="vDoc2" select=
  "document('')/*/xsl:variable[@name='vrtfDoc2']"/>

 <xsl:template match="/">
  <Response>
   <All>
    <xsl:apply-templates select="/*/node()"/>
    <xsl:apply-templates mode="doc2" select=
     "$vDoc2/*/*/Instrument[Date and DXLID]" />
   </All>
  </Response>
 </xsl:template>

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

 <xsl:template match="Date">
  <xsl:variable name="vkeyVal" select=
  "concat(@value, '+', ../DXLID)"/>

  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
   <xsl:for-each select="$vDoc2">
    <xsl:apply-templates select="key('kDateByValAndId', $vkeyVal)/node()"/>
   </xsl:for-each>
  </xsl:copy>
 </xsl:template>
 <xsl:template match="SF|Instrument[@TICKER='APPL']"/>

 <xsl:template match="Instrument" mode="doc2">
  <xsl:variable name="vkeyVal" select=
  "concat(Date/@value, '+', DXLID)"/>

  <xsl:variable name="vcur" select="."/>

  <xsl:for-each select="$vDoc1">
   <xsl:if test="not(key('kDateByValAndId', $vkeyVal))">
    <xsl:copy-of select="$vcur"/>
   </xsl:if>
  </xsl:for-each>

 </xsl:template>
</xsl:stylesheet>

应用于原始第一份文件

<Response>
    <Instrument>
        <Date value="2010-09-02">
            <Quantity>10</Quantity>
        </Date>
        <DXLID>1</DXLID>
    </Instrument>
    <Instrument TICKER="APPL" />
    <SF></SF>
    <Instrument>
        <Date value="2010-09-02">
            <Quantity>20</Quantity>
        </Date>
        <DXLID>2</DXLID>
    </Instrument>
    <Instrument TICKER="APPL" />
    <SF></SF>
</Response>

生成想要的正确结果

<Response>
   <All>
      <Instrument>
         <Date value="2010-09-02">
            <Quantity>10</Quantity>
            <SAF xmlns:xsl="http://www.w3.org/1999/XSL/Transform">1</SAF>
            <SAR xmlns:xsl="http://www.w3.org/1999/XSL/Transform">2</SAR>
         </Date>
         <DXLID>1</DXLID>
      </Instrument>
      <Instrument>
         <Date value="2010-09-02">
            <Quantity>20</Quantity>
         </Date>
         <DXLID>2</DXLID>
      </Instrument>
      <Instrument xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
         <Date value="2010-09-02">
            <SAF>1</SAF>
            <SAR>2</SAR>
         </Date>
         <DXLID>3</DXLID>
      </Instrument>
   </All>
</Response>

请注意:

  1. 如果第二个文档位于自己的文件中,则不会显示命名空间节点 - 为方便起见,它包含在当前转换中。

  2. 首先处理第一个文档的所有节点(如果其中一些节点在第二个文档中具有匹配的节点,则执行合并)。

  3. 最后,第二个文档中没有第一个文档中相应<Instrument>个节点的所有<Instrument>个节点都会被复制到输出中。

  4. 使用密钥识别两个文档中的匹配Date个节点。