How to sort <foo name="..."/>
nodes from $foo using their corresponding node order from $bar?
Partial solution (Mark Veenstra)
<xsl:for-each select="$list-resources">
<xsl:apply-templates select="$building-resources[@name = current()/@name]">
<xsl:with-param name="param" select="$some-value"/>
</xsl:apply-templates>
</xsl:for-each>
New problem (see edit at the end of the question):
How to retrieve the position in the whole sorted node set of each $building-resources node?
TL;DR answer
The
<xsl:sort/>
has an attribute,@data-type
, to specify the type of data in the@select
node. Default is "text". Since I compare numbers, I must set@data-type="number"
. Else, the text comparison fails when it comes to compare 9 and 10.<xsl:sort select="count($bar[@name = current()/@name]/preceding-sibling::*)" data-type="number"/>
In
<xsl:sort/>
,current()
refers to the currently sorted node (not to the<xsl:template/>
current node)
Thanks to michael.hor257k
Complete Answer
Code
<!-- Resource "instances" to order, using $list-resources -->
<xsl:variable name="building-resources"
select="document('building.xml')/building/resource"/>
<result>
<!-- Apply templates to the resource instances -->
<xsl:apply-templates select="$building-resources">
<!-- Sort using the nodes order from the resources list -->
<!-- Don't forget the @data-type: we're comparing NUMBERS not TEXTS -->
<xsl:sort
select="count($list-resources[@name = current()/@name]/preceding-sibling::*)"
data-type="number"/>
</xsl:apply-templates>
</result>
</xsl:template>
<!-- Template to apply for each resource of $building-resources -->
<xsl:template match="ressource">
<!-- position() refers to the node position in $building-resources
because we used $building-resources as @select value
of the apply-templates -->
<output position="{position()}">
<xsl:value-of select="."/>
</output>
</xsl:template>
Inputs
list-resources.xml
<resources>
<resource name="wood"/>
<resource name="stone"/>
<resource name="gold"/>
</resources>
building-resources.xml
<building>
<resource name="stone"/>
<resource name="gold"/>
<resource name="stone"/>
<resource name="wood"/>
<resource name="wood"/>
</building>
Output
<result>
<output position="1">wood</output>
<output position="2">wood</output>
<output position="3">stone</output>
<output position="4">stone</output>
<output position="5">gold</output>
</result>
Original question
Sorting
I have two XML documents:
A list of resources (list-resources.xml
, like a "class" list):
<resources>
<resource name="wood"/>
<resource name="stone"/>
<resource name="gold"/>
</resources>
And resources inside a building (buildings.xml
, like "objects" list):
<building>
<resource name="stone"/>
<resource name="gold"/>
<resource name="stone"/>
<resource name="wood"/>
<resource name="wood"/>
</building>
I want to order the <resource/>
nodes from <building/>
so it matches the <resource/>
order from <resources/>
. This is the desired output:
<result>
<output position="1">wood</output>
<output position="2">wood</output>
<output position="3">stone</output>
<output position="4">stone</output>
<output position="5">gold</output>
</result>
To do so, I have two node-sets in a XSL:
<xsl:variable name="building-resources" select="building/resource"/>
<xsl:variable name="list-resources" select="resources/resource"/>
And I use an XSL apply-templates to treat nodes from $building-resources
:
<xsl:apply-templates select="$building-resources">
<xsl:with-param name="some-param" select="$variable-from-somewhere"/>
</xsl:apply-templates>
Let's say the applied templates is ok. My currently not-sorted result is:
<result>
<output position="1">stone</output>
<output position="2">gold</output>
<output position="3">stone</output>
<output position="4">wood</output>
<output position="5">wood</output>
</result>
Now, I added a <xsl:sort/>
element to sort my nodes from $building-resources
, but I don't know what to put inside its @select
...
<xsl:apply-templates select="$building-resources">
<xsl:sort select="what-to-put-here"/>
<xsl:with-param name="some-param" select="$variable-from-somewhere"/>
</xsl:apply-templates>
I tried the following XPath expression:
count($list-ressources[@name = current()/@name]/preceding-sibling::*)
But inside this XPath,
But this doesn't work, the result is not correctly sorted.
I would mean "the node the current()
refers to the node treated by the <xsl:template/>
where <xsl:apply-templates/>
is.
So, instead of current()
, <xsl:sort/>
process is currently treating" (edit: this is actually exactly what current() does!) inside my XPath brackets $list-resources[]
. How do I do so?
Get the sorted position
Since I simplified the code, I forgot something... Mark Veenstra's solution raised up a problem.
In the applied template (the template that generates the <output/>
node), I use position()
to get the position of the node the template is applied on:
<xsl:template match="resource">
<xsl:variable name="position" select="position()"/>
<output position="{$position}">
<xsl:value-of select="@name"/>
</output>
</xsl:template>
This works well as long as I do only "one" apply-templates (hence the position()
inside the this applied template holds the position of the node in the sorted list). But if I apply Mark's solution (note that current()
inside the <xsl:sort/>
now refers to the node from the <xsl:for-each/>
aka from $liste-resources):
<xsl:for-each select="$list-resources">
<xsl:apply-templates select="$building-resources[@name = current()/@name]">
<xsl:with-param name="param" select="$some-value"/>
</xsl:apply-templates>
</xsl:for-each>
Then the position()
inside the applied template refers to the position of the node in the partial node set ($building-resources[@name = current()/@name]).
How to fix that?
$building-resources
using one of their own attribute (@name
in that other question), but using another node set. So I don't get how these questions could be related. - Xenos