Using xsl:variable in a xsl:foreach select statement

24,457

Solution 1

Try this:

   <xsl:for-each select="/Rating[Type='1']/Path1/*
                         |
                          /Rating[Type='2']/Path2/*">

Solution 2

Standard XSLT 1.0 does not support dynamic evaluation of xpaths. However, you can achieve your desired result by restructuring your solution to invoke a named template, passing the node set you want to process as a parameter:

<xsl:variable name="Type" select="/Rating/Type"/>
<xsl:choose>
    <xsl:when test="$Type='1'">
        <xsl:call-template name="DoStuff">
            <xsl:with-param name="Input" select="/Rating/Path1/*"/>
        </xsl:call-template>
    </xsl:when>
    <xsl:when test="$Type='2'">
        <xsl:call-template name="DoStuff">
            <xsl:with-param name="Input" select="/Rating/Path2/*"/>
        </xsl:call-template>
    </xsl:when>
</xsl:choose>

...

<xsl:template name="DoStuff">
    <xsl:param name="Input"/>
    <xsl:for-each select="$Input">
        <!-- Do stuff with input -->
    </xsl:for-each>
</xsl:template>
Share:
24,457
Nefariousity
Author by

Nefariousity

Updated on August 19, 2020

Comments

  • Nefariousity
    Nefariousity over 3 years

    I'm trying to iterate through an xml document using xsl:foreach but I need the select=" " to be dynamic so I'm using a variable as the source. Here's what I've tried:

    ...

    <xsl:template name="SetDataPath">
      <xsl:param name="Type" />
    
      <xsl:variable name="Path_1">/Rating/Path1/*</xsl:variable>
      <xsl:variable name="Path_2">/Rating/Path2/*</xsl:variable>
    
      <xsl:if test="$Type='1'">
        <xsl:value-of select="$Path_1"/>
      </xsl:if>
    
      <xsl:if test="$Type='2'">
        <xsl:value-of select="$Path_2"/>
      </xsl:if>
    <xsl:template>
    

    ...

        <!-- Set Data Path according to Type -->
      <xsl:variable name="DataPath">
        <xsl:call-template name="SetDataPath">
          <xsl:with-param name="Type" select="/Rating/Type" />
        </xsl:call-template> 
      </xsl:variable>
    

    ...

    <xsl:for-each select="$DataPath">
    

    ...

    The foreach threw an error stating: "XslTransformException - To use a result tree fragment in a path expression, first convert it to a node-set using the msxsl:node-set() function."

    When I use the msxsl:node-set() function though, my results are blank.

    I'm aware that I'm setting $DataPath to a string, but shouldn't the node-set() function be creating a node set from it? Am I missing something? When I don't use a variable:

    <xsl:for-each select="/Rating/Path1/*">
    

    I get the proper results.

    Here's the XML data file I'm using:

    <Rating>
        <Type>1</Type>
        <Path1>
           <sarah>
              <dob>1-3-86</dob>
              <user>Sarah</user>
           </sarah>
           <joe>
              <dob>11-12-85</dob>
              <user>Joe</user>
           </joe>
        </Path1>
        <Path2>
           <jeff>
              <dob>11-3-84</dob>
              <user>Jeff</user>
           </jeff>
           <shawn>
              <dob>3-5-81</dob>
              <user>Shawn</user>
           </shawn>
        </Path2>
    </Rating>
    

    My question is simple, how do you run a foreach on 2 different paths?

  • Nefariousity
    Nefariousity over 13 years
    Thanks for your help. I added the XML data I'm using. So how would you use 2 different paths in the same foreach statement?
  • Tomalak
    Tomalak over 13 years
    @Nefariousity: No, your question is not "How do you run a foreach on 2 different paths?", because this is the solution you are trying to implement. State your problem, not the solution that you think of to solve it.
  • Nefariousity
    Nefariousity over 13 years
    As shown in my XML file, I have the same attributes in 2 different paths. How do I access those attributes in a foreach loop without having to copy the loop twice, into 2 different if statements?
  • Tomalak
    Tomalak over 13 years
    By using the correct XPath. For example: If you are interested in Type 1 DOB's, you could do <xsl:for-each select="/Rating[@Type='1']/*/*/dob" />. I still think you did not fully describe what you want, exactly.
  • Nefariousity
    Nefariousity over 13 years
    Scratch that, this still wouldn't work. Is it possible to have the select choose 2 different paths given the Type value? IE. select = "if(@Type='1') /Rating/Path1/* else /Rating/Path2/*" ?
  • Tomalak
    Tomalak over 13 years
    No, not like that. What about <xsl:value-of select="/Rating/*[name() = $element_name)]/*">? ($element_name could be a string you set earlier) - You still have not clearly stated what you want to do. :-) I have a feeling that a) your XML sample does not exactly reflect your situation and that b) a "desired output XML" sample is needed to find the best answer to your question.
  • Dimitre Novatchev
    Dimitre Novatchev over 13 years
    Yes, in this case it is possible as @Aohci showed in his solution. In general, XSLT 1.0 provides the <xsl:choose> instruction for conditional actions. In XPath 2.0 (XSLT 2.0) there is an "if" (then else) expression and the code can be written much more ellegantly.
  • Tomalak
    Tomalak over 13 years
    @Dimitre: My strong suspicion is that @Nefariousity tries to solve the problem from the wrong end, and that a more natural was exists to do what he intends.
  • Nefariousity
    Nefariousity over 13 years
    @Dimitre: Thank you so much, this is exactly the type of info I was looking for. I wanted to keep it very elegant (to avoid copying the loop twice). How do you use xslt 2.0 instead of 1.0 in an .xslt file? @Tomalak: I did pose my question in a simpler, representative manner, but the question I needed answered was the same. Thanks for your help.
  • Tomalak
    Tomalak over 13 years
    @Nefariousity: One last time: Can't you simply post the desired XML output instead of asking the same question over and over again? What's so hard about that?
  • Nefariousity
    Nefariousity over 13 years
    This the EXACT solution I was looking for, thank you so much! Elegant and effective =D
  • Nefariousity
    Nefariousity over 13 years
    @Tomalak: Dude, let it go, someone else answered my question. Constantly berating someone isn't very polite. And just to clarify, I was generating an HTML report which looped through an XML data records. Pretty simple, not complex.
  • Tomalak
    Tomalak over 13 years
    @Nefariousity: "Dude", I wasn't being impolite. How could I be, taking nontrivial time trying to answer your question and commenting on it? Impolite is letting me ask for output XML four times straight (even more so when it would have been simple). What I was trying to tell you is that you are very likely shooting yourself in the foot with your approach. Never mind tho', you got your solution.
  • Nefariousity
    Nefariousity over 13 years
    I'm so perplexed by your comments, I have to respond. I'll break it down: I have XML and I want to generate an HTML report with it. Thus I wrote XSL and within said report I want to show this collection of values from my XML. I have 2 paths to the 2 sets of information though, and have to choose 1 or the other depending on a condition. Thus to run through all the records I need a loop, and to access 2 paths I need some type of conditional programming. What's so hard to understand about that? Instead of asking the same question 4 times, try rephrasing it or questioning your assumptions.
  • Tomalak
    Tomalak over 13 years
    @Nefariousity: It starts with that you very likely don't even need a for-each loop for what you want to do. XSLT is not an imperative programming language, and imperative concepts often are the wrong way to go. But it's difficult to tell since you do not show how you want your output to look like. I admit that there is a chance that a for-each loop approach is the only way to solve your problem. Still, I suspect there is a more elegant way to do it, one that as a side-effect removes the necessity to ask your question the way you ask it in the first place.
  • Tomalak
    Tomalak over 13 years
    @Nefariousity: That being said - I didn't mean any offense with my comments, I was trying to help. I know one thing or other about XSLT and was suspecting a misconception on your end, that's why I kept insisting. Then again, you have a solution that works for you, go with it.
  • Nefariousity
    Nefariousity over 13 years
    @Tomalak: Hmm, you raise an interesting question. I was just trying to be brief and concise with my initial post, but I think I see what you mean. My approach to generating an HTML report table with rows of values may be done in another way completely - I hadn't considered that, although I didn't want to necessarily change it either. That aside, thanks for helping, the discussion really helped me understand a few things and sorry for getting a little heated. =)
  • Tomalak
    Tomalak over 13 years
    @Nefariousity: Never mind. ;-) There is a strong chance that we meet again on your next XSLT question anyway, as I'm watching this tag through RSS.