I had the most frustrating problem recently when I was trying to
extend Tim's locator package - I wanted to build up a dynamic XPath
select statement based on QueryString values, for example if one
value was present then select nodes based on the value but if
another QueryString value was present select nodes based on the
value of that.
So I tried what I thought was very simple.. I create a
variable called $thesearch and build up the XPath statement within
the variable like so
- <xsl:variable
name="thesearch"
>
-
<xsl:text>/root//node
[string-length(./data [@alias = 'distance']) >
0 and number(./data [@alias = 'distance']) <
$radius</xsl:text>
- <xsl:if
test="string(umbraco.library:Request('area'))
!= '' ">
- <xsl:text> and
string(./data [@alias ='eventCounty']) =
</xsl:text>
-
<xsl:text>'</
xsl:text>
- <xsl:value-of
select="umbraco.library:Request('area')"
/>
-
<xsl:text>'</
xsl:text>
- </xsl:if>
-
<xsl:text>]</
xsl:text>
-
</xsl:variable>
And then just use the variable in my select statement
- <xsl:for-each
select="$thesearch"
>
Yep simple… BUT… Nooooooooooooooo!!!!!!!!!!! God
damn XSLT…. Grrrrrrrr [more]
According to XSLT this variable is a 'String' and NOT 'XPath'
and the 'String' would have to be evaluated as valid XPath (Even if
I take the completed string, put it directly in the foreach select
and it works??!!)… Anyway who am I to question this wonderful
language (Said in a sarcastic tone). As you might have
guessed this flummoxed me slightly and after hours on the Umbraco
forum and Googling various things, I turned to the XSLT Jedi (Doug) & his
rather clever wife. They created a nice little helper method
in C# for me which I could insert directly into the XSLT file like
so
Place the below after the closing template tag
</xsl:stylesheet>
- <msxml:script
language="C#"
implements-prefix="ps"
>
- <![CDATA[
- public string evaluateXpath(XPathNodeIterator nodes,
string xpath){
- /* this is intended for a SINGLE node to be passed in,
although the type is XPathNodeIterator; if multiple
- nodes are passed in, only the first one will be evaluated
*/
- try{
- while (nodes.MoveNext()){
- XPathNavigator n = nodes.Current as
XPathNavigator;
- return n.Evaluate(xpath).ToString();
- }
- }catch{
- return null;
- }
- return null;
- }
- ]]>
-
</msxml:script>
You need to make sure you have added a namespace in the top of
the XSLT
-
xmlns:ps="urn:anynamehere:xslt"
And then add the 'ps' to the 'exclude-result-prefixes' attribute
like so
- exclude-result-prefixes="ps
msxml umbraco.library Exslt.ExsltCommon Exslt.ExsltDatesAndTimes
Exslt.ExsltMath Exslt.ExsltRegularExpressions Exslt.ExsltStrings
Exslt.ExsltSets"
Now we can use this method on our dynamic 'String' to turn it
into valid 'XPath' like so
- <!-- ############ Build up the
xpath portions
- (note: any variables or umbraco.library functions or
other extensions must be inserted with xsl:value-of)
- ############## -->
- <xsl:variable
name="distanceXpath"
>
- string-length(./data [@alias = 'distance'])
> 0
- and number(./data [@alias = 'distance']) <
<xsl:value-of
select="$radius"
/>
- <!-- just keep adding more conditions
here to build up your xpath -->
-
</xsl:variable>
- <!-- ############ End xpath
portions ############## -->
Now we use the new method to process the xpath statement
- <!-- once you've got the xpath string,
use ps:evaluateXpath and check that it returns
- 'True' to actually process the xpath statement
-->
- <xsl:variable
name="searchResults"
select="/root//node
[ps:evaluateXpath(., $distanceXpath) =
'True']"/>
And finally we can use the final variable as you normally
would
- <xsl:for-each
select="$searchResults"
>
-
</xsl:for-each>
In my final code I actually had to do a date compare so the
method had to be modified to use date comparisons - But this will
help anyone wanting to build up and dynamic XPath select
statement