-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?

2
  • 1
    1. What defines "same" node? -- 2. Please state if using XSLT 1.0 or 2.0. Commented Feb 13, 2017 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 Commented Feb 13, 2017 at 12:41

2 Answers 2

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)

Sign up to request clarification or add additional context in comments.

Comments

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.