1

I have the following XML structure:

<file>
  <root1>
          <object1 id="abc" info="blah"/>
          <object1 id="def" info="blah blah"/>
  </root1>


  <root2>
          <object2 id="abc" x="10" y="20"/>
          <object2 id="def" x="30" y="40""/>
  </root2>
</file>

and I want to transform (merge) it into the following structure:

<file>
  <root>
          <object id="abc" info="blah" x="10" y="20"/>
          <object id="def" info="blah blah" x="30" y="40"/>
  </root>
</file>

We can assume that there are no duplicated nodes nor attributes, for the same id.

Currently, I'm looping throughout the collection of object1 using <xsl:for-each ...>, but I can't figure out how to make this work:

<xsl:for-each select="file/root1/object1">
  <object>
    <xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
    <xsl:attribute name="info"><xsl:value-of select="@info"/></xsl:attribute>
    <xsl:attribute name="x">???</xsl:attribute>
    <xsl:attribute name="y">???</xsl:attribute>
  </object>
</xsl:for-each>

i.e. I need to use the @id of the currently selected <object1> as input for an xpath query on <object2>, inside an attribute of <object>.

I've seen this, this, this, this, this and this but they're all a bit different and I couldn't see how I use it in my case.

Community
  • 1
  • 1
Sparkler
  • 2,581
  • 1
  • 22
  • 41
  • This is (probably*) a grouping problem. If you have read [this](http://stackoverflow.com/questions/14120003/xslt-group-merge-childs-using-key), then you know that the solutions are different for XSLT 1.0 or 2.0. – michael.hor257k Aug 02 '14 at 01:36
  • (*) Unless there is always a 1:1 correspondence between objects in the two root branches of your input - in such case, it's merely a matter of pulling info from the "opposite" object, using a key based on matching id. – michael.hor257k Aug 02 '14 at 01:42

1 Answers1

2

The following stylesheet:

XSLT 1.0

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

<xsl:template match="/">
    <file>
        <root>
            <xsl:for-each select="file/root1/object1">
                <object>
                    <xsl:copy-of select="@* | key('object2', @id)/@*"/>
                </object>
            </xsl:for-each>
        </root> 
    </file>
</xsl:template>

</xsl:stylesheet>

when applied to your input example (corrected for well-formedness), will produce:

<?xml version="1.0" encoding="UTF-8"?>
<file>
   <root>
      <object id="abc" info="blah" x="10" y="20"/>
      <object id="def" info="blah blah" x="30" y="40"/>
   </root>
</file>

It should be obvious that a 1:1 correspondence between the two root branches is assumed here.

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • +1 good answer. I would just add that this will work even when there's not a 1:1 correspondence. The only thing it won't work with is if there are two attributes with the same name and different values (on objects with matching id's). – LarsH Aug 02 '14 at 02:07
  • @LarsH My point re the 1:1 correspondence is that if there is an object2 that does not have a corresponding object1, that object is going to be left out completely. Unlike grouping/merging, where we would collect all distinct ids, regardless of location. – michael.hor257k Aug 02 '14 at 02:29