views:

1047

answers:

3

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?

A: 

The node-set() function you mention can convert result tree fragments into node-sets, that's correct. But: Your XSLT does not produce a result tree fragment.

Your template SetDataPath produces a string, which is then stored into your variable $DataPath. When you do <xsl:for-each select="$DataPath">, the XSLT processor chokes on the fact that DataPath does not contain a node-set, but a string.

Your entire stylesheet seems to be revolve around the idea of dynamically selecting/evaluating XPath expressions. Drop that thought, it is neither possible nor necessary.

Show your XML input and specify the transformation your want to do and I can try to show you a way to do it.

Tomalak
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?
Nefariousity
@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.
Tomalak
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?
Nefariousity
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.
Tomalak
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/*" ?
Nefariousity
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.
Tomalak
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.
Dimitre Novatchev
@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.
Tomalak
@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.
Nefariousity
@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?
Tomalak
@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.
Nefariousity
@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.
Tomalak
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.
Nefariousity
@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
@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.
Tomalak
@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. =)
Nefariousity
@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.
Tomalak
+2  A: 

Try this:

   <xsl:for-each select="/Rating[Type='1']/Path1/*
                         |
                          /Rating[Type='2']/Path2/*">
Alohci
Thanks for your help!
Nefariousity
This the EXACT solution I was looking for, thank you so much! Elegant and effective =D
Nefariousity
+1  A: 

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>
markusk
This works great, thanks a lot for helping =D
Nefariousity