Derek Gourlay
Derek Gourlay

Reputation: 279

XSLT copy-of without duplicates

I am completely new to XSLT so please bear with me.

I have two xml files that I am attempting to concatenate together using XSLT. I would like to combine the files such that any values specified in the second file override the first. E.g.

firstFile.xml

<person>
  <person-name>Sandy</person-name>
  <person-age>21</person-age>
</person>

<person>
  <person-name>Bob</person-name>
  <person-age>15</person-age>
</person>

override.xml

<person>
  <person-name>Bob</person-name>
  <person-age>21</person-age>
</person>

Result:

<person>
  <person-name>Sandy</person-name>
  <person-age>21</person-age>
</person>
<person>
  <person-name>Bob</person-name>
  <person-age>21</person-age>
</person>

My template for concatenating the 2 files is as follows:

  <xsl:template match="/">
     <!-- MainFile -->
     <xsl:copy-of select="/*"/>

      <!-- Overrides-->
      <xsl:copy-of select="document($overrideFile)/*"/>
  </xsl:template>

I was attempting to setup a for-each loop such that before copying each person in firstFile.xml check if there is a corresponding node in override.xml, but was unsuccessful.

Any tips would be greatly appreciated

Upvotes: 2

Views: 900

Answers (1)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243449

This transformation:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pDoc2Url" select="'file:///c:/temp/delete/override.xml'"/>
 <xsl:variable name="vDoc2" select="document($pDoc2Url)"/>

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

 <xsl:template match="/*">
  <t>
    <xsl:apply-templates
       select="person[not(person-name = $vDoc2/*/person/person-name)]"/>
    <xsl:apply-templates select="$vDoc2/*/person"/>
  </t>
 </xsl:template>
</xsl:stylesheet>

when applied on the first of the provided XML documents (wrapped into a single top element -- to be made a well-formed XML document):

<t>
    <person>
        <person-name>Sandy</person-name>
        <person-age>21</person-age>
    </person>
    <person>
        <person-name>Bob</person-name>
        <person-age>15</person-age>
    </person>
</t>

and being passed as parameter the filename where the second document (again wrapped into a top element) resides -- here is the corrected second document:

c:/temp/delete/override.xml:

<t>
    <person>
        <person-name>Bob</person-name>
        <person-age>21</person-age>
    </person>
</t>

produces the wanted, correct result:

<t>
   <person>
      <person-name>Sandy</person-name>
      <person-age>21</person-age>
   </person>
   <person>
      <person-name>Bob</person-name>
      <person-age>21</person-age>
   </person>
</t>

II. A shorter, but less flexible solution -- no identity rule and no xsl:apply-templates:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:param name="pDoc2Url" select="'file:///c:/temp/delete/override.xml'"/>
 <xsl:variable name="vDoc2" select="document($pDoc2Url)"/>

 <xsl:template match="/*">
  <t>
    <xsl:copy-of
       select="person[not(person-name = $vDoc2/*/person/person-name)]"/>
    <xsl:copy-of select="$vDoc2/*/person"/>
  </t>
 </xsl:template>
</xsl:stylesheet>

Upvotes: 3

Related Questions