首先,我吮吸XSLT。其次,我知道有几篇关于如何与XSLT进行合并的文章,但我没有找到任何关于我特殊挑战的内容。
我有2个XML文件。一个是新的客户信息,另一个是下面的当前/以前的信息。我需要生成的XML来合并所有的Customer / Addresses,并将属性(NoChange,Updated,Deleted,New)添加到最终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</Street>
</Address>
<Address>
<AddressesId>4</AddressesId>
<Street>888 Goner St.</Street>
</Address>
</Addresses>
</Customer>
更新信息。
<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>
如何进行我想要的合并?
答案 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>
对同一文档应用此转换时,会产生相同的正确结果。