XSLT sort by tag name and attribute value
I'm a noob with XSLT, so please excuse my ignorance... I'm trying to sort a simple XML file by attribute value and tag name, but I struggle in accessing the value of the attribute. Here is a complete example:
<a>
<b attribute="e"></b>
<b attribute="b"></b>
<d attribute="a"></d>
<c></c>
</a>
And the expected result is:
<a>
<b attribute="b"></b>
<b attribute="e"></b>
<c></c>
<d attribute="a"></d>
</a>
Here is my attempt to solve this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="@*">
<xsl:sort select="."/>
开发者_StackOverflow </xsl:apply-templates>
<xsl:apply-templates select="node()">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
And this obviously don't work at all...
In the above example I want to sort the b tag by their attribute value but as you can see the d tag is not sorted by attribute value because it's another tag name...
I wonder if this is possible using XSLT... Do you have an idea?
Thanks in advance.
UPDATE----------------------
I tried andyb solution that seems to work fine and looks pretty simple, but I have another issue with this solution.
Let's say I have this XML:
<a>
<b attribute="e" optionalAttr="fg"></b>
<b attribute="b"></b>
<d attribute="a"></d>
<c></c>
</a>
I added an optional parameter for the b tag. Applying andyb solution the optional parameter will be ignored, because it is not matched in the template. Here is the result:
<a>
<b attribute="b"></b>
<b attribute="e"></b>
<c></c>
<d attribute="a"></d>
</a>
Instead of the following which is what I expect:
<a>
<b attribute="b"></b>
<b attribute="e" optionalAttr="fg"></b>
<c></c>
<d attribute="a"></d>
</a>
Do you have any idea? Thanks in advance.
You can use multiple xsl:sort
instructions, for example:
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*">
<xsl:sort select="name()" />
<xsl:sort select="@*" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
and since the default data-type is "text" and the default order is "ascending" this gives the desired output.
Edit
This is strange, because for the following XML:
<a>
<b attribute="e" optionalAttr="fg"></b>
<b attribute="b"></b>
<d attribute="a"></d>
<c></c>
</a>
and the XSL above, I get this result:
<a>
<b attribute="b"></b>
<b attribute="e" optionalAttr="fg"></b>
<c></c>
<d attribute="a"></d>
</a>
This includes the desired optional attribute but the order is different to the XML in the edited question (<c></c>
is in a different position).
This XSLT 2.0 transformation performs sorting by element name and multiple attributes nameand value:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*">
<xsl:sort select="name()" />
<xsl:sort select="my:attributeScore(.)" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:function name="my:attributeScore" as="xs:string">
<xsl:param name="pThis" as="node()"/>
<xsl:variable name="vScore">
<xsl:for-each select="$pThis/@*">
<xsl:sort select="name()"/>
<xsl:value-of select="concat(name(),'+',.)"/>
</xsl:for-each>
<xsl:text>|</xsl:text>
</xsl:variable>
<xsl:sequence select="string-join($vScore, '')"/>
</xsl:function>
</xsl:stylesheet>
when applied on this XML document (the provided one, but added multiple attributes):
<a>
<b attribute="e" x="y"></b>
<b attribute="e" x="x"></b>
<b attribute="b"></b>
<d attribute="a"></d>
<c></c>
</a>
the correctly sorted result is produced:
<a>
<b attribute="b"/>
<b attribute="e" x="x"/>
<b attribute="e" x="y"/>
<c/>
<d attribute="a"/>
</a>
I just saw this question and as I'm new at xpath and XSL thought i'll give it a shot.
I seem to have come up with a completely different solution.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="* | @*">
<xsl:sort select="not(@*)" order="ascending" data-type="number"/>
<xsl:sort select="@*"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="*/@*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()[not(string-length(normalize-space()))]"/>
<xsl:template match="*/text()[normalize-space()]">
<xsl:value-of select="normalize-space()"/>
</xsl:template>
It does depend on how you want the attributes to be order...
Sample 1:
<?xml version="1.0" encoding="utf-8" ?>
<a>
<b attribute="e" ></b>
<b attribute="b" ></b>
<c></c>
<d attribute="a"></d>
</a>
Result 1
<a>
<d attribute="a" />
<b attribute="b" />
<b attribute="e" />
<c />
</a>
Sample 2
<?xml version="1.0" encoding="utf-8" ?>
<a>
<b attribute="e" ></b>
<b attribute="b" optionalAttr="fg"></b>
<c></c>
<d attribute="a"></d>
</a>
Result 2
<?xml version="1.0" encoding="utf-8"?>
<a>
<d attribute="a" />
<b attribute="b" optionalAttr="fg" />
<b attribute="e" />
<c />
</a>
Just wondering if anyone could see anything wrong with this approach?
Thank you in advanced
精彩评论