XSL for-each filtering; how to evaluate TWO variable in select? - xslt

<xsl:variable name="filterValue"
>#subject = 'Subject To Filter On'</xsl:variable>
<xsl:for-each select="$Rows[$filterValue]">
It seems to me that the $filterValue is being treated as a literal string rather than being evaluated. How can I make it so that the variable's value is evaluated instead?
Desired result:
<xsl:for-each select="$Rows[#subject = 'Subject To Filter On']">
I apologize if this has been answered before, I don't know much terminology about XSL so I'm having trouble searching online. Thanks.

Dynamic evaluation of XPath expressions is not supported in both XSLT 1.0 and XSLT 2.0.
Usually, there are workarounds.
In this specific case you can have (global/external) parameters:
<xsl:param name="attrName" select="'subject'"/>
<xsl:param name="attrValue" select="'Subject To Filter On'"/>
and then in your code:
<xsl:for-each select="$Rows[#*[name()=$attrName] = $attrValue]">

Related

In XSLT, why do I need to do comparisons with variables instead of with attribute values (in a test expression)

I am parsing a document, with different behavior depending on whether the id attribute is an element of a collection of values ($item-ids in the code below). My question is, why do I need to assign a variable and then compare with that value, like this:
<xsl:template match="word/item">
<xsl:variable name="id" select="#abg:id"/>
<xsl:if test="$item-ids[.=$id]">
<xsl:message>It matches!</xsl:message>
</xsl:if>
</xsl:template>
It seems to be that I should be able to do it like this, though it doesn't work:
<xsl:template match="word/item">
<xsl:if test="$item-ids[.=#abg:id]">
<xsl:message>It matches!</xsl:message>
</xsl:if>
</xsl:template>
This is something I keep forgetting and having to relearn. Can anybody explain why it works this way? Thanks.
To understand XPath, you need to understand the concept of the context node. An expression like #id is selecting an attribute of the context node. And the context node changes inside square brackets.
You don't have to use a variable in this case. Here you can use:
<xsl:template match="word/item">
<xsl:if test="$item-ids[. = current()/#abg:id]">
<xsl:message>It matches!</xsl:message>
</xsl:if>
</xsl:template>
The reason you can't just use $item-ids[. = #abg:id] is that inside the [], you are in the context of whatever is right before the [] (in this case $item-ids), so #abg:id would be treated as $item-ids/#abg:id, which isn't what you want.
current() refers to the current context outside of the <xsl:if> so current()/#abg:id should reflect you the value you want.
I think it's because the line
<xsl:if test="$item-ids[.=#abg:id]">
compares the value of $item-ids to the string '#abg:id' - you need to compare it to the value of #abg:id which is why you need to select that value into the $id variable for the test to work.
Does that help at all?
Edit: I've misunderstood the issue - the other answers are better than mine.

How to use an XSL variable as a condition to evaluate XPath?

I have faced an issue when using a variable as a condition for XPath evaluation. I have the following template which works fine:
<xsl:template name="typeReasonDic">
<xsl:variable name="dic" select="$schema//xs:simpleType[#name = 'type_reason_et']"/>
<!-- do something with the variable -->
</xsl:template>
However, when I change it to look like this:
<xsl:template name="typeReasonDic">
<xsl:param name="choose_dic" select="#name = 'type_reason_et'"/>
<xsl:variable name="dic" select="$schema//xs:simpleType[$choose_dic]"/>
<!-- do something with the variable -->
</xsl:template>
it fails to find the desired node.
What I wish to get is a template with a default value for $choose_dic which can be overriden where necessary.
What am I missing here?
UPD: there is this link I found with the description of what I'm trying to do, but it doesn't seem to work for me.
You can't do this directly in XSLT 1.0 or 2.0 without an extension function. The problem is that with
<xsl:template name="typeReasonDic">
<xsl:param name="choose_dic" select="#name = 'type_reason_et'"/>
<xsl:variable name="dic" select="$schema//xs:simpleType[$choose_dic]"/>
<!-- do something with the variable -->
</xsl:template>
the <xsl:param> will evaluate its select expression a single time in the current context and store the true/false result of this evaluation in the $choose_dic variable. The <xsl:variable> will therefore select either all xs:simpleType elements under the $schema (if $choose_dic is true) or none of them (if $choose_dic) is false. This is very different from
<xsl:variable name="dic" select="$schema//xs:simpleType[#name = 'type_reason_et']"/>
which will evaluate #name = 'type_reason_et' repeatedly, in the context of each xsl:simpleType, and select those elements for which the expression evaluated to true.
If you store the XPath expression as a string you can use an extension function such as dyn:evaluate or the XSLT 3.0 xsl:evaluate element if you're using Saxon.
By doing
<xsl:param name="choose_dic" select="#name = 'type_reason_et'"/>
the XSL engine will try to evaluate "#name = 'type_reason_et'" as an XPath expression, and will assign the RESULT to your variable.
You should use the following variable declaration instead:
<xsl:param name="choose_dic">#name = 'type_reason_et'</xsl:param>
This is the default value, but you can override it when you call your template by using xsl:with-param.
XSLT is not a macro language where you might be able to concatenate your code at run-time from strings and then evaluate them dynamically. So in general for your purpose you would need an extension function to evaluate an XPath expression stored in a string or you need to look into a new XSLT 3.0 features like http://www.saxonica.com/documentation/xsl-elements/evaluate.xml.
What is possible in the scope of XSLT 1.0 or 2.0 is doing e.g.
<xsl:param name="p1" select="'foo'"/>
<xsl:variable name="v1" select="//bar[#att = $p1]"/>
where the param holds a value you compare to other value, for instance those in a node like an attribute or element node.

XPath sorting inline if statement

I've been trying to wrap my head around using XPath and XQuery for this with the help of some previous posts to no avail. Right now I have null child nodes which should just default to ordering at the end of a sort but unfortunately, the sort does not occur at all on these null nodes. As a result I have been trying to find a way to set them to zero during the sorting section. Here is a sample below:
<xsl:for-each select="MyItems/Item">
<xsl:sort select="Order/obj/Number" order="ascending">
I want to do something similar to an inline if statement as part of the sort like in C# below:
foreach(item in MyItems.OrderBy(Order/obj/Exists != false ? Order/obj/Number : 0)
I was using these links: dynamic xpath expression and XSLT transfom with inline if statements to try and understand but I'm still not getting it. Any help is appreciated. I need the solution in XSLT.
Your situation is unclear as you say nothing about the contents of your XML or the nature of your XSLT transform. But it sounds something like you have Item elements with no Order/obj/Number elements to sort on?
I would code that something like this
<xsl:template match="/root">
<xsl:copy>
<xsl:apply-templates select="MyItems/Item[Order/obj/Number]">
<xsl:sort select="Order/obj/Number" />
</xsl:apply-templatesh>
<xsl:apply-templates select="MyItems/Item[not(Order/obj/Number)]" />
</xsl:copy>
</xsl:template>
<xsl:template select="MyItems/Item">
<xsl:copy-of select="current()" />
</xsl:template>
Talking about "null nodes" isn't helpful. It's not a well-defined term. Show us your XML, your desired results and your actual results, and we can help you.
What should happen is that if the select expression in xsl:sort returns an empty sequence/node-set, the effective sort key is a zero-length string, so these items sort before any others (assuming ascending order).

Using dynamic xpath in XSLT

I am using a variable say xpathvar in the XSLT whose value will be supplied at the time of call to XSLT. How can i achieve this?
My XSLT file looks like -
<xsl:param name="xpathvar"/>
<xsl:param name="keyxpath"/>
<xsl:template match="/">
<listofResults>
<xsl:for-each select="$xpathvar">
<keyvalues><xsl:value-of select="xalan:evaluate(substring-before($keyxpath,'||'))"/></keyvalues>
<keyvalues><xsl:value-of select="xalan:evaluate(substring-after($keyxpath,'||'))"/></keyvalues>
</xsl:for-each>
</listofResults>
</xsl:template>
</xsl:stylesheet>
If I mention the variable in the for-each, it throws error. Please guide how can I achieve this.
Thanks!
According to Global xsl:param containing xpath expression string it can't be done purely with plain old XSLT, you need to use the evaluate extension see: Xalan evaluate expression
I dont think this can be done like this you must use xpath so example would be
<template match="Node[name=$xpathvar]" />
<xsl:for-each select="*[name()=$xpathvar]">
</xsl:for-each>
I think it does not let you use the variable directly because it is not a nodeset.
If I mention the variable in the
for-each, it throws error
In XSLT 1.0 (it looks you are using Xalan), you can iterate over node sets, only. So $xpathvar should be an instance of node set data type.
In XSLT 2.0 you can iterate over sequence (including scalar values).
Also, if the string containing the "dynamic" XPath expression is simple enough (only QName test step, maybe positional predicates) this could be done (as is already answered in SO) with standar XSLT.

Assigning parameter value to the xsl: for each

Can anybody who has worked with XSLT help me on this?
I am using XSL version 1.0.
I have declared a parameter in XSL file like:
<xsl:param name="HDISageHelpPath"/>
Now I am assigning the value to this parameter from an asp page . The value which I assign is "document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help". Now I want to assign this parameter to the <xsl for each> like
<xsl:for-each select="msxsl:node-set($HDISageHelpPath)" > (This does not work)
But it does not work. I checked the parameter value by debugging it as below
<debug tree="$HDISageHelpPath">
<xsl:copy-of select="$HDISageHelpPath"/>
</debug>
I'm able to print the value and it seems correct. In fact when I assign the static path ("document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help") by hard-coding it, it works
<xsl:for-each select="document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help"> (This works)
Can anyone please let me know why assigning the parameter to xsl:for-each does not work?
Note: I have referred the site "http://www.dpawson.co.uk/xsl/sect2/N1553.html"
You can't easily evaluate dynamic strings as XPath expressions in XSLT 1.0. They must be hard-coded, normally.
There's EXSLT's dyn:evaluate(), but I doubt you can use that with the MXSML processor.
As an alternative approach, you could either try passing the file path only:
<xsl:param name="HDISageHelpFilePath"/>
<!-- ... -->
<xsl:for-each select="document($HDISageHelpFilePath)/HelpFiles/Help">
</xsl:for-each>
or making placeholder, replacing it with search-and-replace before you load the actual XSL code into the processor (as a string). This is a bit messy and error-prone, but it could give you the possibility to use an actual dynamic XPath expression.
<xsl:for-each select="%HELP_FILE_XPATH%">
</xsl:for-each>
Load the file as text, replace %HELP_FILE_XPATH% with your actual XPath, feed it to the processor. If it loads, you are fine, if it doesn't, your input XPath was malformed.