XSLT sort based on attributes

D

Darren Davison

given the following DOM snippet;

<root>
<sub1 foo="4">testing</sub1>
<sub1 foo="0">hello</sub1>
<sub1 foo="0">world</sub1>
<sub1>hello again</sub1>
</root>

I need to transform with XSL to something like;

start foo = 0
hello
world
end foo = 0

start foo = 4
testing
end foo = 4

hello again


The (hopefully clear) constraints are that the attribute foo is
optional and that it may be any whole number if present, though may or
may not be sequential.

Sorting the nodeset based on the value of foo I can manage (!), but I
can't figure out how to output the boundary markers shown. Can anyone
help??

Many TIA!
 
E

Ed Beroset

Darren said:
given the following DOM snippet;

<root>
<sub1 foo="4">testing</sub1>
<sub1 foo="0">hello</sub1>
<sub1 foo="0">world</sub1>
<sub1>hello again</sub1>
</root>

I need to transform with XSL to something like;

start foo = 0
hello
world
end foo = 0

start foo = 4
testing
end foo = 4

hello again


The (hopefully clear) constraints are that the attribute foo is
optional and that it may be any whole number if present, though may or
may not be sequential.

Sorting the nodeset based on the value of foo I can manage (!), but I
can't figure out how to output the boundary markers shown. Can anyone
help??

I think so. Try this:

<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"<xsl:eek:utput method="text" indent="yes"/>


<xsl:template match="/">
<xsl:apply-templates select="root/sub1[@foo]">
<xsl:sort select="@foo" order="ascending"/>
</xsl:apply-templates>
<xsl:apply-templates select="root/sub1[not(@foo)]">
<xsl:sort select="@foo" order="ascending"/>
</xsl:apply-templates>
</xsl:template>


<xsl:template match="sub1">
<xsl:if test="not(@foo=preceding::node()/@foo)">
<xsl:text>


</xsl:text>
<xsl:if test="@foo">
<xsl:text>start foo = </xsl:text>
<xsl:value-of select="@foo"/>
</xsl:if>
</xsl:if>
<xsl:text>
</xsl:text>
<xsl:value-of select="."/>
<xsl:if test="not(@foo=following::node()/@foo)">
<xsl:text>
</xsl:text>
<xsl:if test="@foo">
<xsl:text>end foo = </xsl:text>
<xsl:value-of select="@foo"/>
</xsl:if>
</xsl:if>
</xsl:template>


</xsl:stylesheet>

When I run that on the data you've posted, I get this output:


start foo = 0
hello
world
end foo = 0

start foo = 4
testing
end foo = 4


hello again

That's pretty close to what you wanted, if I understood you correctly.
If not, you can probably see how to modify it. If you don't actually
need the non-foo nodes at the end (and could accept them at beginning of
the output) then you can just elimate the second apply-templates in the
root template and change the select clause of the first one to be just
root/sub1.

Ed
 
D

David Carlisle

This is a standard grouping guestion, see
http://www.jenitennison.com/xslt/grouping

something like

<xsl:key name="x" match="sub1" use="foo"/>

<xsl:template match="root">
<xsl:for-each select="sub1[generate-id()=generate-id(key('x',@foo))]">
<xsl:sort select="@foo" data-type="number"/>
<xsl:if test="@foo">start foo = <xsl:value-of select="@foo"/></xsl:if>
<xsl:for-each select="key('x',@foo)">
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
<xsl:if test="@foo">end foo = <xsl:value-of select="@foo"/></xsl:if>
</xsl:for-each>
</xsl:template>

David
 
D

Darren Davison

David said:
This is a standard grouping guestion, see
http://www.jenitennison.com/xslt/grouping

looks like a pretty useful site - bookmarked!
something like

<xsl:key name="x" match="sub1" use="foo"/>

<xsl:template match="root">
<xsl:for-each select="sub1[generate-id()=generate-id(key('x',@foo))]">
<xsl:sort select="@foo" data-type="number"/>
<xsl:if test="@foo">start foo = <xsl:value-of select="@foo"/></xsl:if>
<xsl:for-each select="key('x',@foo)">
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
<xsl:if test="@foo">end foo = <xsl:value-of select="@foo"/></xsl:if>
</xsl:for-each>
</xsl:template>

Thanks very much for your help David

:)
 
D

Darren Davison

Ed said:
I think so. Try this:

- snip -
That's pretty close to what you wanted, if I understood you correctly.
If not, you can probably see how to modify it. If you don't actually
need the non-foo nodes at the end (and could accept them at beginning of
the output) then you can just elimate the second apply-templates in the
root template and change the select clause of the first one to be just
root/sub1.

Many thanks for your help Ed.
 
?

=?ISO-8859-1?Q?Joachim_Wei=DF?=

Darren said:
David Carlisle wrote:


...

I was thinking about the Problem with the optional @foo which is not
handled in Davids solution.

I tried to solve this but ended up in a bit messy code :-(

Are there more elegant solutions?

Jo

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:key name="fooKey" match="sub1" use="@foo" />
<xsl:eek:utput omit-xml-declaration="yes" method="text" />
<xsl:preserve-space elements="/" />
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>

<xsl:template match="root">
<xsl:for-each select="sub1[generate-id() =
generate-id(key('fooKey',@foo)[1])] | sub1[not(@foo)]">
<xsl:variable name="fooParm" select="@foo" />
start foo=<xsl:value-of select="$fooParm" /><xsl:text>
</xsl:text>
<xsl:for-each select="../sub1[@foo = $fooParm or (not(@foo) and
not($fooParm))]">
<xsl:value-of select="." /><xsl:text>
</xsl:text>
</xsl:for-each>
end foo=<xsl:value-of select="$fooParm" /><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>

<xsl:template match="sub1[@foo]">
<xsl:value-of select="." />
</xsl:template>
</xsl:stylesheet>
 
D

David Carlisle

I was thinking about the Problem with the optional @foo which is not
handled in Davids solution.

my code handles an omitted foo the same as foo="" Given that foo had
numeric values I assumed that "" would not be a legal value and so
no distinction between these cases was necessary.

David
 
D

David Carlisle

I wrote
I was thinking about the Problem with the optional @foo which is not
handled in Davids solution.

my code handles an omitted foo the same as foo="" Given that foo had
numeric values I assumed that "" would not be a legal value and so
no distinction between these cases was necessary.

although in fairness I should say there were some typos so actually my
code wouldn't run at all, also the original poster wanted "" to sort
high whereas by default it sorts low. The code below fixes the typos and
negates the sort key so "" (NaN) sorts high.


<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:key name="x" match="sub1" use="string(@foo)"/>

<xsl:template match="root">
<xsl:for-each select="sub1[generate-id()=generate-id(key('x',string(@foo)))]">
<xsl:sort select="-@foo" data-type="number" order="descending"/>
<xsl:if test="@foo">
start foo = <xsl:value-of select="@foo"/></xsl:if>
<xsl:for-each select="key('x',string(@foo))">
<xsl:text>
</xsl:text>
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:if test="@foo">
end foo = <xsl:value-of select="@foo"/></xsl:if>
</xsl:for-each>
</xsl:template>

</xsl:stylesheet>


$ saxon8 sort.xml sort.xsl
Warning: Running an XSLT 1.0 stylesheet with an XSLT 2.0 processor
<?xml version="1.0" encoding="UTF-8"?>
start foo = 0
hello

world

end foo = 0
start foo = 4
testing

end foo = 4
hello again
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top