Reputation: 1928
I have been trying to put this list on a hierarchy based on the attribute count but I can't get right. At first was trying to do it all at one, the in phases but I keep running into the same problems when using preceding-sibling and following-sibling.
This is what I have
<body>
<element count="2"/>
<element count="2"/>
<element count="2"/>
<element count="2"/>
<element count="4"/>
<element count="4"/>
<element count="6"/>
<element count="4"/>
<element count="2"/>
<element count="4"/>
<element count="2"/>
<element count="4"/>
<element count="6"/>
<element count="6"/>
<element count="4"/>
<element count="2"/>
</body>
This is what I want
<body>
<element count="2"/>
<element count="2"/>
<element count="2"/>
<element count="2">
<element count="4"/>
<element count="4">
<element count="6"/>
</element>
<element count="4"/>
</element>
<element count="2">
<element count="4"/>
</element>
<element count="2">
<element count="4">
<element count="6"/>
<element count="6"/>
</element>
<element count="4"/>
</element>
<element count="2"/>
</body>
Any help will be appreciated, thanks!
Upvotes: 1
Views: 229
Reputation: 122394
Here is a possible XSLT 1.0 solution. The trick here is to use a key to link each element
to its nearest preceding sibling that has a strictly smaller count
value.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*" />
<xsl:output method="xml" indent="yes" />
<xsl:key name="childrenByParent" match="element"
use="generate-id(preceding-sibling::element[@count < current()/@count][1])" />
<xsl:template match="/body">
<body>
<xsl:apply-templates select="key('childrenByParent', '')" />
</body>
</xsl:template>
<xsl:template match="element">
<element>
<xsl:copy-of select="@*" />
<xsl:apply-templates select="key('childrenByParent', generate-id())" />
</element>
</xsl:template>
</xsl:stylesheet>
The starting point key('childrenByParent', '')
works because we need to start from the elements that don't have a "parent", i.e. those for which preceding-sibling::element[@count < current()/@count][1]
selects an empty node set, and the generate-id
of an empty node set is defined to be the empty string.
You ask in a comment what would need to change if the elements could be named anything as long as they have a count
attribute. In that case you'd just need to make a small change to the key definition to make it (a) match any element with a count
and (b) look for the parent with any name rather than just element
:
<xsl:key name="childrenByParent" match="*[@count]"
use="generate-id(preceding-sibling::*[@count < current()/@count][1])" />
and to the second template to give it a more general match
pattern, and to use xsl:copy
instead of hard-coding the element name to create:
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates select="key('childrenByParent', generate-id())" />
</xsl:copy>
</xsl:template>
Upvotes: 0
Reputation: 167696
If you know the count
attributes have values 2,4,6,..
respectively you know the initial count and the difference between counts then it is easy:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:output indent="yes"/>
<xsl:function name="mf:group" as="element(element)*">
<xsl:param name="elements" as="element(element)*"/>
<xsl:param name="level" as="xs:integer"/>
<xsl:param name="step" as="xs:integer"/>
<xsl:for-each-group select="$elements" group-starting-with="element[@count = $level]">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:sequence select="mf:group(current-group() except ., $level + $step, $step)"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="body">
<xsl:copy>
<xsl:sequence select="mf:group(element, 2, 2)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1