Glenn N
Glenn N

Reputation: 845

Merge 2 XML including unmatched nodes

I have two XML files, a and b. I want to merge at a certain child level, Some nodes have attributes. When node names and attributes match, I want the child nodes to be copied. If there are unmatched nodes, I want those also. I present here an example where all nodes of a are copied, but I only get matched nodes of b (they are merged). How do I get the unmatched nodes of b?

File a.xml:

<level0>
  <level1>
    <level2 value="21">
      <level3 value="31">
        <a>A31</a>
      </level3>
      <level3 value="32">
        <a>A32</a>
      </level3>
    </level2>
    <level2 value="22">
      <level3 value="33">
        <a>A33</a>
      </level3>
    </level2>
  </level1>
</level0>   

File b.xml:

<level0>
  <level1>
    <level2 value="21">
      <level3 value="31">
        <b>B31</b>
      </level3>
    </level2>
    <level2 value="22">
      <level3 value="33">
        <b>B33</b>
      </level3>
      <level3 value="34">
        <b>B34</b>
      </level3>
    </level2>
  </level1>
</level0> 

Using XSL (below), I can merge b onto a, but this node from b is dropped, because there is no match in a:

<level3 value="34">
  <b>B34</b>
</level3>

My output looks like this:

<?xml version="1.0" encoding="ISO-8859-1"?>
<level0>
  <level1>
    <level2 value="21">
      <level3 value="31">
        <a>A31</a>
      <b>B31</b></level3>
      <level3 value="32">
        <a>A32</a>
      </level3>
    </level2>
    <level2 value="22">
      <level3 value="33">
        <a>A33</a>
      <b>B33</b></level3>
    </level2>
  </level1>
</level0>

Here is my current XSL. Run it: xsltproc ab.xsl b.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="ISO-8859-1" indent="yes" />
    <xsl:variable name="with" select="'b.xml'" />

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

    <xsl:template match="level3">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
            <xsl:variable name="info" select="document($with)//*/level2[@value=current()/../@value]/level3[@value=current()/@value]/." />
            <xsl:for-each select="$info/*">
                <xsl:copy-of select="." />
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
</xsl:transform>

I'll bet this is easy, but XSL is not my thing, I rarely need to use it. Thanks for you help.

Upvotes: 0

Views: 170

Answers (1)

Mathias M&#252;ller
Mathias M&#252;ller

Reputation: 22617

As far as I can see, there is no need to write your own merge stylesheet. Use the merge algorithm that was developed by Oliver Becker. You find the code online here.

The easiest way to run the stylesheet is to create an XML file that summarizes which files should be merged:

Summary Input XML

<?xml version="1.0"?>
<merge xmlns="http://informatik.hu-berlin.de/merge">
   <file1>a.xml</file1>
   <file2>b.xml</file2>
</merge> 

When applied to both of your input files, this is the output I get:

Output

<?xml version="1.0" encoding="UTF-8"?><level0>
  <level1>
    <level2 value="21">
      <level3 value="31">
        <a>A31</a>
      <b>B31</b>
      </level3>
      <level3 value="32">
        <a>A32</a>
      </level3>
    </level2>
    <level2 value="22">
      <level3 value="33">
        <a>A33</a>
      <b>B33</b>
      </level3>
    <level3 value="34">
        <b>B34</b>
      </level3>
    </level2>
  </level1>
</level0>

The output differs from your expected output in one respect: <level3 value="34"> is not present in the expected output, as pointed out by @LegoStormtroopr already.

Note that "intersection" and "union" in XSLT (or XML in general) are not necessarily the same concepts as in SQL.

Upvotes: 2

Related Questions