-1

Note: this is NOT a duplicate of Merge 2 XML files based on attribute values using XSLT? but an extension to it. Given the following input files

file1.xml

<config>
 <state version="10">
  <root value="100" group="5">
     <leaf number = "2"/>
  </root>
  <root value="101" group="6" overrideAttr="oldval">
     <leaf number = "3"/>
  </root>
 </state>
</config>

file2.xml

<config>
 <state version="10">
  <root value="100" group="5">
     <leaf number = "6"/>
  </root>
  <root value="101" group="6" overrideAttr="newval" addtionalAttr="hello">
     <leaf number = "4"/>
  </root>
 </state>
</config>

I'd like to have this output.xml

<config>
 <state version="10">
  <root value="100" group="5">
     <leaf number = "2"/>
     <leaf number = "6"/>
  </root>
  <root value="101" group="6" overrideAttr="newval" addtionalAttr="hello">
     <leaf number = "3"/>
     <leaf number = "4"/>
  </root>
 </state>
</config>

Desired extensions are

  • attributes (e.g. overrideAttr) on the "same node" (e.g. element root with value="101" and group="6") should be overwritten
  • new attributes (e.g. addtionalAttr) should be added

Can this be achieved by xsl?

Community
  • 1
  • 1
Clemens
  • 23
  • 1
  • 4
  • 1
    **1.** What defines "same" node? -- **2.** Please state if using XSLT 1.0 or 2.0. – michael.hor257k Feb 13 '17 at 12:09
  • >1.What defines "same" node? see the "solution" of the above mentioned thread >2. Please state if using XSLT 1.0 or 2.0 whatever javax.xml.transform.TransformerFactory supports – Clemens Feb 13 '17 at 12:41

2 Answers2

1

In the answer that you have linked to, there is an xsl:apply-templates that copies across the child elements from the second file.

  <xsl:apply-templates
    select="document('file2.xml')
          /config/state[@version = current()/../@version]
                 /root[@value = current()/@value and
                       @group = current()/@group]/*" />

All you need to do is add a similar line to copy across attributes

  <xsl:apply-templates
    select="document('file2.xml')
          /config/state[@version = current()/../@version]
                 /root[@value = current()/@value and
                       @group = current()/@group]/@*" />

Although this would need to done before the copying of any existing child nodes (as attributes must be added before child nodes).

Additionally, you might want to use a variable to avoid repeating the xpath expression.

Try this XSLT...

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

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

  <xsl:template match="root">
        <xsl:variable name="file2root" select="document('file2.xml')
              /config/state[@version = current()/../@version]
                     /root[@value = current()/@value and
                           @group = current()/@group]" />
    <xsl:copy>
      <xsl:apply-templates select="@*" />
      <xsl:apply-templates select="$file2root/@*" />
            <xsl:apply-templates select="node()" />
      <xsl:apply-templates select="$file2root/*" />
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Note that this takes advantage of the fact that "Adding an attribute to an element replaces any existing attribute of that element with the same expanded-name". (See https://www.w3.org/TR/xslt#creating-attributes)

Tim C
  • 70,053
  • 14
  • 74
  • 93
0

If you want to extend that solution then you can do it as follows, change that template for the root elements (http://xsltransform.net/gWEamLR/1) to

      <xsl:template match="root">
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <xsl:apply-templates select="$doc2
              /config/state[@version = current()/../@version]
                     /root[@value = current()/@value and
                           @group = current()/@group]/@*" />
      <xsl:apply-templates select="node()"/>
      <xsl:apply-templates select="
        $doc2
              /config/state[@version = current()/../@version]
                     /root[@value = current()/@value and
                           @group = current()/@group]/*" />
    </xsl:copy>
  </xsl:template>

and make sure you define <xsl:param name="doc2" select="document('file2.xml')"/>.

There might be better ways using keys or grouping doing that merging, and of course in XSLT 3.0 we now have xsl:merge

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="xs math"
    version="3.0">

    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/" name="main">
        <config>
            <xsl:merge>
                <xsl:merge-source select="doc('file1.xml')/config/state">
                    <xsl:merge-key select="@version"/>
                </xsl:merge-source>
                <xsl:merge-source select="doc('file2.xml')/config/state">
                    <xsl:merge-key select="@version"/>
                </xsl:merge-source>
                <xsl:merge-action>
                    <xsl:copy>
                        <xsl:copy-of select="@*"/>
                        <xsl:merge>
                            <xsl:merge-source select="current-merge-group()[1]/root">
                                <xsl:merge-key select="@value"/>
                                <xsl:merge-key select="@group"/>
                            </xsl:merge-source>
                            <xsl:merge-source select="current-merge-group()[2]/root">
                                <xsl:merge-key select="@value"/>
                                <xsl:merge-key select="@group"/>
                            </xsl:merge-source>
                            <xsl:merge-action>
                                <xsl:copy>
                                    <xsl:copy-of select="current-merge-group()[2]/@*"/>
                                    <xsl:copy-of select="current-merge-group()/node()"/>
                                </xsl:copy>
                            </xsl:merge-action>
                        </xsl:merge>
                    </xsl:copy>
                </xsl:merge-action>
            </xsl:merge>
        </config>
    </xsl:template>

</xsl:stylesheet>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110