xsl:sort by variable
I've been looking at thr开发者_运维百科eads about sorting XML using <xsl:sort>
and variables, and still can't get my sorting to work. Here's some XML structure for context:
<records>
<record>
<contributors>
<authors>
<author>Author 1</author>
<author>Author 2</author>
</authors>
</contributors>
<titles>
<title>I'm a Title!</title>
<secondary-title></secondary-title>
</titles>
<dates>
<year>1901</year>
</dates>
</record>
<record>...</record>
<record>...</record>
</records>
And here's the relevant XSL:
<xsl:variable name="sortby"
select="contributors/authors/author[1]" as="element()*"/>
<xsl:for-each select="//record">
<xsl:sort select="$sortby" order="ascending"/>
[a bunch of HTML to render the records as a bibliography]
</xsl:for-each>
If I copy the string in the variable's "select" attributes and paste it into sort, like this:
<xsl:sort select="contributors/authors/author[1]" order="ascending">
then it works. With the variable, it doesn't. I tried it both with and without as="element()*"
-- Help?
It is not possible in general to perform dynamic evaluation of XPath expressions -- neither in XSLT/Xpath 1.0 or in XSLT/Xpath 2.0.
This said, one can always implement sorting guided by variables, if there are some limitations on their contents.
Here is an example that solves your specific problem and a class of similar problems:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pSortName" select="'authors'"/>
<xsl:param name="pSortPosition" select="1"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="records">
<records>
<xsl:apply-templates>
<xsl:sort select=
".//*[name()=$pSortName]/*
[position()=$pSortPosition]"/>
</xsl:apply-templates>
</records>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on this XML document:
<records>
<record>
<contributors>
<authors>
<author>X.Y.Z</author>
<author>A.B.C</author>
</authors>
</contributors>
<titles>
<title>Title B</title>
<secondary-title>Title AB</secondary-title>
</titles>
<dates>
<year>1901</year>
</dates>
</record>
<record>
<contributors>
<authors>
<author>T.U.V</author>
<author>D.E.F</author>
</authors>
</contributors>
<titles>
<title>Title A</title>
<secondary-title>Title BA</secondary-title>
</titles>
<dates>
<year>2001</year>
</dates>
</record>
</records>
the wanted, correct result (the records sorted by first author) is produced:
<records>
<record>
<contributors>
<authors>
<author>T.U.V</author>
<author>D.E.F</author>
</authors>
</contributors>
<titles>
<title>Title A</title>
<secondary-title>Title BA</secondary-title>
</titles>
<dates>
<year>2001</year>
</dates>
</record>
<record>
<contributors>
<authors>
<author>X.Y.Z</author>
<author>A.B.C</author>
</authors>
</contributors>
<titles>
<title>Title B</title>
<secondary-title>Title AB</secondary-title>
</titles>
<dates>
<year>1901</year>
</dates>
</record>
</records>
If we change the parameters to:
<xsl:param name="pSortName" select="'authors'"/>
<xsl:param name="pSortPosition" select="2"/>
then the transformation sorts using as sort-key the second author
.
If we change the parameters to:
<xsl:param name="pSortName" select="'titles'"/>
<xsl:param name="pSortPosition" select="1"/>
then the transformation sorts using as sort-key the titles/title
element.
If we change the parameters to:
<xsl:param name="pSortName" select="'titles'"/>
<xsl:param name="pSortPosition" select="2"/>
then the transformation sorts using as sort-key the titles/secondary-title
element.
Do note: Here we assume that there will be a unique descendent of any element being sorted, whose name is equal to the value specified in pSortName
. We also assume that this element has children elements and pSortPosition
specifies the position of the child to be used as a sort key.
Two other solutions that haven't been mentioned:
(a) Many processors have an extension, called something like dyn:evaluate() that evaluates an XPath expression supplied in the form of a character string
(b) In some environments it's feasible to modify the stylesheet (using an XSLT transformation of course) before executing it. This allows you to insert whatever XPath expression you need. In XSLT 2.0 you can write the sort key as select="my:sort(.)"
, and then define my:sort() in a separate xsl:included stylesheet module.
Another related option which I've seen is to use an external entity: select="&sortkey;", where the entity reference can be redirected programmatically to a different XPath expression using an EntityResolver registered with the XML parser.
You can't use a variable in the select=
portion of an xsl:sort
element. The XSLT specification states:
xsl:sort
has aselect
attribute whose value is an expression. For each node to be processed, the expression is evaluated with that node as the current node and with the complete list of nodes being processed in unsorted order as the current node list.
The expression is evaluated only once, not twice as you seem to expect. Your expression $sortby
is evaluated once to result in something that is the same every time (the actual value depends on what was the current node at the time the xsl:variable
assignment ran). Therefore, the sort does not change the order of the selected elements.
You must use a specific expression as the sort criteria, as you have discovered.
精彩评论