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.

2
  • This is (probably*) a grouping problem. If you have read this, then you know that the solutions are different for XSLT 1.0 or 2.0. Commented Aug 2, 2014 at 1: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. Commented Aug 2, 2014 at 1:42

1 Answer 1

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.

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

2 Comments

+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 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.

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.