vandroid
vandroid

Reputation: 312

Combine two XML documents with similar schema

I have two XML documents that need to be combined in special way.

As a simple example I'll just put both documents into one:

<?xml version="1.0" encoding="utf-8" ?>
<data>
    <config1>
        <option1 type="a">A</option1>
        <option2 type="b">B</option2>
        <option3 type="c">C</option3>
    </config1>
    <config2>
        <option2 type="bb">BB</option2>
        <option4>D</option4>
    </config2>
</data>

So, i need to merge config1 and config2 to this result:

<?xml version="1.0" encoding="UTF-8"?>
<data>
  <config>
    <option1 type="a">A</option1>
    <option2 type="bb">BB</option2>
    <option3 type="c">C</option3>
    <option4>D</option4>
  </config>
</data>

Transformation rules are:

  1. Get option from config1 if there is no such option in config2
  2. Otherwise get that option from config2
  3. Get options from config2 if they are not present in config1

i've made following XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/*">
    <xsl:copy>
      <config>
        <!-- Select options from config 1. -->
        <xsl:for-each select="config1/*">
          <xsl:choose>
            <!-- Leave option as is. -->
            <xsl:when test="not(/data/config2/*[name() = name(current())])">
              <xsl:copy-of select="."/>
            </xsl:when>
            <!-- Overwrite option. -->
            <xsl:otherwise>
              <xsl:copy-of select="/data/config2/*[name() = name(current())]"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each>

        <!-- Select options from config 2. -->
        <xsl:for-each select="config2/*">
          <!-- Append "new" option -->
          <xsl:if test="not(/data/config1/*[name()=name(current())])">
            <xsl:copy-of select="."/>
          </xsl:if>
        </xsl:for-each>
      </config>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

It works, but looks lame.

Many XSL gurus prefer apply-templates over for-each.

Is it possible to rewrite my template in that way?

Upvotes: 0

Views: 97

Answers (1)

michael.hor257k
michael.hor257k

Reputation: 117043

As a simple example I'll just put both documents into one:

That's not a good idea, because dealing with two documents is not the same as dealing with two node-sets in the same document. So, assuming your processed document is:

<?xml version="1.0" encoding="utf-8" ?>
<data>
    <config1>
        <option1 type="a">A</option1>
        <option2 type="b">B</option2>
        <option3 type="c">C</option3>
    </config1>
</data>

and there is another document:

file2.xml

<?xml version="1.0" encoding="utf-8" ?>
<data>
    <config2>
        <option2 type="bb">BB</option2>
        <option4>D</option4>
    </config2>
</data>

you could use the following stylesheet:

XSLT 1.0

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:variable name="config2" select="document('file2.xml')/data/config2" />

<xsl:template match="/">
    <data>
        <config>
            <xsl:apply-templates select="data/config1/*[not(name()=name($config2/*))] | $config2/*"/>
        </config>
    </data>
</xsl:template>

<xsl:template match="data/*/*">
    <xsl:copy-of select="."/>
</xsl:template>

</xsl:stylesheet>

to obtain this result:

<?xml version="1.0" encoding="UTF-8"?>
<data>
   <config>
      <option1 type="a">A</option1>
      <option3 type="c">C</option3>
      <option2 type="bb">BB</option2>
      <option4>D</option4>
   </config>
</data>

Upvotes: 2

Related Questions