Snooze to nested structure based on attribute value using XSLT

I have a flat structured XML file as shown below:

<rs> <r id="r1" lev="0"/> <r id="r2" lev="1"/> <r id="r3" lev="0"/> <r id="r4" lev="1"/> <r id="r5" lev="2"/> <r id="r6" lev="3"/> <r id="r7" lev="0"/> <r id="r8" lev="1"/> <r id="r9" lev="2"/> </rs> 

which I need to convert to nested. A rule is something, all r[number(@lev) gt 0] must be nested in r[number(@lev) eq 0] . And the result will be something like this:

 <rs> <r id="r1"> <r id="r2"/> </r> <r id="r3"> <r id="r4"> <r id="r5"> <r id="r6"/> </r> </r> </r> <r id="r7"> <r id="r8"> <r id="r9"/> </r> </r> </rs> 

I tried the following conversion:

 <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0"> <xsl:output indent="yes"/> <xsl:template match="/"> <rs> <xsl:apply-templates select="node()|@*"/> </rs> </xsl:template> <xsl:template match="r"> <xsl:variable name="lev" select="number(@lev)" as="xs:double"/> <r> <xsl:copy-of select="@id"/> <xsl:apply-templates select="following-sibling::r[not(number(@lev) eq $lev) and count(preceding-sibling::r[number(@lev) eq $lev]) eq 1]"/> </r> </xsl:template> </xsl:stylesheet> 

But this does not give me the desired result. Recognizing my coding error or any other approach to doing the work, we greatly appreciate it.

+4
source share
3 answers

Dimitre usually answers questions using XSLT 1.0, unless otherwise requested. This may be a good guess, but I think it's worth noting that XSLT 2.0 is now quite widely available and used, and that code for grouping problems in XSLT 2.0 is much simpler (it may not always be much shorter, but it is much more readable) . Unlike Dimitre, I donโ€™t have the time or desire to give excellent complete and proven solutions for each issue, but if you want to see XSLT 2.0 solution for this problem, then in an article I wrote a few years ago, there is:

http://www.saxonica.com/papers/ideadb-1.1/mhk-paper.xml

Find the recursive template name = "process level".

+1
source

This conversion is :

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:key name="kRByLevelAndParent" match="r" use="concat(generate-id(preceding-sibling::r [not(@lev >= current()/@lev)][1]), @lev )"/> <xsl:template match="/*"> <rs> <xsl:apply-templates select="key('kRByLevelAndParent', '0')"/> </rs> </xsl:template> <xsl:template match="r"> <r id="{@id}"> <xsl:apply-templates select= "key('kRByLevelAndParent', concat(generate-id(), @lev+1) )"/> </r> </xsl:template> </xsl:stylesheet> 

when applied to the provided XML document:

 <rs> <r id="r1" lev="0"/> <r id="r2" lev="1"/> <r id="r3" lev="0"/> <r id="r4" lev="1"/> <r id="r5" lev="2"/> <r id="r6" lev="3"/> <r id="r7" lev="0"/> <r id="r8" lev="1"/> <r id="r9" lev="2"/> </rs> 

creates the desired, correct result:

 <rs> <r id="r1"> <r id="r2"/> </r> <r id="r3"> <r id="r4"> <r id="r5"> <r id="r6"/> </r> </r> </r> <r id="r7"> <r id="r8"> <r id="r9"/> </r> </r> </rs> 

Explanation

Positional grouping using a composite key - for all its โ€œchildrenโ€, the element is the first previous relationship, so its lev attribute is less than the corresponding lev attribute.

+2
source

Since I need to apply the conversion in temporary variables, using xsl:key would not help. And if I need to use a Dimitre solution , I had to modify the existing code.

And, obviously, this was my mistake, about which I did not talk very much about this issue.

From the link in //programlisting[contains(.,'xsl:template name="process-level"')] provided by Dr. Kay I came to the conclusion that maybe someone else can use it later:

Style sheet

 <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs"> <xsl:output indent="yes"/> <xsl:template match="/*"> <rs> <xsl:call-template name="process-level"> <xsl:with-param name="context" select="r"/> <xsl:with-param name="level" select="0"/> </xsl:call-template> </rs> </xsl:template> <xsl:template name="process-level"> <xsl:param name="context" required="yes" as="element()*"/> <xsl:param name="level" as="xs:double"/> <xsl:for-each-group select="$context" group-starting-with="*[number(@lev) eq $level]"> <xsl:element name="{name()}"> <!--<xsl:variable name="position" as="xs:double"> <xsl:number level="any" count="*[starts-with(local-name(), 'r')]"/> </xsl:variable>--> <xsl:copy-of select="@id"/> <xsl:call-template name="process-level"> <xsl:with-param name="context" select="current-group()[position() != 1]"/> <xsl:with-param name="level" select="$level + 1"/> </xsl:call-template> </xsl:element> </xsl:for-each-group> </xsl:template> </xsl:stylesheet> 

XML input

 <rs> <r id="r1" lev="0"/> <r id="r2" lev="1"/> <r id="r3" lev="0"/> <r id="r4" lev="1"/> <r id="r5" lev="2"/> <r id="r6" lev="3"/> <r id="r7" lev="0"/> <r id="r8" lev="1"/> <r id="r9" lev="2"/> </rs> 

And the result

 <rs> <r id="r1"> <r id="r2"/> </r> <r id="r3"> <r id="r4"> <r id="r5"> <r id="r6"/> </r> </r> </r> <r id="r7"> <r id="r8"> <r id="r9"/> </r> </r> </rs> 
0
source

All Articles