2012-07-26 157 views
0

我知道这里有几个xml/xslt合并相关的问题,但似乎没有解决我的问题。XSLT合并2 XML文件

我寻找是一个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定义 因为我不是任何专家。

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

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

通过“通用解决方案”我不是指任何可以工作在输入XML格式的任何格式的东西。我的意思是,xslt不应该包含硬代码xpaths,而您可能会添加像“只有当a.xml中的节点是唯一的”这样的限制,或者您认为适合的任何其他限制。

+0

有没有什么特别的原因setting4是不存在的输出?从这个例子中很难推断出转换规则,特别是因为你已经说过你希望转换一般适用于很多其他输入。你需要尝试解释一般规则。 – 2012-07-26 11:42:10

+0

您是否看到子弹点?这就是我试图用2个子弹做的事情 - >解释一般规则。 – Doru 2012-07-26 12:01:50

+0

Doru,请编辑问题并精确定义“常见节点”的含义。在目前的形式下,如果没有这样的定义,这个问题就太模棱两可了,并且会得到来自N个读者的N +解释,他们不明智地决定给出答案。标题中的“联合”一词也具有误导性 - 从问题文本看来,这里似乎不需要联合操作 - 因为只有“常见”元素(无论这意味着什么)应该被复制,并且只能从Doc1中复制。 – 2012-07-26 12:18:58

回答

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具有在A没有相应的节点

      • 所有文本节点值:

其结果是,新的文档匹配B的结构,并且包含。

  • B中的对应节点存在时,来自A的文本节点值。
  • 这里的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> 
    
    +0

    您的解决方案非常接近所要求的。你的假设'暗示是你不能有任何两个具有相同名称的元素在输入中共享同一个父元素。'也是准确的。唯一的是在执行这个xslt之后,b.xml中的值不会被复制。节点在输出中有空值。 – Doru 2012-07-26 15:01:37

    +0

    @Doru是的,那是我几分钟前修复的一个错误。 – Tomalak 2012-07-26 15:16:27

    +0

    是的,现在它效果很好。谢谢。 – Doru 2012-07-26 15:43:08

    2

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

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

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