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