2012-07-26 127 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', /)"/> 
    

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