1

まず、私は XSLT が苦手です。次に、XSLT を使用してマージを実行する方法に関する記事がいくつかあることは知っていますが、特定の課題については何も見つかりませんでした。

2 つの XML ファイルがあります。1 つは新しいお客様情報で、もう 1 つは以下の現在/以前の情報です。すべての Customer/Addresses をマージし、属性 (NoChange、Updated、Deleted、New) を最終的な XML の属性に追加するには、結果の 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>

必要なマージを行うにはどうすればよいですか?

4

2 に答える 2

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>

これを実用的な解決策として提案しているわけではなく、単にあなたの興味のために提供しています。これまたは類似のものをテスト ケースとして公開することに異議がある場合は、今すぐその旨を述べてください。

于 2012-04-23T09:56:48.760 に答える
0

I. この 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>

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

この変換を同じドキュメントに適用すると、同じ正しい結果が生成されます。

于 2012-04-22T22:00:30.197 に答える