如何使用匹配节点连接两个 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>

检查&lt;B&gt;==&lt;B&gt; 的位置然后添加&lt;E&gt; 的最佳方法是什么

更新

好吧,我无法让这个与我假设的例子一起工作,我想我会更新我真正在做什么,看看是否有人可以帮助我解决这个问题。我已经尝试了下面的方法以及我在 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.xmlc:/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-eachxsl:if 或任何其他 XSLT 条件指令。这使得代码更简单、更易于维护和理解。

    最后,请注意身份规则/模板的使用和覆盖。这是最基本和最强大的 XSLT 设计模式

【讨论】:

很好的解决方案。请注意,通过这种转换,合并后的文件还将添加到源文件中已经存在的元素。 @empo:我知道。 OP 没有指定任何替换或非替换策略,并且该解决方案使用他的数据产生了预期的结果。我不想猜测他认为哪种替代政策是正确的——这样做有 50% 以上的错误机会。如果 OP 指定了他的替换要求,我将修改解决方案以准确反映它们。 谢谢迪米特!我仍然遇到在该过程之后所有节点都被删除的问题。您是否还建议我为此使用撒克逊语?如果可能的话,我希望避免这种情况.. @Sushi K:您需要使用 XSLT 处理器——无论是 Saxon 还是其他处理器。在 XSLT 开发过程中使用浏览器作为 XSLT 处理器是绝对不严肃的。如果这是您的主要问题,我建议您将问题重新标记为“xsltprocessors”。从 XSLT 的角度来看,您得到了两个正确的解决方案。无法使用它们不是 XSLT 问题。 知道了,感谢您的所有帮助,很抱歉很愚蠢。这对我来说很新鲜(显然)。

以上是关于如何使用匹配节点连接两个 XML 文件的主要内容,如果未能解决你的问题,请参考以下文章

XPath如何定位dom节点

如何匹配文本节点然后使用 XPath 跟随父节点

如何使用多个 XPath 查询在 c# 中选择单个 XML 节点

使用 XSLT 基于 ID 从多个 xPath 中选择 XML 节点

如何比较两个 XML 节点结构 Delphi

如何使用 C# 搜索连续 XML 节点的值?