select all matching nodes using xpath and xslt (wi

2019-05-11 15:41发布

问题:

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

回答1:

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.



回答2:

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>


回答3:

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



回答4:

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!



标签: xml xslt xpath