XSLT来合并2 XML文件(XSLT to Merge 2 XML Files)

2019-07-30 15:40发布

我知道很少有XML / XSLT合并相关的问题在这里但是,没有似乎解决我的问题。

我寻找的是XSLT(尽可能的通用 - 与输入XML文件的结构不紧),它可以

合并A.XML与B.XML和产生c.xml这样的方式

  • c.xml将包含A.XML和B.XML之间的公共节点(与来自A.XML截取的节点值)
  • 此外c.xml将包含在A.XML节点(以及值),其存在于B.XML和不

例如:合并A.XML:

<root_node>
  <settings>
    <setting1>a1</setting1>
    <setting2>a2</setting2>
    <setting3>
      <setting31>a3</setting31>
    </setting3>
    <setting4>a4</setting4>
  </settings>
</root_node>

B.XML:

<root_node>
  <settings>
    <setting1>b1</setting1>
    <setting2>b2</setting2>
    <setting3>
      <setting31>b3</setting31>
    </setting3>
    <setting5 id="77">b5</setting5>
  </settings>
</root_node>

会产生c.xml:

<root_node>
  <settings>
  <setting1>a1</setting1>
  <setting2>a2</setting2>
  <setting3>
    <setting31>a3</setting31>
  </setting3>
  <setting5 id="77">b5</setting5>
</settings>

附加信息

我会尽量解释什么,我由一个“公共节点”明白了。 因为我没有在任何一个专家这可能不是一个准确的XML / XSLT定义。

一个 / root_node /设置/ 设置1是一个“公共节点”用b / root_node /设置/ 设置1由于2个节点使用相同的路径到达。 同样对于设置2和setting3。

在2“非公共节点”是一个 / root_node /设置/ setting4其仅在A.XML实测值(它应该在输出不来)和b / root_node /设置/ setting5其仅在B.XML实测值(它应该到输出)。

所谓“通用的解决方案:”我不意味着什么,将工作的任何格式输入个XML都会有。 我的意思是,该XSLT不应该包含硬编码的XPath,而你可能会添加类似的限制或任何其他限制你可能会认为这将是合适的“如果在A.XML节点是唯一的,这只会工作”。

Answer 1:

下面XSLT 1.0程序你想要做什么。

其应用到b.xml并传递路径a.xml作为参数。

下面是它的工作原理。

  1. 它穿越B ,如包含您要保留的新节点,以及之间的共同之处 AB
    1. 我定义“共同元件”具有相同的简单路径上的任何元素。
    2. 我定义“简单的路径”为祖先元素和元素本身的名称的斜杠分隔列表,即在ancestor-or-self轴。
      因此,在你的样品B<setting31>将有一个简单的路径 root_node/settings/setting3/setting31/
    3. 请注意,这条道路是不明确的。 言下之意是,你不能有相同名称的任何两个元素共享相同的父在你的输入。 根据您的样品我相信,这将不会是这样。
  2. 对于每个叶文本节点 (没有进一步的子元素中的一个元素的任何文本节点)
    1. 简单的路径计算与一个名为模板calculatePath
    2. 递归模板nodeValueByPath被称为是尝试从其它文件检索对应的简单路径的文本值。
    3. 如果发现相应的文本节点,则使用它的值。 这满足你的第一点。
    4. 如果没有找到相应的节点,它使用的值在手边,即,从值B 。 这满足你的第二个圆点。

其结果是,新的文件相匹配B的结构,并且包含:

  • 从所有文本节点值B具有在没有相应的节点A
  • 从文本节点值A当在相应的节点B存在。

这里的XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes" />

  <xsl:param name="aXmlPath" select="''" />
  <xsl:param name="aDoc"     select="document($aXmlPath)" />

  <xsl:template match="@* | node()">
    <xsl:copy>
       <xsl:apply-templates select="@* | node()" />
    </xsl:copy>
  </xsl:template>

  <!-- text nodes will be checked against doc A -->
  <xsl:template match="*[not(*)]/text()">
    <xsl:variable name="path">
      <xsl:call-template name="calculatePath" />
    </xsl:variable>

    <xsl:variable name="valueFromA">
      <xsl:call-template name="nodeValueByPath">
        <xsl:with-param name="path"    select="$path" />
        <xsl:with-param name="context" select="$aDoc" />
      </xsl:call-template>
    </xsl:variable>

    <xsl:choose>
      <!-- either there is something at that path in doc A -->
      <xsl:when test="starts-with($valueFromA, 'found:')">
        <!-- remove prefix added in nodeValueByPath, see there --> 
        <xsl:value-of select="substring-after($valueFromA, 'found:')" />
      </xsl:when>
      <!-- or we take the value from doc B -->
      <xsl:otherwise>
        <xsl:value-of select="." />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- this calcluates a simpe path for a node -->
  <xsl:template name="calculatePath">
    <xsl:for-each select="..">
      <xsl:call-template name="calculatePath" />
    </xsl:for-each>
    <xsl:if test="self::*">
      <xsl:value-of select="concat(name(), '/')" />
    </xsl:if>
  </xsl:template>

  <!-- this retrieves a node value by its simple path -->
  <xsl:template name="nodeValueByPath">
    <xsl:param name="path"    select="''" />
    <xsl:param name="context" select="''" />

    <xsl:if test="contains($path, '/') and count($context)">
      <xsl:variable name="elemName" select="substring-before($path, '/')" />
      <xsl:variable name="nextPath" select="substring-after($path, '/')" />
      <xsl:variable name="currContext" select="$context/*[name() = $elemName][1]" />

      <xsl:if test="$currContext">
        <xsl:choose>
          <xsl:when test="contains($nextPath, '/')">
            <xsl:call-template name="nodeValueByPath">
              <xsl:with-param name="path"    select="$nextPath" />
              <xsl:with-param name="context" select="$currContext" />
            </xsl:call-template>
          </xsl:when>
          <xsl:when test="not($currContext/*)">
            <!-- always add a prefix so we can detect 
                 the case "exists in A, but is empty" -->
            <xsl:value-of select="concat('found:', $currContext/text())" />
          </xsl:when>
        </xsl:choose>
      </xsl:if>
    </xsl:if>    
  </xsl:template>
</xsl:stylesheet>


Answer 2:

对多个文件操作的基本技术是通过文档()函数。 该文件的功能如下:

<xsl:variable name="var1" select="document('http://example.com/file1.xml', /)"/>
<xsl:variable name="var2" select="document('http://example.com/file2.xml', /)"/>

一旦你有两个文件,你可以像他们在同一文档中提供使用它们的内容。



文章来源: XSLT to Merge 2 XML Files