簡體   English   中英

XSLT合並2個XML文件

[英]XSLT to Merge 2 XML Files

我知道這里很少有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 / setting1b / root_node / settings / setting1的“公共節點”,因為使用相同路徑訪問了2個節點。 設置2和設置3相同。

在2“非公共節點”是一個 / root_node /設置/ setting4其僅在A.XML實測值(它應該在輸出不來)和b / root_node /設置/ setting5其僅在B.XML實測值(它應該進入輸出)。

我所說的“通用解決方案”並不是說某種東西可以用輸入XML的任何格式工作。 我的意思是,xslt不應包含硬編碼的xpath,而您可能會添加諸如“僅當a.xml中的節點是唯一的時這才有效”之類的限制,或者您可能認為合適的任何其他限制。

對多個文件進行操作的基本技術是通過document()函數。 文檔功能如下所示:

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

擁有兩個文檔后,就可以像在同一文檔中一樣使用它們的內容。

以下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的模板calculatePath
    2. 調用遞歸模板nodeValueByPath ,嘗試從另一個文檔中檢索相應簡單路徑的文本值。
    3. 如果找到相應的文本節點,則使用其值。 這滿足您的第一個要點。
    4. 如果沒有找到對應的節點,它將使用當前值,即B的值。 這滿足您的第二個要點。

結果,新文檔匹配B的結構並包含:

  • B中所有在A沒有對應節點的文本節點值。
  • 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>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM