Creating nested XML using the muenchian method in XLST

I am trying to create a nested XML document from a flat XML document as shown below. After searching here for other resources available on the network, it seems that the muenchain method may be the solution, however I struggle with its application to my situation.

The XML I need to translate takes the form:

<i> <item id="1" name="one" sub_id="10" sub_name="s1" detail_id="t1" detail_name="aaaa"/> <item id="1" name="one" sub_id="10" sub_name="s1" detail_id="t2" detail_name="bbb"/> <item id="1" name="one" sub_id="20" sub_name="s2" detail_id="t1" detail_name="ccc"/> <item id="1" name="one" sub_id="20" sub_name="s2" detail_id="t2" detail_name="ddd"/> <item id="2" name="two" sub_id="10" sub_name="s1" detail_id="t1" detail_name="eee"/> <item id="2" name="two" sub_id="10" sub_name="s1" detail_id="t2" detail_name="fff"/> <item id="2" name="two" sub_id="20" sub_name="s2" detail_id="t1" detail_name="ggg"/> <item id="2" name="two" sub_id="20" sub_name="s2" detail_id="t2" detail_name="hhh"/> <item id="3" name="three" /> <item id="4" name="four" sub_id="10" sub_name="s1" detail_id="t1" detail_name="mmm"/> <item id="4" name="four" sub_id="10" sub_name="s1" detail_id="t2" detail_name="nnn"/> <item id="4" name="four" sub_id="20" sub_name="s2" detail_id="t1" detail_name="ooo"/> <item id="4" name="four" sub_id="20" sub_name="s2" detail_id="t2" detail_name="ppp"/> </i> 

I would like to convert this to XML in the following form:

  <i> <item id="1" name="one" sub_items="true"> <sub_item sub_id="10" sub_name="s1"> <detail detail_id="t1" detail_name="aaaa"/> <detail detail_id="t2" detail_name="bbb"/> </sub_item> <sub_item sub_id="20" sub_name="s2"> <detail detail_id="t1" detail_name="ccc"/> <detail detail_id="t2" detail_name="ddd"/> </sub_item> </item> <item id="2" name="two" sub_items="true"> <sub_item sub_id="10" sub_name="s1"> <detail detail_id="t1" detail_name="eee"/> <detail detail_id="t2" detail_name="fff"/> </sub_item> <sub_item sub_id="20" sub_name="s2"> <detail detail_id="t1" detail_name="ggg"/> <detail detail_id="t2" detail_name="hhh"/> </sub_item> </item> <item id="3" name="three" sub_items="false"/> <item id="4" name="four" sub_items="true"> <sub_item sub_id="10" sub_name="s1"> <detail detail_id="t1" detail_name="mmm"/> <detail detail_id="t2" detail_name="nnn"/> </sub_item> <sub_item sub_id="20" sub_name="s2"> <detail detail_id="t1" detail_name="ooo"/> <detail detail_id="t2" detail_name="ppp"/> </sub_item> </item> </i> 

From my research, I have the following XLST to do the conversion. I use the key method for the id attribute of the element. This does not group the data, repeating everything correctly for a given element identifier at each level, which makes sense given the key. So my problem is how can I select the nodes needed to output each level of the socket, do I need to use a different key operator?

 <?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:key name="indexes" match="i/item" use="@id"/> <xsl:template match="i"> <i> <xsl:for-each select="item[count(. | key('indexes',@id)[1]) = 1]" > <xsl:sort select="@id"/> <item> <xsl:attribute name="id"> <xsl:value-of select="@id"/> </xsl:attribute> <xsl:attribute name="name"> <xsl:value-of select="@name"/> </xsl:attribute> <xsl:attribute name="hasRows"> <xsl:value-of select="@id"/> </xsl:attribute> <xsl:for-each select="key('indexes',@id)"> <subitem> <xsl:attribute name ="sub_id"> <xsl:value-of select="@sub_id"/> </xsl:attribute> <xsl:attribute name ="sub_name"> <xsl:value-of select="@sub_name"/> </xsl:attribute> <xsl:for-each select="key('indexes',@id)"> <segment> <xsl:attribute name ="detail_id"> <xsl:value-of select="@detail_id"/> </xsl:attribute> <xsl:attribute name ="detail_name"> <xsl:value-of select="@detail_name"/> </xsl:attribute> </segment> </xsl:for-each> </subitem> </xsl:for-each> </item> </xsl:for-each> </i> </xsl:template> </xsl:stylesheet> 

Is it also possible to populate the * sub_items * attribute with true when subitems / parts for an element exist and false when they do not?

Finally, to improve XSLT understanding / skills, can anyone recommend some good learning resources?

+4
source share
2 answers

Pay attention to this XSLT 1.0 conversion with:

  • AVT (attribute value templates) to simplify attribute definition)
  • one master key used to group an element of the first level.
  • One second composite key to execute sublevel groups
  • widespread use of xsl:apply-templates with modes compared to xsl:for-each

     <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:key name="kSubs" match="item" use="concat(@id,'|',@sub_id)"/> <xsl:key name="kItems" match="item" use="@id"/> <xsl:template match="i"> <i> <xsl:apply-templates select="item[ generate-id() = generate-id(key('kItems',@id)[1])]"/> </i> </xsl:template> <xsl:template match="item"> <item id="{@id}" name="{@name}" sub_items="{boolean(@sub_id)}"> <xsl:apply-templates select=".|following-sibling::item[ (generate-id() = generate-id(key('kSubs',concat(@id,'|',@sub_id))[1])) and (./@id = current()/@id) ]" mode="sub_id"/> </item> </xsl:template> <xsl:template match="item[@sub_id]" mode="sub_id"> <sub_item sub_id="{@sub_id}" sub_name="{@sub_name}"> <xsl:apply-templates select="key('kSubs',concat(@id,'|',@sub_id))" mode="detail"/> </sub_item> </xsl:template> <xsl:template match="item" mode="detail"> <detail detail_id="{@detail_id}" detail_name="{@detail_name}"/> </xsl:template> </xsl:stylesheet> 

Applied to the input specified in your question creates:

 <i> <item id="1" name="one" sub_items="true"> <sub_item sub_id="10" sub_name="s1"> <detail detail_id="t1" detail_name="aaaa"/> <detail detail_id="t2" detail_name="bbb"/> </sub_item> <sub_item sub_id="20" sub_name="s2"> <detail detail_id="t1" detail_name="ccc"/> <detail detail_id="t2" detail_name="ddd"/> </sub_item> </item> <item id="2" name="two" sub_items="true"> <sub_item sub_id="10" sub_name="s1"> <detail detail_id="t1" detail_name="eee"/> <detail detail_id="t2" detail_name="fff"/> </sub_item> <sub_item sub_id="20" sub_name="s2"> <detail detail_id="t1" detail_name="ggg"/> <detail detail_id="t2" detail_name="hhh"/> </sub_item> </item> <item id="3" name="three" sub_items="false"/> <item id="4" name="four" sub_items="true"> <sub_item sub_id="10" sub_name="s1"> <detail detail_id="t1" detail_name="mmm"/> <detail detail_id="t2" detail_name="nnn"/> </sub_item> <sub_item sub_id="20" sub_name="s2"> <detail detail_id="t1" detail_name="ooo"/> <detail detail_id="t2" detail_name="ppp"/> </sub_item> </item> </i> 
+1
source

Muenchian's method is difficult for nested grouping. The first level of grouping is easy to manage, but for others you need to index elements with compound keys composed with previous identifiers.

 <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output indent="yes" /> <xsl:key name="items" match="item" use="@id" /> <xsl:key name="sub-items" match="item" use="concat(@id, '|', @sub_id)" /> <xsl:template match="i"> <i> <xsl:for-each select="item[generate-id() = generate-id(key('items', @id)[1])]"> <xsl:copy> <xsl:copy-of select="@id|@name" /> <xsl:choose> <xsl:when test="count(key('items', @id)) &gt; 1"> <xsl:attribute name="sub_items">true</xsl:attribute> <xsl:for-each select="key('items', @id)[generate-id() = generate-id(key('sub-items', concat(@id, '|', @sub_id))[1])]"> <sub_item> <xsl:copy-of select="@sub_id|@sub_name" /> <xsl:for-each select="key('sub-items', concat(@id, '|', @sub_id))"> <detail> <xsl:copy-of select="@detail_id|@detail_name" /> </detail> </xsl:for-each> </sub_item> </xsl:for-each> </xsl:when> <xsl:otherwise> <xsl:attribute name="sub_items">false</xsl:attribute> </xsl:otherwise> </xsl:choose> </xsl:copy> </xsl:for-each> </i> </xsl:template> </xsl:stylesheet> 
+2
source

All Articles