tags:

views:

78

answers:

4

Hi!

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! :)

A: 

What happens when you use <xsl:copy-of> instead of <xsl:value-of>?

Drew Wills
No, that does not give desired results. Thanks for offering your advice though.
developer
A: 

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!

nearlymonolith
Yea, I am concerned there is no other way.. but I will wait a couple days to see if someone else does in fact know a "fancy" way to do it. Thank you so much for your response either way! :D
developer
+1  A: 

With this stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <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.

Alejandro
I like this solution. It works well with the example expression I used. But what happens if the xpath expression is not exactly as stated in my example? such as /aaa/bbb//ddd/@id? I could not get this expression to work. (which should result in same output given the xml data) I need a solution that will work with any xpath expression given by the user.. no matter how complex. I did not express this in my question, so I will edit it to include this. Sorry for not being clear enough in my original question.
developer
@iHeartGreek: You have two problems here. First, there is not standar dynamic evaluation feature. So, you have to rely on extensions or write your own XPath parser with XSLT (big task!). Two other ways: two step transformation (first, add xpath string to the stylesheet; second, use the stylesheet), use stylesheet param. The second problem is that `$node-set` must be a node set: there is no way in XSLT 1.0 to test if an expression result in a node set or some other type. My templates are examples. You can add the other node test in order to get all node types (text nodes, comment, etc.)
Alejandro
@iHeartGreek: Also, my stylesheet works fine with `/aaa/bbb//ddd/@id`
Alejandro
+1  A: 

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"&gt;
 <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"&gt;
 <xsl:output method="text"/>

 <xsl:template match="/">
   <xsl:sequence select="//aaa/bbb/ddd/@id/string(.)"/>
 </xsl:template>
</xsl:stylesheet>
Dimitre Novatchev
I like the first answer.. very simple to use. But if I change the xml data a bit, and change my xpath to aaa/bbb/ddd/@id - the template will still match all possible paths instead of treating aaa as the root. Is there just something wrong with the xpath? How do i specify that aaa should be the root? Also.. I am curious about xpath 2.0 and xslt 2.0 - I have never used that version. Maybe I will save that for a new question :)
developer
@iHeartGreek: In XPath/XSLT if you want to match the root element in a pattern you must write `/aaa/bbb/ddd/@id` (Note the first `/`). Also note that with Dimitre first answer the XPath expression is used as a pattern. So, there are restrictions for what you can express. In particular, only child and attribute axis are allowed (into predicates, you could write any axis)
Alejandro
@Dimitre: I now have tried using the third solution listed (100% as is), but I receive an error saying the sequence element is unknown.
developer
@iHeartGreek: Most probably you are not running an XSLT 2.0 processor. With Saxon 9.0.0.4 I get the correct result: 34 35 36
Dimitre Novatchev
@iHeartGreek: re: the first solution: just change: `<xsl:template match="aaa/bbb/ddd/@id">` to `<xsl:template match="/aaa/bbb/ddd/@id">`
Dimitre Novatchev