I have a custom XSLT function that tests a node for a type (which involves string matching, thus it is in its own function), but I am not sure how to correctly use this in an XPath expression. The my:isType() function should test the nodes which are returned by the //cell xpath expression and not the outer node from xsl:when.
<xsl:when test="...">
<my:elem>
<xsl:value-of select="//cell[#parent = #id and my:istype(., 'type')]/#id"/>
</my:elem>
</xsl:when>
Also am I correct, that the #id in //cell[#parent = #id] refers to the outer node?
Edit: include function definition:
<xsl:function name="my:istype" as="xs:boolean">
<xsl:param name="element"/>
<xsl:param name="type" as="xs:string"/>
<xsl:sequence select="contains($element/#style, concat('type=', $type, ';'))"/>
</xsl:function>
As pointed out in the comments one has to use current() to refer to the context node and a . is used to refer to the current cell node in the XPath expression.
<xsl:when test="...">
<my:elem>
<xsl:value-of select="//cell[#parent = current()/#id and my:istype(., 'type')]/#id"/>
</my:elem>
</xsl:when>
Related
I am using a variable in my XSLT to select correct node and then I want to use this variable to retrieve its child nodes:
<xsl:variable name="CorrectNode">
<xsl:choose>
<xsl:when test="$Formula='14'">
<xsl:value-of select="f:node14" />
</xsl:when>
<xsl:when test="$Formula='15'">
<xsl:value-of select="f:node15" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<Revenue>
<xsl:value-of select="msxsl:node-set($CorrectNode)/f1:revenueValue" />
</Revenue>
However, it does not output anything. If I have:
<xsl:value-of select="msxsl:node-set($CorrectNode)" />
or
<xsl:copy-of select="msxsl:node-set($CorrectNode)" />
then the values or nodes are outputted but how to access its children?
This snippet sets the variable to a result tree fragment consisting of one text node, whose value is the string value of the appropriate element. The variable does not refer to the element itself, only its string value.
But you don't need the RTF and node-set function here at all, as you can do it with a straight select using appropriate predicates, since the test expressions are not context-dependent (a test like $Formula = '15' gives the same value regardless of whether the current context is the f:node15 element or its parent):
<xsl:variable name="CorrectNode"
select="f:node14[$Formula = '14'] | f:node15[$Formula = '15']" />
This way the variable is a reference to the original element, and you can navigate from there using XPath.
In a predicate filter, I am trying to figure out how best to explain why and when one must use the XSLT current() function rather than the XPath context node . expression.
The example given at the w3schools XSLT current() Function page helps only a bit as it mostly seems to illustrate the difference without really explaining it. Both Michael Kay's discussion of the current() function in Chapter 13 of "XSLT 2.0 and XPath 2.0, 4th Edition" and Paul Jungwirth's reply to the question Current node vs. Context node in XSLT/XPath? help me understand the difference personally; but each leaves me struggling with how to explain the difference to others rather than merely illustrating the difference using diagrams and code.
If someone will share how they have gone further in explaining this, I would be most grateful.
In XPath, a Location Path expression is relative to the context node, e.g.:
head/title
or
#class
Predicates are used to filter sequences (node-sets in v1). A Predicate Expression is evaluated against each item (or node) in the sequence, using the item as context. e.g.
div[#class] (: #class is relative to div :)
The current() function is available in XSLT (not XPath) to refer to the node currently being processed by the containing xsl:template or xsl:for-each instruction.
http://www.w3.org/TR/xslt#function-current
whit this code I understood
source:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<parent>
<variable>A</variable>
<child>
<variable>B</variable>
</child>
</parent>
xslt:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/parent">
<xsl:value-of select="string('using current')"/>
<xsl:choose>
<xsl:when test="child[current()/variable = 'A']">
<xsl:text>
child[current()/variable = 'A']// -> is true</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>
child[current()/variable = 'A']// -> is false</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="string('
using dot')"/>
<xsl:choose>
<xsl:when test="child[./variable = 'A']">
<xsl:text>
child[./variable = 'A']// -> is true</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>
child[./variable = 'A']// -> is false</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="string('
using dot')"/>
<xsl:choose>
<xsl:when test="child[./variable = 'B']">
<xsl:text>
child[./variable = 'B']// -> is true</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>
child[./variable = 'B']// -> is false</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
output:
using current
child[current()/variable = 'A']// -> is true
using dot
child[./variable = 'A']// -> is false
using dot
child[./variable = 'B']// -> is true
so, you can check that with "current" function you are looking for inside the current node, not inside the child, such as with the dot.
The context node changes with any step and any predicate you add to a path expression while the current node is only changed by XSLT instructions like apply-templates and for-each.
<xsl:variable name="id">
<idNum>0607V45621014F</idNum>
</xsl:variable>
<xsl:variable name="pathId" select="Orders/Order[ORD_Num='$id/idNum']"/>
....not select the idNum
an other..not..
<xsl:variable name="XmlFile" select="YG.xml"/>
<xsl:value-of select="document($XmlFile)/aziende/azienda/ragione_sociale"/>
or other...not..
<xsl:variable name="tagName" select="aziende"/>
<xsl:value-of select="document($XmlFile)/$tagName/azienda/ragione_sociale"/>
1)
<xsl:variable name="pathId" select="Orders/Order[ORD_Num='$id/idNum']"/>
You are saying: An Order element wich has at least one ORD_Num child with string value equal to '$id/idNum'.
Replace with:
<xsl:variable name="pathId" select="Orders/Order[ORD_Num=$id]"/>
Because the string value of $id variable (a Result Tree Fragment as you define) is 0607V45621014F.
Note: It would be better if you define $id as a string like select="'0607V45621014F'". Also, you can't (in XSLT 1.0) do: [ORD_Num=$id/idNum] because / operator can't be apply to a RTF.
2)
<xsl:variable name="XmlFile" select="YG.xml"/>
<xsl:value-of select="document($XmlFile)/aziende/azienda/ragione_sociale"/>
Here, you are saying: Let be $XmlFile a node set with all YG.xml elements childs of context node, etc.
Replace (if you want a document with relative uri YG.xml)
<xsl:variable name="XmlFile" select="'YG.xml'"/>
Note: this does not trought an error because document() is very versatile (It's the few ones that take an object as param)
3)
<xsl:variable name="tagName" select="aziende"/>
<xsl:value-of select="document($XmlFile)/$tagName/azienda/ragione_sociale"/>
This does not work because the right expresion of / must be a path (In XSLT 2.0 can be a function as well).
Replace with:
<xsl:variable name="tagName" select="document($XmlFile)/aziende"/>
<xsl:value-of select="$tagName/azienda/ragione_sociale"/>
Or
<xsl:variable name="tagName" select="'aziende'"/>
<xsl:value-of select="document($XmlFile)/*[name()=$tagName]/azienda/ragione_sociale"/>
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?
Try this:
<xsl:for-each select="/Rating[Type='1']/Path1/*
|
/Rating[Type='2']/Path2/*">
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>
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.
Using XSLT 1.0, how do I check whether the value in the variable exists or not?
I am assigning the value to the variable initially from my XML data and then need to check whether it exits or not:
<xsl:variable name="DOC_TYPE">
<xsl:value-of select="name(./RootTag/*[1])"/>
</xsl:variable>
<xsl:if test="string($DOC_TYPE) = ''">
<xsl:variable name="DOC_TYPE">
<xsl:value-of select="name(./*[1])"/>
</xsl:variable>
</xsl:if>
The above is not working as expected. What I need is if <RootTag> exists in my data then the variable should contain the child node below the <RootTag>. If <RootTag> does not exist then the DOC_TYPE should be the first Tag in my XML data.
Thanks for your response.
You can't re-assign variables in XSLT. Variables a immutable, you can't change their value. Ever.
This means you must decide within the variable declaration what value it is going to have:
<xsl:variable name="DOC_TYPE">
<xsl:choose>
<xsl:when test="RootTag">
<xsl:value-of select="name(RootTag/*[1])" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="name(*[1])" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
A few other notes:
this: './RootTag' is redundant. Every XPath you don't start with a slash is relative by default, so saying 'RootTag' is enough
this: <xsl:value-of select="name(*[1])"/> already results in a string (names are strings by definition), so there is no need to do <xsl:if test="string($DOC_TYPE) = ''"> , a simple <xsl:if test="$DOC_TYPE = ''"> suffices
to check if a node exists simply select it via XPath in a test="..." expression - any non-empty node-set evaluates to true
XSLT has strict scoping rules. Variables are valid within their parent elements only. Your second variable (the one within the <xsl:if>) would go out of scope immediately(meaning right at the </xsl:if>).
Try this
<xsl:variable name="DOC_TYPE">
<xsl:choose>
<xsl:when test="/RootTag"><xsl:value-of select="name(/RootTag/*[1])"></xsl:value-of></xsl:when>
<xsl:otherwise><xsl:value-of select="name(/*[1])"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
It only exists if you have assigned it. There's no reason to test it for existence.
See also here