pankaj
pankaj

Reputation: 57

How to select nodes dynamically in XSLT

I have an XML file:

<?xml version="1.0" standalone="yes"?>
<Questionnaire>
  <Temp_GridTypeTable_6>
    <Column2>Select Yes/No</Column2>
  </Temp_GridTypeTable_6>
  <Temp_GridTypeTable_1>
    <Column2>Rank 1,2,3</Column2>
  </Temp_GridTypeTable_1>
  <Temp_GridTypeTable_1>
    <Column1>I needed the income</Column1>
    <Column2>Why did you take a job on this project?</Column2>
  </Temp_GridTypeTable_1>
  <Temp_GridTypeTable_1>
    <Column1>Other</Column1>
    <Column2></Column2>
  </Temp_GridTypeTable_1>
  <Temp_GridTypeTable_2>
    <Column2>Select "Yes/No"</Column2>
  </Temp_GridTypeTable_2>
  <Temp_GridTypeTable_2>
    <Column1>No jobs</Column1>
    <Column2>344</Column2>
  </Temp_GridTypeTable_2>
  <Temp_GridTypeTable_3>
    <Column2>Input</Column2>
  </Temp_GridTypeTable_3>
  <Temp_GridTypeTable_3>
    <Column1>Unit</Column1>
    <Column2>123</Column2>
  </Temp_GridTypeTable_3>
</Questionnaire> 

I want to access

<xsl:for-each select="Questionnaire/concat('Temp_GridTypeTablenode_',"1"))>

but this statement is not working.

Upvotes: 1

Views: 1779

Answers (3)

codeape
codeape

Reputation: 100766

You cannot evaluate strings as XPath expressions at run-time with pure XSLT.

You need an extension function that can evaluate an xpath expression at runtime. See for instance the EXSLT project.

On my system, using xsltproc, I can accomplish what you want with:

<!-- load the saxon extensions -->
<xsl:stylesheet version="1.0" xmlns:xx="http://icl.com/saxon" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
...
...
<xsl:for-each 
    select="xx:evaluate(concat('Questionnaire/Temp_GridTypeTable_', '1'))">

Here, I use the evaluate function from the saxon extension library. EXSLT's evaluate function should work the same way.

Must XSLT processors/libraries have some sort of evaluate function built in. See your library's documentation.

Upvotes: 1

Tomalak
Tomalak

Reputation: 338208

This is quite an example of how not to use XML. "Temp_GridTypeTable" and "Column" numbers are data, not structure, they should not be contained in the element names. So why aren't you using something less painful, say:

<Questionnaire>
  <Temp_GridTypeTable type="6">
    <Column num="2">Select Yes/No</Column>
  </Temp_GridTypeTable>
  <Temp_GridTypeTable type="1">
    <!-- ... -->
  <Temp_GridTypeTable>
  <!-- ... -->
</Questionnaire>

That being said, for your current situation, this is needed:

<xsl:for-each select="Questionnaire/*[
  local-name()
  =
  concat('Temp_GridTypeTable_', '1')
]">

For the "less painful" version of the input, this would have been required:

<xsl:for-each select="Questionnaire/Temp_GridTypeTable[@type = 1]">

Despite the fact that the second expression is a lot simpler and more straightforward, it will also perform much better. If you can help it, I recommend to change the input XML.


EDIT: Following up the argument that unfolded itself in the comments, I try to emphasize the difference between the local-name() and name() XPath functions, and where the difference matters:

                            | XML has namespaces  |  XML has no namespaces
----------------------------+---------------------+-----------------------
I care about namespaces     | use `name()`        |  use either function
                            |                     |
don't care about namespaces | use `local-name()`  |  use either function

Generally: If you fall into the "don't care about namespaces" group (most XML novices or casual XML users do), it's okay (sometimes even beneficial) to just always use local-name(). However, be prepared to learn about XML namespaces when the results you get and the results you expect start to diverge. At this point you don't belong to the said group anymore.

If you fall into the "I care about namespaces" group, you don't need this advice anyway. ;-)

Upvotes: 11

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243449

Use:

        Questionnaire/*[name() = concat('', $vSuffix)]

where the variable $vSuffix contains the statically-unknown string -- in this case '1'.

Using local-name() as in Tomalak's answer is both unnecessarily long and imprecise, as in the general case it allows elements with a variety of (possibly unwanted and unexpected) names to be selected, such as:

  • OhMy:Temp_GridTypeTable_1
  • Different:Temp_GridTypeTable_1
  • UnWanted:Temp_GridTypeTable_1

Upvotes: 2

Related Questions