I have a particular problem that I can't seem to solve.
Is it possible to select all nodes using xpath and xslt without the use of additional templates or for-each?
Example xml:
<aaa id="11">
<aaa id="21"></aaa>
<bbb id="22">
<aaa id="31"></aaa>
<bbb id="32"></bbb>
<ccc id="33"></ccc>
<ddd id="34"></ddd>
<ddd id="35"></ddd>
<ddd id="36"></ddd>
</bbb>
<ccc id="23"></ccc>
<ccc id="24"></ccc>
</aaa>
A user has the ability to type in an xpath expression through a form, such as:
//aaa/bbb/ddd/@id
The user would expect to receive the ids from:
<ddd id="34"></ddd>
<ddd id="35"></ddd>
<ddd id="36"></ddd>
Outputting:
34 35 36
The only ways I have been able to achieve this is by using additional templates and for-each:
For-each way:
<xsl:template match="/">
<html>
<body>
<xsl:for-each select="//aaa/bbb/ddd">
<tr>
<td>
<xsl:value-of select="@id" />
</td>
</tr>
</xsl:for-each>
</body>
</html>
</xsl:template>
Additional template way:
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="//aaa/bbb/ddd">
<xsl:value-of select="@id"/>
</xsl:template>
Each of these examples require extra work to detach the @id from the original expression. I would like to use the user inputted expression as is, and just plug it in somewhere.
I have tried the following, which I thought would select all, but it only returns the first instance:
<xsl:template match="/">
<html>
<body>
<xsl:value-of select="//aaa/bbb/ddd/@id"/>
</body>
</html>
</xsl:template>
Is there a solution to my problem (i.e. a way to just plug in the user inputted expression as is?)
EDIT: Note - I need a solution that will work with any xpath expression given by the user.. no matter how complex.
Let me know if you need any further clarification.. I tried my best to explain it, but maybe I didn't do that very well.. Thank you in advance for your patience!
Thanks! :)
With this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="node-set" select="/aaa/bbb//ddd/@id"/>
<xsl:template match="/">
<html>
<body>
<table>
<tr>
<th>Type</th>
<th>Name</th>
<th>Value</th>
</tr>
<xsl:apply-templates select="$node-set" mode="result"/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="@*|node()" mode="result">
<tr>
<td>
<xsl:choose>
<xsl:when test="self::*">Element</xsl:when>
<xsl:when test="self::text()">Text</xsl:when>
<xsl:when test="self::comment()">Comment</xsl:when>
<xsl:when test="self::processing-instruction()">PI</xsl:when>
<xsl:when test="count(.|/)=1">Root</xsl:when>
<xsl:when test="count(.|../@*)=count(../@*)">Attribute</xsl:when>
<xsl:when test="count(.|../namespace::*)=count(../namespace::*)">Namespace</xsl:when>
</xsl:choose>
</td>
<td>
<xsl:value-of select="name()"/>
</td>
<th>
<xsl:value-of select="."/>
</th>
</tr>
</xsl:template>
</xsl:stylesheet>
Result:
<html>
<body>
<table>
<tr>
<th>Type</th>
<th>Name</th>
<th>Value</th>
</tr>
<tr>
<td>Attribute</td>
<td>id</td>
<th>34</th>
</tr>
<tr>
<td>Attribute</td>
<td>id</td>
<th>35</th>
</tr>
<tr>
<td>Attribute</td>
<td>id</td>
<th>36</th>
</tr>
</table>
</body>
</html>
Note: It would be an error if $node-set isn't a node set.
Edit: Added complete node type test in order to prove that this stylesheet works with any XPath expression wich eval to a node set.
Edit 2: Added template/@mode
in order to not miss root.
In XSLT 1.0 this is probably the simplest way to produce the desired result, without "extra work to detach the @id from the original expression":
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="node()|@*">
<xsl:apply-templates select="node()|@*"/>
</xsl:template>
<xsl:template match="aaa/bbb/ddd/@id">
<xsl:value-of select="concat(., ' ')"/>
</xsl:template>
</xsl:stylesheet>
Here is an XPath 2.0 one-liner:
//aaa/bbb/ddd/@id/string(.)
The same wrapped in XSLT 2.0 stylesheet:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select="//aaa/bbb/ddd/@id/string(.)"/>
</xsl:template>
</xsl:stylesheet>
What happens when you use <xsl:copy-of>
instead of <xsl:value-of>
?
To my knowledge/experience with XSL, @id
is very context specific, meaning to access that information you must first attain the proper context as you showed above through select="foo"
or template match="foo"
. I've run into this problem in my own work previously, and these were the only solutions I was able to find.
However, there are certainly more knowledgeable XSL people than I, so perhaps there is some fancy way I don't know about - if so I'd love to hear it!