tags:

views:

169

answers:

2

Hi all,

I have an xml of the form:

<Set>
   <Element name="Superset1_Set1_Element1"/>
   <Element name="Superset1_Set1_Element2"/>
   <Element name="Superset1_Set2_Element1"/>
   <Element name="Superset2_Set1_Element1"/>
   <Element name="Superset2_Set2_Element1"/>
</Set>

I wish to transform it to an xml of the form:

<Superset name="Superset1">
   <Set name="Set1">
       <Element name="Element1"/>
       <Element name="Element2"/>
   </Set>
   <Set name="Set2">
       <Element name="Element1"/>
   </Set>
</Superset>
<Superset name="Superset2">
   <Set name="Set1">
       <Element name="Element1"/>
   </Set>
   <Set name="Set2">
       <Element name="Element1"/>
   </Set>
</Superset>

How can this be done with XSLT?

Thanks a lot in advance!

+3  A: 

Preserved for reference, but I strongly suggest the use of templates (i.e. Tomalak's solution) where possible for readability alone...


Certainly possible, but actually harder than I anticipated because of the second order sets and the double underscores - the following could certainly be improved if the "name" values were of a friendlier format.

<xsl:key name="supers" match="Set/Element" use="substring-before(@name,'_')"/>
<xsl:key name="sets" match="Set/Element" use="concat(substring-before(@name,'_'),'_',substring-before(substring-after(@name,'_'),'_'))"/>

<xsl:template match="/">
    <xsl:for-each select="Set/Element[generate-id() = generate-id(key('supers',substring-before(@name,'_'))[1])]">  
     <xsl:variable name="super" select="substring-before(@name,'_')"/>
      <Superset name="{$super}">   
      <xsl:for-each select="//Set/Element[generate-id() = generate-id(key('sets',concat($super,'_',substring-before(substring-after(@name,'_'),'_')))[1])]">
      <Set name="{substring-before(substring-after(@name,'_'),'_')}">
       <xsl:variable name="set" select="concat($super,'_',substring-before(substring-after(@name,'_'),'_'))"/>
       <xsl:for-each select="//Set/Element[starts-with(@name,$set)]">
        <Element name="{substring-after(substring-after(@name,'_'),'_')}"/>
       </xsl:for-each>
      </Set>
      </xsl:for-each>
      </Superset>
    </xsl:for-each>
</xsl:template>

The trick is just muenchian grouping and capturing the right key values.

It's really not pretty, so I'm sure there's a better solution available but I'm jetlagged :P

annakata
+1 - Though I would recommend replacing the "//Set/Element" expressions with the equivalent calls of key() - that's what you have it for, after all. :)
Tomalak
Thanks guys for your in-depth solution proposition. Since my case is much more complicated than the example given I have not yet decided which of your versions I should use. Time will tell I suppose...
Yaneeve
I can't seem to validate this xslt fragment even when I wrap it up with the xsl:stylesheet element...
Yaneeve
WFM - what's the problem you're seeing?
annakata
Sorry :( My Bad... It works now, I must have copied it wrong off this page.
Yaneeve
+5  A: 
Tomalak
Side note: Though this *looks* a lot more complicated than @annakata's solution, it is in fact absolutely the same thing. It just uses separate templates in place of nested <xsl:for-each> calls. And a lot of line breaks that are not strictly necessary. ;-)
Tomalak
There is a little compile error in this. change 'kElementBySuperset' to 'kElementBySup' and then this will work fine.
Aamir
Yes, just seen and fixed. This is what you get when you edit the code here *after* you pasted the tested version from your text editor...
Tomalak
+1 see, I knew it could be done with templates and I did say I was tired :D
annakata
Hey - I don't think your solution should be deleted! It is a lot shorter than this version, which is clearly one of the advantages of <xsl:for-each> over separate templates.
Tomalak
@annakata... you shouldn't have deleted your reply. That was another good thing to learn for us XSLT newbies.@Tomalak: +1 for an excellent solution
Aamir
Happy now? :P (the "//" alone in the for-each solution scares me, and brevity is never a great reason for anything)
annakata
The "//" is easily fixed - just call key() with the current name part.
Tomalak
Thanks guys for your in-depth solution proposition. Since my case is much more complicated than the example given I have not yet decided which of your versions I should use. Time will tell I suppose...
Yaneeve
How much more complicated is it? And why didn't you come up with the real case from the start? :-)
Tomalak
Ultra complicated... This is just the first problem of many. I didn't come up with the real case for two reasons. The first was because I had wanted to have this as a reference for people with likewise problems. The second because my other problems are of a different nature all together and I didn't want to mix and match...
Yaneeve
So *this part* of your compound problem has been solved? You sounded a bit like it is also more complicated than you indicated.
Tomalak
This part has not been solved yet, though I've got some ideas, due to the fact that unlike my example where superset, set and element do not have underscores within them, my real supersets, sets and elements do. For example Superset1 could be homeward_bound whereas a set could be type_of_home and element could be loft. In this case my current element name would be "homeward_bound_type_of_home_loft". What would you suggest? Currently I think I will replace my concrete superset and set names with virtual Superset1,2,etc... Set1,2,etc.. and then run your algorithm. What do you say?
Yaneeve
This sounds like you're in knee deep sh*t here. ;-) Whoever is responsible for that XML should be hurt a lot. If you have nothing to split on that would work with substring-before() etc., then you have nothing to build an <xsl:key> on. Which means you must take a two-step approach, transforming your XML to something more digestible first (using recursion and a list of known, fixed set names), and then you can apply grouping to it. The grouping part should then also get a bit easier, since you can cut out all the substring calculations.
Tomalak
It's actually waist high ;-) Can I take the two-step approach within the same xslt or must I create two xslts and run the second one on the output of the first? (As you can see I am kind of an XSLT newbie)
Yaneeve
Question continues in: http://stackoverflow.com/questions/1075400/how-to-create-subsets-of-a-single-set-of-elements-where-element-names-are-comple
Yaneeve