XSLT合并挑战

时间:2012-04-22 20:02:29

标签: xml xslt merge

首先,我吮吸XSLT。其次,我知道有几篇关于如何与XSLT进行合并的文章,但我没有找到任何关于我特殊挑战的内容。

我有2个XML文件。一个是新的客户信息,另一个是下面的当前/以前的信息。我需要生成的XML来合并所有的Customer / Addresses,并将属性(NoChange,Updated,Deleted,New)添加到最终XML的属性中:


输入1

当前客户信息。

<Customer>
 <CustId>1</CustId>
 <CustName>Acme</CustName>
 <Addresses>
  <Address>
   <AddressesId>1</AddressesId>
   <Street>123 Main</Street>
  </Address>
  <Address>
   <AddressesId>2</AddressesId>
   <Street>345 Main</Street</Street>
  </Address>
  <Address>
   <AddressesId>4</AddressesId>
   <Street>888 Goner St.</Street>
  </Address>
 </Addresses>
</Customer>

输入2

更新信息。

<Customer>
 <CustId>1</CustId>
 <CustName>Acme</CustName>
 <Addresses>
  <Address>
   <AddressesId>2</AddressesId>
   <Street>999 Updated St.</Street>
  </Address>
  <Address>
   <AddressesId>3</AddressesId>
   <Street>3999 New St.</Street>
  </Address>
 </Addresses>
</Customer>

结果

<Customer>
 <CustId>1</CustId>
 <CustName>Acme</CustName>
 <Addresses>
  <Address>
   <Address status="NoChange">
   <AddressesId>1</AddressesId>
   <Street>123 Main</Street>
  </Address>
  <Address>
   <Address status="Updated">
   <AddressesId>2</AddressesId>
   <Street>999 Updated St.</Street>
  </Address>
   <Address status="New">
   <AddressesId>3</AddressesId>
   <Street>3999 New St.</Street>
  </Address>
  <Address status="Deleted">
   <AddressesId>4</AddressesId>
   <Street>888 Goner St.</Street>
  </Address>
 </Addresses>
</Customer>

如何进行我想要的合并?

2 个答案:

答案 0 :(得分:1)

我决定尝试将其作为XSLT 3.0中新的xsl:merge指令的用例。使用当前的Saxon实现,以下结果给出了期望的结果:

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

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

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

  <xsl:variable name="original" select="doc('merge018-current.xml')"/>
  <xsl:variable name="updates" select="doc('merge018-updates.xml')"/>

  <xsl:template name="main">
    <xsl:apply-templates select="$original"/>
  </xsl:template>  

  <xsl:template match="Addresses">
    <Addresses>
      <xsl:merge>
        <xsl:merge-source for-each="$updates, $original"
                        select=".//Address">
          <xsl:merge-key select="AddressesId"/>
        </xsl:merge-source>
        <xsl:merge-action>
          <xsl:variable name="status" select="
            if (count(current-group()) = 1)
            then if (current-group()[1]/root(.) is $original) then 'Deleted' else 'New'
            else if (deep-equal(current-group()[1], current-group()[2])) then 'NoChange' else 'Updated'"/>
          <Address status="{$status}">
            <xsl:copy-of select="current-group()[1]/(AddressesId, Street)"/>
          </Address>
        </xsl:merge-action>
      </xsl:merge>
    </Addresses>
  </xsl:template>
</xsl:stylesheet>

我并不是说这是一个实用的解决方案,只是为了您的利益而提供它。如果您对此类或类似的内容作为测试用例发布有任何异议,请立即说明。

答案 1 :(得分:0)

<强>予。这个XSLT 1.0转换(相应的XSLT 2.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="kAddrById" match="Address"
  use="concat(../../CustId, '+', ../../CustName,
              '+', AddressesId)"/>

 <xsl:key name="kNodeByGenId" match="node()"
          use="generate-id()"/>

 <xsl:param name="pUpdatesPath" select=
  "'file:///c:/temp/delete/CustomersUpdates.xml'"/>

 <xsl:variable name="vUpdates" select=
   "document($pUpdatesPath)"/>

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

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

 <xsl:template match="Addresses">
  <Addresses>
   <xsl:apply-templates select="Address | $vUpdates/*/*/*">
    <xsl:sort select="AddressesId" data-type="number"/>
   </xsl:apply-templates>
  </Addresses>
 </xsl:template>

 <xsl:template match="Address">
   <xsl:variable name="vIsThisUpdate" select=
     "generate-id(/) = generate-id($vUpdates)"/>

   <xsl:variable name="vOtherDoc" select=
    "$vmainDoc[$vIsThisUpdate]
    |
     $vUpdates[not($vIsThisUpdate)]
    "/>

    <xsl:variable name="vCustId" select="../../CustId"/>
    <xsl:variable name="vCustName" select="../../CustName"/>
    <xsl:variable name="vAddrId" select="AddressesId"/>

    <xsl:variable name="vOtherNodeId">
     <xsl:for-each select="$vOtherDoc">
       <xsl:value-of select=
       "generate-id(key('kAddrById',
                        concat($vCustId,'+', $vCustName,
                               '+', $vAddrId)
                       )
                   )"/>
     </xsl:for-each>
    </xsl:variable>
    <xsl:apply-templates mode="selected"
     select="self::node()
              [$vIsThisUpdate
             or
              (not($vIsThisUpdate) and not(string($vOtherNodeId)))
              ]">
     <xsl:with-param name="pIsUpdating" select="$vIsThisUpdate"/>
     <xsl:with-param name="pOtherDoc" select="$vOtherDoc"/>
     <xsl:with-param name="pOtherNodeId"
                     select="string($vOtherNodeId)"/>
    </xsl:apply-templates>
 </xsl:template>

 <xsl:template match="Address" mode="selected">
  <xsl:param name="pIsUpdating"/>
  <xsl:param name="pOtherDoc" select="/.."/>
  <xsl:param name="pOtherNodeId"/>

  <xsl:variable name="vStatus">
   <xsl:choose>
     <xsl:when test="$pIsUpdating and not($pOtherNodeId)">New</xsl:when>
       <xsl:when test="$pIsUpdating">
         <xsl:variable name="vOldStreet">
          <xsl:for-each select="$pOtherDoc">
            <xsl:value-of select=
              "key('kNodeByGenId', $pOtherNodeId)/Street"/>
          </xsl:for-each>
         </xsl:variable>

         <xsl:choose>
           <xsl:when test=
           "Street = string($vOldStreet)">NoChange</xsl:when>
           <xsl:otherwise>Updated</xsl:otherwise>
         </xsl:choose>
       </xsl:when>
       <xsl:otherwise>Deleted</xsl:otherwise>
   </xsl:choose>
  </xsl:variable>

  <Address>
    <Address status="{$vStatus}"/>
    <xsl:apply-templates/>
  </Address>
 </xsl:template>
</xsl:stylesheet>

应用于提供的XML文档

<Customer>
    <CustId>1</CustId>
    <CustName>Acme</CustName>
    <Addresses>
        <Address>
            <AddressesId>1</AddressesId>
            <Street>123 Main</Street>
        </Address>
        <Address>
            <AddressesId>2</AddressesId>
            <Street>345 Main</Street>
        </Address>
        <Address>
            <AddressesId>4</AddressesId>
            <Street>888 Goner St.</Street>
        </Address>
    </Addresses>
</Customer>

并且在c:/temp/delete/CustomersUpdates.xml此XML文档(稍微改变一下,以使第一个地址以“NoChange”状态结束):

<Customer>
    <CustId>1</CustId>
    <CustName>Acme</CustName>
    <Addresses>
        <Address>
            <AddressesId>1</AddressesId>
            <Street>123 Main</Street>
        </Address>
        <Address>
            <AddressesId>2</AddressesId>
            <Street>999 Updated St.</Street>
        </Address>
        <Address>
            <AddressesId>3</AddressesId>
            <Street>3999 New St.</Street>
        </Address>
    </Addresses>
</Customer>

生成想要的正确结果

<Customer>
   <CustId>1</CustId>
   <CustName>Acme</CustName>
   <Addresses>
      <Address>
         <Address status="NoChange"/>
         <AddressesId>1</AddressesId>
         <Street>123 Main</Street>
      </Address>
      <Address>
         <Address status="Updated"/>
         <AddressesId>2</AddressesId>
         <Street>999 Updated St.</Street>
      </Address>
      <Address>
         <Address status="New"/>
         <AddressesId>3</AddressesId>
         <Street>3999 New St.</Street>
      </Address>
      <Address>
         <Address status="Deleted"/>
         <AddressesId>4</AddressesId>
         <Street>888 Goner St.</Street>
      </Address>
   </Addresses>
</Customer>

<强> II。 XSLT 2.0解决方案:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my" exclude-result-prefixes="my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kAddrById" match="Address"
  use="concat(../../CustId, '+', ../../CustName,
              '+', AddressesId)"/>

 <xsl:param name="pUpdatesPath" select=
  "'file:///c:/temp/delete/CustomersUpdates.xml'"/>

 <xsl:variable name="vUpdates" select=
   "document($pUpdatesPath)"/>

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

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

 <xsl:template match="Addresses">
  <Addresses>
   <xsl:apply-templates select="Address | $vUpdates/*/*/*">
    <xsl:sort select="AddressesId" data-type="number"/>
   </xsl:apply-templates>
  </Addresses>
 </xsl:template>

 <xsl:template match="Address[root() is $vUpdates]">
    <xsl:variable name="vOtherDoc" select="$vmainDoc"/>

    <xsl:variable name="vOtherNode" select="my:OtherNode(., $vOtherDoc)"/>

    <xsl:variable name="vStatus" select=
     "concat('New'[not($vOtherNode)],
             'NoChange'[$vOtherNode 
                       and current()/Street eq $vOtherNode/Street],
             'Updated'[$vOtherNode and current()/Street ne $vOtherNode/Street]
             )"/>
    <xsl:apply-templates select="self::node()" mode="selected">
     <xsl:with-param name="pStatus" select="$vStatus"/>
    </xsl:apply-templates>
 </xsl:template>

  <xsl:template match="Address[not(root() is $vUpdates)]">
   <xsl:variable name="vOtherDoc" select="$vUpdates"/>

    <xsl:variable name="vOtherNode" select="my:OtherNode(., $vOtherDoc)"/>

    <xsl:apply-templates select="self::node()[not($vOtherNode)]" mode="selected">
      <xsl:with-param name="pStatus" select="'Deleted'"/>
    </xsl:apply-templates>
  </xsl:template>

 <xsl:template match="Address" mode="selected">
  <xsl:param name="pStatus"/>

  <Address>
    <Address status="{$pStatus}"/>
    <xsl:apply-templates/>
  </Address>
 </xsl:template>

 <xsl:function name="my:OtherNode" as="element()?">
   <xsl:param name="pThis" as="element()"/>
   <xsl:param name="pOtherDoc" as="document-node()"/>

    <xsl:sequence select=
     "key('kAddrById',
          concat($pThis/../../CustId,'+', $pThis/../../CustName,
                 '+', $pThis/AddressesId
                 ),
                 $pOtherDoc
          )"/>
 </xsl:function>
</xsl:stylesheet>

对同一文档应用此转换时,会产生相同的正确结果。