views:

269

answers:

3

Hi!

I would like to dynamically create XPath expressions in an XSL determined by data in an XML file. (i.e. the XML data is "concatenated" to create an XPath expression).

Example of XML data:

<criteria>
    <criterion>AAA</criterion>
    <criterion>BBB</criterion>
    <criterion>CCC</criterion>
</criteria>

Example of how I would like the XPath expression to look like:

//AAA | //BBB | //CCC

And this dynamic generation needs to be done in an XSL file.

I am fairly new to XSL (and family) and would appreciate some general direction on how to tackle this problem.

Thanks!

Edit: To provide a little more context.... What I need to do is generate an XPath to be used to create a second XSL which transforms an entirely different XML file. I know how to create an XSL from an XSL, I just need to dynamically create XPath expressions. If I could modify variables (which I read from somewhere else I can't) I would just keep concatenating nodes together to form an expression. Then from there I would use the variable name wherever I needed it. Unfortunately I can't do this though.. :(

A: 

You cannot dynamically do that with xsl or xpath. You'll have to use php or whatever (depends on where you're using this). If you have a limited set of nodes, you can use //AAA | //BBB | //CCC and it will work, though this is not really usefull if you want it to work on lots of different nodes.

See here for more info on XPath.

Edit: Jeez, you edited it.

Use /criteria/[criterion=AAA] for that.

Edit again:

the query should be: "/criteria/[criterion=AAA] | criteria/[criterion=BBB] | criteria/[criterion=CCC]". You can probably rephrase this to be more efficient though i forgot how. See this tutorial to see how it's done. Hope that helps.

Jouke van der Maas
I have not worked with php before. Can I embed JavaScript into the xsl file to do this dynamically?
developer
Sorry can you rephrase your edit? How will using /criteria/[criterion==AAA] help me?
developer
+2  A: 

It is possible to create XPath expressions dynamically in XSLT, but no dynamic evaluation of these expressions is possible (at least until XSLT 2.1).

What you want can be done in another way in XSLT.

Here is an example:

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my"
 >
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <my:criteria>
  <criterion>a</criterion>
  <criterion>b</criterion>
  <criterion>c</criterion>
 </my:criteria>

 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="*[name()=document('')/*/my:criteria/*]">
   <xsl:element name="{name()}-{name()}" namespace="{namespace-uri()}">
     <xsl:apply-templates select="node()|@*"/>
   </xsl:element>
 </xsl:template>
</xsl:stylesheet>

when applied on this XML document:

<t>
  <a>
    <x/>
    <y/>
    <b>
      <c/>
      <d/>
    </b>
  </a>
</t>

produces:

<t>
    <a-a>
        <x></x>
        <y></y>
        <b-b>
            <c-c></c-c>
            <d></d>
        </b-b>
    </a-a>
</t>

That is, we only processed in a special way any element (anywhere in the document), whose name was a or b or c.

Dimitre Novatchev
hmm.. ok, this may work for what I am doing. I need to play around with it to see what you are doing. The XPath expressions I have played around with aren't as complex as yours, so I need to learn what you have there first.What I need to do is generate an XPath to be used to create another XSL which transforms an entirely different XML file.. I know how to create an XSL from an XSL, I just need to dynamically create XPath expressions. If I could modify variables (which I read from somewhere else I can't) I would just keep concatenating nodes together to form an expression.
developer
@iHeartGreek: Just provide a small example -- everything can be done with XSLT when the problem is well defined. Don't try to solve it just in your way -- in XSLT as in any functional language the ways to solve problems are quite different from those in imperative programming.
Dimitre Novatchev
@iHeartGreek: There is nothing complex about this solution -- this is really a very short and simple transformation. You are welcome to ask (best in a separate question) how this "works", or about things you want to do with XSLT, but don't know how.
Dimitre Novatchev
+2  A: 

The following will create the sample XPATH string as the value of the "generatedXPATH" variable when run against the sample XML file:

    <xsl:variable name="generatedXPATH">
        <xsl:for-each select="/criteria/criterion">
            <xsl:text>//</xsl:text>
            <xsl:value-of select="." />
            <xsl:if test="position()!=last()">
                <xsl:text> | </xsl:text>
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>

You could use that variable in a stylesheet that produces a stylesheet to construct the template @match values like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xslt="http://www.w3.org/1999/XSL/TransformAlias"
    version="1.0">
    <xsl:namespace-alias stylesheet-prefix="xslt" result-prefix="xsl"/>
    <xsl:output indent="yes" />
    <xsl:template match="/">
        <xsl:variable name="generatedXPATH">
            <xsl:for-each select="/criteria/criterion">
                <xsl:text>//</xsl:text>
                <xsl:value-of select="." />
                <xsl:if test="position()!=last()">
                    <xsl:text> | </xsl:text>
                </xsl:if>
            </xsl:for-each>
        </xsl:variable>

        <xslt:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
            <xslt:template>
                <xsl:attribute name="match">
                    <xsl:value-of select="$generatedXPATH" />
                </xsl:attribute>
                <xsl:comment>Do something special, like select the value of the matched elements</xsl:comment>
                <xslt:value-of select="." />
            </xslt:template>
        </xslt:stylesheet>
    </xsl:template>

</xsl:stylesheet>

When run against the sample XML produces this stylesheet:

<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
<xsl:template match="//AAA | //BBB | //CCC">
<!--Do something special, like select the value of the matched elements-->
<xsl:value-of select="." />
</xsl:template>
</xsl:stylesheet>
Mads Hansen
Don't know why this was downvoted; there are applications for which this approach is entirely appropriate.
Robert Rossney
Thanks, Robert. @Dimitre Novatchev's solution is a more simple/elegant solution, and is nice that it can be run in a single transform. If `criterion` values contained more complex XPATH values rather than simple element name matches, then my solution might be a better fit. It's difficult to tell whether the needed solution is a way to dynamically build the XPATH string(what was specifically asked for), or how to leverage the `criterion` values to perform dynamic selects, or both.
Mads Hansen
I feel like this may be the best solution for me. I'm still playing around with things. My xml file is more complex than the dummy stuff I used here of course.. but I think this might work out. Thanks :)
developer