0

我知道这里很少有与 xml/xslt 合并相关的问题,但似乎没有一个可以解决我遇到的问题。

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

将 a.xml 与 b.xml 合并并以这样的方式生成 c.xml

  • c.xml 将包含 a.xml 和 b.xml 之间的公共节点(节点值取自 a.xml)
  • 此外,c.xml 将包含 b.xml 中而不是 a.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 定义,因为我不是任何方面的专家。

a /root_node/settings/ setting1是与b /root_node/settings/ setting1的“公共节点”,因为使用相同的路径到达 2 个节点。设置 2 和设置 3 相同。

2个“非公共节点”是一个/root_node/settings/ setting4仅在 a.xml 中找到(它不应该出现在输出中)和b /root_node/settings/ setting5仅在 b.xml 中找到(它应该进入输出)。

我所说的“通用解决方案”并不是指可以使用任何输入 XML 格式的东西。我的意思是 xslt 不应该包含硬编码的 xpath,而您可能会添加诸如“这仅在 a.xml 中的节点是唯一的时才有效”之类的限制,或者您可能认为合适的任何其他限制。

4

2 回答 2

2

对多个文件进行操作的基本技术是通过 document() 函数。文档函数如下所示:

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

拥有这两个文档后,您可以像在同一个文档中一样使用它们的内容。

于 2012-07-26T13:02:21.500 回答
2

下面的 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>
于 2012-07-26T14:10:34.883 回答