Tim B
Tim B

Reputation: 118

Merging data from an XML node to another node using XSLT

Using XSLT, I am trying to figure out how to merge/update the data in a set of nodes with data from another set of nodes. The nodes have the same schema, but different parents. The data needs to be merged based on a shared parent attribute. In the example below, data is being copied from Principal to Driver. Can anyone help me out here?

Input File:

<Info>
  <Principal id="Insured">
    <PersonName>
      <GivenName>Jane</GivenName>
      <OtherGivenName>A</OtherGivenName>
      <Surname>Doe</Surname>
    </PersonName>
    <PersonInfo>
      <BirthDate>01-01-1980</BirthDate>
      <MaritalStatus>M</MaritalStatus>
    </PersonInfo>
    <PrincipalInfo></PrincipalInfo>
  </Principal>
  <Policy>
    <Driver id="Insured">
      <PersonName>
        <GivenName>Jane</GivenName>
        <Surname>Smith</Surname>
      </PersonName>
      <PersonInfo>
        <BirthDate>01-01-1980</BirthDate>
        <MaritalStatus>S</MaritalStatus>
        <Occupation>Manager</Occupation>
      </PersonInfo>
    </Driver>
    <PolicyInfo></PolicyInfo>
  </Policy>
</Info>

Desired Result:

<Info>
  <Principal id="Insured">
    <PersonName>
      <GivenName>Jane</GivenName>
      <OtherGivenName>A</OtherGivenName>
      <Surname>Doe</Surname>
    </PersonName>
    <PersonInfo>
      <BirthDate>01-01-1980</BirthDate>
      <MaritalStatus>M</MaritalStatus>
    </PersonInfo>
    <PrincipalInfo></PrincipalInfo>
  </Principal>
  <Policy>
    <Driver id="Insured">
      <PersonName>
        <GivenName>Jane</GivenName>
        <OtherGivenName>A</OtherGivenName>
        <Surname>Doe</Surname>
      </PersonName>
      <PersonInfo>
        <BirthDate>01-01-1980</BirthDate>
        <MaritalStatus>M</MaritalStatus>
        <Occupation>Manager</Occupation>
      </PersonInfo>
    </Driver>
    <PolicyInfo></PolicyInfo>
  </Policy>
</Info>

Upvotes: 2

Views: 1309

Answers (1)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243459

Here is a complete solution:

<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:key name="kPrincipalById" match="Principal"
  use="@id"/>

 <xsl:key name="kPrincipalChild" match="Principal/*/*"
  use="concat(../../@id, name())"/>

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

 <xsl:template match="Driver/*">
  <xsl:variable name="vPrincipal"
   select="key('kPrincipalById', ../@id)"/>

   <xsl:copy>
    <xsl:apply-templates select="@*"/>
    <xsl:apply-templates select=
     "$vPrincipal/*[name()=name(current())]/*"/>
    <xsl:apply-templates select=
     "*[not(key('kPrincipalChild',
                 concat(../../@id,name())
                 )
            )
        ]"/>
   </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<Info>
    <Principal id="Insured">
        <PersonName>
            <GivenName>Jane</GivenName>
            <OtherGivenName>A</OtherGivenName>
            <Surname>Doe</Surname>
        </PersonName>
        <PersonInfo>
            <BirthDate>01-01-1980</BirthDate>
            <MaritalStatus>M</MaritalStatus>
        </PersonInfo>
        <PrincipalInfo></PrincipalInfo>
    </Principal>
    <Policy>
        <Driver id="Insured">
            <PersonName>
                <GivenName>Jane</GivenName>
                <Surname>Smith</Surname>
            </PersonName>
            <PersonInfo>
                <BirthDate>01-01-1980</BirthDate>
                <MaritalStatus>S</MaritalStatus>
                <Occupation>Manager</Occupation>
            </PersonInfo>
        </Driver>
        <PolicyInfo></PolicyInfo>
    </Policy>
</Info>

the wanted, correct result is produced:

<Info>
   <Principal id="Insured">
      <PersonName>
         <GivenName>Jane</GivenName>
         <OtherGivenName>A</OtherGivenName>
         <Surname>Doe</Surname>
      </PersonName>
      <PersonInfo>
         <BirthDate>01-01-1980</BirthDate>
         <MaritalStatus>M</MaritalStatus>
      </PersonInfo>
      <PrincipalInfo/>
   </Principal>
   <Policy>
      <Driver id="Insured">
         <PersonName>
            <GivenName>Jane</GivenName>
            <OtherGivenName>A</OtherGivenName>
            <Surname>Doe</Surname>
         </PersonName>
         <PersonInfo>
            <BirthDate>01-01-1980</BirthDate>
            <MaritalStatus>M</MaritalStatus>
            <Occupation>Manager</Occupation>
         </PersonInfo>
      </Driver>
      <PolicyInfo/>
   </Policy>
</Info>

Explanation:

  1. The identity rule/template copies every node "as-is". The use and overriding of the identity rule is the most fundamental and powerful XSLT design pattern.

  2. There is just one additional template that overrides the identity rule for children-elements of Driver. It copies (and effectively replaces the same-named grand-child elements of Driver with the corresponding) grand-child elements of Principal. Then it still processes (copies) those grand-children elements of Driver that do not have corresponding grand-children elements of Principal

  3. For convenient access to Principal and its grand-children -- by id and id++name(), there are two keys defined and used.

Upvotes: 2

Related Questions