如何使用XSLT比较和合并两个XML文件

5
我希望能够比较两个 XML 并将它们合并。例如:

myFile1.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<catalog>
<data>
    <title>Title1</title>
    <description>Description1</description>
    <myid>1</myid>
</data>
<data>
    <title>Title2</title>
    <description>Description2</description>
    <myid>2</myid>
</data>
</catalog>

myFile2.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<catalog>
<data>
    <title>Title1</title>
    <description>Description1</description>
    <author>Author1</author>
    <date>12/34/5678</date>
    <myid>1</myid>
</data>
<data>
    <author>Author2</author>
    <date>87/65/4321</date>
    <myid>2</myid>
</data>
</catalog>

期望的输出:

<?xml version="1.0" encoding="ISO-8859-1"?>
<catalog>
<data>
    <title>Title1</title>
    <description>Description1</description>
    <myid>1</myid>
    <author>Author1</author>
    <date>12/34/5678</date>
</data>
<data>
    <title>Title2</title>
    <description>Description2</description>
    <myid>2</myid>
    <author>Author2</author>
    <date>87/65/4321</date>
</data>
</catalog>

我有一段代码,但它的输出与要求的不符。
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="ISO-8859-1" indent="yes"/>
<xsl:variable name="compare" select="'myFile1.xml'"/>
<xsl:variable name="with" select="'myFile2.xml'"/>
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>
<xsl:template match="*">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
        <xsl:variable name="info1" select="document($compare)/catalog/data[myid=current()/myid]/."/>
        <xsl:variable name="info2" select="document($with)/catalog/data[myid=current()/myid]/."/>
        <xsl:for-each select="$info1/*">
            <xsl:variable name="check1" select="name(current())"/>
            <!--xsl:text>Current node1 : </xsl:text><xsl:value-of select="$check1"/-->
            <xsl:for-each select="$info2/*">
                <xsl:variable name="check2" select="name(current())"/>
                <!--xsl:text>Current node2 : </xsl:text><xsl:value-of select="$check2"/-->
                <xsl:if test="$check1!=$check2">
                    <xsl:copy-of select="."/>
                </xsl:if>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>
</xsl:transform>

请帮忙!

你如何在两个文档之间匹配节点?仅通过它们的位置吗?我的意思是,doc1上的第一个节点必须与doc2上的第一个节点合并吗?还是通过“myid”来匹配? - Emiliano Poggi
不是根据位置匹配,而是与 myid 匹配。如果只更改 <data> 字段的顺序,则会相应地复制第二个文档中的字段。 - Arnab
我明白了,我正在尝试与你不同的方法。也许你会看到我的答案。 - Emiliano Poggi
当然可以。实际上我对XSLT还很陌生,所以我想不到其他的方法。任何可行的方案都将不胜感激。 - Arnab
现在看一下我的答案。我已经进行了一些测试,似乎可以工作。 - Emiliano Poggi
结果树上节点的顺序是否重要? - Emiliano Poggi
1个回答

1

这个解决方案完全没有循环或键。我只使用了一个文档来加载document(),而将另一个作为源。简而言之,在源文档中缺少的元素会在加载的文档中被取代。您拥有的元素越多,此解决方案的可用性就越低。请参见底部以获取更通用的解决方案。


XSLT 1.0Saxon-HE 9.2.1.1J 上进行了测试。

<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:variable name="catalog2" select="document('source_test2.xml')/catalog"/>

    <xsl:template match="catalog">
        <catalog>
            <xsl:apply-templates select="data"/>
        </catalog>
    </xsl:template>

    <xsl:template match="data">
        <xsl:variable name="data2" select="$catalog2/data[myid=current()/myid]/."/>
        <data>
            <xsl:choose>
                <xsl:when test="title">
                    <xsl:copy-of select="title"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="$data2/title"/>
                </xsl:otherwise>
            </xsl:choose>

            <xsl:choose>
                <xsl:when test="description">
                    <xsl:copy-of select="description"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="$data2/description"/>
                </xsl:otherwise>
            </xsl:choose>

            <xsl:copy-of select="myid"/>

            <xsl:choose>
                <xsl:when test="author">
                    <xsl:copy-of select="author"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="$data2/author"/>
                </xsl:otherwise>
            </xsl:choose>

            <xsl:choose>
                <xsl:when test="date">
                    <xsl:copy-of select="date"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="$data2/date"/>
                </xsl:otherwise>
            </xsl:choose>

        </data>
    </xsl:template>

</xsl:stylesheet>

这里提供一个更通用的解决方案。方法是相同的。对于每个data,在myFile2中存在但在myFile1中缺失的元素将被添加到结果树中,反之亦然。

XSLT 1.0Saxon-B 9.0.0.4J 上测试通过。

<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:variable name="catalog2" select="document('myFile2.xml')/catalog"/>

    <xsl:template match="catalog">
        <catalog>
            <xsl:apply-templates select="data"/>
        </catalog>
    </xsl:template>

    <xsl:template match="data">
        <xsl:variable name="data1" select="."/>
        <xsl:variable name="data2" select="$catalog2/data[myid=current()/myid]/."/>
        <data>
            <xsl:copy-of select="$data1/*"/>
            <xsl:for-each select="$data2/*">
                <xsl:variable name="element2" select="name(.)"/>
                <xsl:if test="count($data1/*[name()=$element2])=0">
                    <xsl:copy-of select="."/>
                </xsl:if>
            </xsl:for-each>
        </data>
    </xsl:template>

</xsl:stylesheet>

谢谢您的快速回复,但这将是特定于模式的,对吗?如果我有一个超过1000个元素的大模式,那么这就不是可行的方法了。如果我错了,请纠正我。 - Arnab
是的,元素越多,这种方法就越不可用。但我无法想出另一种方法。也许在接下来的几天里会有新的想法。如果您找到了解决方案,请在此处发布答案。 - Emiliano Poggi
我已经添加了一个更通用的解决方案。请注意,在某些情况下,元素的顺序可能会受到影响。由于源模式未知,这很难修复,因为您的假设是不确定的。希望这样能帮到你。 - Emiliano Poggi
我猜这可能会起作用。实际上,源模式相当庞大。我将不得不进行一些修改,但这可能有效。非常感谢。一旦我获得结果,我会让您知道。 - Arnab
是的,这个方法到目前为止都很好用。非常感谢你提供的解决方案,如果我有任何进一步的问题,我会告诉你的。再次感谢。 - Arnab

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接