如何使用匹配节点连接两个 XML 文件
Posted
技术标签:
【中文标题】如何使用匹配节点连接两个 XML 文件【英文标题】:How to join two XML files with a matching node 【发布时间】:2011-09-15 21:37:34 【问题描述】:当两个 XML 文件有一个匹配的节点时,我需要找到一种方法来连接它们。据我所知,这可以用许多不同的语言来完成......是否有 php 或 AJAX 方式来做到这一点?从 SO 上的其他帖子中,我看到了 XSLT 解决方案.. 我真的不明白。这是最好/首选的方法吗?如果有,知道任何有用的 XSLT 教程吗?
例如 XML-1 是这样的:
<FOO>
</A>
</B>
</C>
</D>
</FOO>
和 XML-2 :
<FOO>
</B>
</E>
</FOO>
检查<B>==<B>
的位置然后添加<E>
的最佳方法是什么
更新
好吧,我无法让这个与我假设的例子一起工作,我想我会更新我真正在做什么,看看是否有人可以帮助我解决这个问题。我已经尝试了下面的方法以及我在 SO 上找到的其他方法,但都没有运气。
真正的架构是这样的:
file1.xml
<?xml version="1.0"?>
<DATA>
<ITEM>
<PRODUCT_TYPE>simple</PRODUCT_TYPE>
<STYLE_COLOR>1524740007</STYLE_COLOR>
<SHORT_DESCRIPTION>Black Shoe</SHORT_DESCRIPTION>
<CLASS_NAME>FOOTWEAR</CLASS_NAME>
<STATUS>Disabled</STATUS>
</ITEM>
...
</DATA>
file2.xml
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="merge.xsl" ?>
<DATA>
<ITEM>
<STYLE_COLOR>1524740007</STYLE_COLOR>
<NEXT_ARRIVAL>2011-08-05</NEXT_ARRIVAL>
</ITEM>
....
</DATA>
我需要弄清楚的是生成一个新的 XML 文件,它将这些节点与相同的 SYTLE_COLOR 合并,看起来像:
<DATA>
<ITEM>
<PRODUCT_TYPE>simple</PRODUCT_TYPE>
<STYLE_COLOR>1524740007</STYLE_COLOR>
<SHORT_DESCRIPTION>Black Shoe</SHORT_DESCRIPTION>
<CLASS_NAME>FOOTWEAR</CLASS_NAME>
<NEXT_ARRIVAL>2011-08-05</NEXT_ARRIVAL>
<STATUS>Disabled</STATUS>
</ITEM>
我尝试创建一个如下所示的 merge.xsl:
<?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:output indent="yes"/>
<xsl:variable name="with" select="'file-2.xml'" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="scene">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
<xsl:variable name="info" select="document($with)/DATA/ITEM[STYLE_COLOR=current()/STYLE_COLOR]/." />
<xsl:for-each select="$info/*">
<xsl:if test="name()!='STYLE_COLOR'">
<xsl:copy-of select="." />
</xsl:if>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:transform>
我也尝试过这样的合并:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:variable name="input2" select="document('file-2.xml')/DATA/ITEM"/>
<xsl:template match="STYLE_COLOR">
<xsl:copy>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:choose>
<xsl:when test="$input2/*[name()=name(current())]">
<xsl:copy-of select="$input2/*"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
这些方法都不起作用.. 抱歉 XSLT 对我来说很新,所以我不确定我在做什么,真的很感激有人握着这个方法。
【问题讨论】:
XSLT 是一种方法。 “这是最好/首选的方法吗?”这取决于您的经验和您的要求。你的xml真的那么简单吗?? 标签的顺序重要吗?比如D
一定要放在最后?
请同时指出您可以考虑的 XSLT 版本。
标签的顺序并不重要..我想我必须使用1.0
好问题,+1。请参阅我的答案以获取一个非常简单的解决方案,该解决方案也是参数化且灵活的。该解决方案使用 XSLT document()
函数,也是基于最基础和最强大的 XSLT 设计模式。答案末尾有详细说明。
【参考方案1】:
这是为适应新要求而对原始变换进行了轻微修改。通过检查 file2.xml 元素来执行合并。对于 file1 中的当前项目,file2 中的子项目将被合并仅如果 file1 中不存在.
[XSLT 1.0]
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="input2" select="document('test_input2.xml')/DATA"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM">
<xsl:variable name="item" select="
$input2/ITEM[STYLE_COLOR=current()/STYLE_COLOR]"/>
<xsl:variable name="ITEM" select="."/>
<xsl:if test="$item">
<xsl:copy>
<xsl:for-each select="$item/*">
<xsl:if test="count($ITEM/*[name()=name(current())])=0">
<xsl:copy-of select="." />
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
应用于此input1.xml
:
<DATA>
<ITEM>
<PRODUCT_TYPE>simple</PRODUCT_TYPE>
<STYLE_COLOR>1524740007</STYLE_COLOR>
<SHORT_DESCRIPTION>Black Shoe</SHORT_DESCRIPTION>
<CLASS_NAME>FOOTWEAR</CLASS_NAME>
<STATUS>Disabled</STATUS>
</ITEM>
<ITEM>
<PRODUCT_TYPE>simple</PRODUCT_TYPE>
<STYLE_COLOR>1524740008</STYLE_COLOR>
<SHORT_DESCRIPTION>Black Shoe</SHORT_DESCRIPTION>
<CLASS_NAME>FOOTWEAR</CLASS_NAME>
<STATUS>Disabled</STATUS>
</ITEM>
<ITEM>
<PRODUCT_TYPE>simple</PRODUCT_TYPE>
<STYLE_COLOR>777</STYLE_COLOR>
<SHORT_DESCRIPTION>Black Shoe</SHORT_DESCRIPTION>
<CLASS_NAME>FOOTWEAR</CLASS_NAME>
<STATUS>Disabled</STATUS>
</ITEM>
</DATA>
和input2.xml
合并,产生:
<DATA>
<ITEM>
<STYLE_COLOR>1524740007</STYLE_COLOR>
<NEXT_ARRIVAL>2011-08-05</NEXT_ARRIVAL>
<CLASS_NAME>XXX</CLASS_NAME>
<OTHER>YYY</OTHER>
</ITEM>
<ITEM>
<STYLE_COLOR>1524740008</STYLE_COLOR>
<NEXT_ARRIVAL>2011-08-05</NEXT_ARRIVAL>
</ITEM>
</DATA>
产生:
<DATA>
<ITEM>
<NEXT_ARRIVAL>2011-08-05</NEXT_ARRIVAL>
<OTHER>YYY</OTHER>
<PRODUCT_TYPE>simple</PRODUCT_TYPE>
<STYLE_COLOR>1524740007</STYLE_COLOR>
<SHORT_DESCRIPTION>Black Shoe</SHORT_DESCRIPTION>
<CLASS_NAME>FOOTWEAR</CLASS_NAME>
<STATUS>Disabled</STATUS>
</ITEM>
<ITEM>
<NEXT_ARRIVAL>2011-08-05</NEXT_ARRIVAL>
<PRODUCT_TYPE>simple</PRODUCT_TYPE>
<STYLE_COLOR>1524740008</STYLE_COLOR>
<SHORT_DESCRIPTION>Black Shoe</SHORT_DESCRIPTION>
<CLASS_NAME>FOOTWEAR</CLASS_NAME>
<STATUS>Disabled</STATUS>
</ITEM>
</DATA>
注意:
变换不会覆盖给定项目的现有元素,只复制缺失的元素input1.xml
中的 ITEM 仅在 input2.xml
中有匹配项时才会复制到输出中
【讨论】:
感谢您的回答.. 请原谅我的回复时间,因为我要出去度过急需的假期,而且我还需要一点时间才能开始工作。 我在这方面工作了一段时间,但它并没有发生在我身上。我更新了我的问题,使它成为一个更“真实”的例子 @Sushi,最初的例子太简单了。我已经根据更新的问题更新了我的答案以满足新的要求。看看它是否是您正在搜索的内容。 非常感谢您的帮助 empo 我给了你赏金并接受,但请你再回答一个问题。如果我希望结果只是文件 1 和文件 2 中的匹配记录(类似于 SQL 中的内部连接)需要在 XSLT 中更改什么?【参考方案2】:XSLT 很强大,但是我必须承认我对它不是很熟练,所以给你一个手动转换的建议:
print "<FOOCONTAINER>\n";
readfile($xml1file);
readfile($xml2file);
print "</FOOCONTAINER>\n";
您可以在进一步处理中轻松完成的任何其他事情,因为这是 XML。
编辑:这仅适用于 OP 提供的第一个 XML。
【讨论】:
【参考方案3】:此转换(c:/temp/file1.xml
和 c:/temp/file2.xml
已在问题中提供):
<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:param name="pDoc1" select="'file:///c:/temp/file1.xml'"/>
<xsl:variable name="vDoc1" select="document($pDoc1)"/>
<xsl:param name="pDoc2" select="'file:///c:/temp/file2.xml'"/>
<xsl:variable name="vDoc2" select="document($pDoc2)"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="$vDoc1/node()"/>
</xsl:template>
<xsl:template match="ITEM">
<ITEM>
<xsl:apply-templates/>
<xsl:apply-templates select=
"$vDoc2/*/ITEM
[STYLE_COLOR = current()/STYLE_COLOR]
/node()[not(self::STYLE_COLOR)]
"/>
</ITEM>
</xsl:template>
</xsl:stylesheet>
当应用于任何 XML 文档(未使用/忽略)时,会产生所需的正确结果:
<DATA>
<ITEM xmlns:xs="http://www.w3.org/2001/XMLSchema">
<PRODUCT_TYPE>simple</PRODUCT_TYPE>
<STYLE_COLOR>1524740007</STYLE_COLOR>
<SHORT_DESCRIPTION>Black Shoe</SHORT_DESCRIPTION>
<CLASS_NAME>FOOTWEAR</CLASS_NAME>
<STATUS>Disabled</STATUS>
<NEXT_ARRIVAL>2011-08-05</NEXT_ARRIVAL>
</ITEM> ...
</DATA>
解释:
首先我们处理(应用模板)第一个 XML 文档的节点。
身份规则/模板按原样复制每个节点。
有一个模板可以覆盖身份规则。此模板匹配任何名为 ITEM
的元素。它创建一个也名为ITEM
的元素,然后处理所有子节点,这会导致标识模板复制它们。最后,作为第二个 XML 文档中任何 ITEM
元素的子节点的所有节点,其 STYLE_COLOR
子节点的字符串值与当前(匹配)元素的 STYLE_COLOR
子节点的字符串值相同(通过将模板应用到它们并作为标识模板的选择和执行的结果),STYLE_COLOR
子本身除外。
请注意,两个 pat 的文件路径作为参数传递给转换,这使得它更加灵活,能够处理任何两个 XML 文件——无需任何修改。我们使用 XSLT document()
函数来加载和解析这两个文档,以便它们可以被 xslt 转换处理。
请注意,在此转换中没有使用 xsl:for-each
或 xsl:if
或任何其他 XSLT 条件指令。这使得代码更简单、更易于维护和理解。
最后,请注意身份规则/模板的使用和覆盖。这是最基本和最强大的 XSLT 设计模式。
【讨论】:
很好的解决方案。请注意,通过这种转换,合并后的文件还将添加到源文件中已经存在的元素。 @empo:我知道。 OP 没有指定任何替换或非替换策略,并且该解决方案使用他的数据产生了预期的结果。我不想猜测他认为哪种替代政策是正确的——这样做有 50% 以上的错误机会。如果 OP 指定了他的替换要求,我将修改解决方案以准确反映它们。 谢谢迪米特!我仍然遇到在该过程之后所有节点都被删除的问题。您是否还建议我为此使用撒克逊语?如果可能的话,我希望避免这种情况.. @Sushi K:您需要使用 XSLT 处理器——无论是 Saxon 还是其他处理器。在 XSLT 开发过程中使用浏览器作为 XSLT 处理器是绝对不严肃的。如果这是您的主要问题,我建议您将问题重新标记为“xsltprocessors”。从 XSLT 的角度来看,您得到了两个正确的解决方案。无法使用它们不是 XSLT 问题。 知道了,感谢您的所有帮助,很抱歉很愚蠢。这对我来说很新鲜(显然)。以上是关于如何使用匹配节点连接两个 XML 文件的主要内容,如果未能解决你的问题,请参考以下文章
如何使用多个 XPath 查询在 c# 中选择单个 XML 节点