Using xsl:evaluate to evaluate value of XPath - xslt

I am trying to use xsl:evaluate to get the value referecend by an XPath, but can't get it to work.
Here is an example (it would not make sense to use xsl:evaluate for this in a real application).
So what I expected in my code was to evaluate the XPath expression defined and store the value of that referecend attribute in a variable to be used in my code.
There must be something I don't understand in the way to use xsl:evaluate.
Input XML
<?xml version="1.0" encoding="UTF-8"?>
<Country name="USA">
<State name="California">
<City name="Los Angeles"/>
<City name="San Francisco"/>
</State>
</Country>
Expected output
<?xml version="1.0" encoding="UTF-8"?>
<Country name="USA">
<State name="California">
<City name="USA"/>
<City name="San Francisco"/>
</State>
</Country>
My XSLT code
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#name[.='Los Angeles']">
<xsl:variable name="xPathString" select="'ancestor::Country/#name'"/>
<xsl:variable name="input" as="xs:string">
<xsl:evaluate xpath="$xPathString"/>
</xsl:variable>
<xsl:attribute name="name" select="$input"/>
</xsl:template>
</xsl:stylesheet>
Error message I am getting
Error at char 0 in expression in xsl:evaluate/#xpath on line 19 column 40 of Test.xsl:
XPDY0002 Dynamic error in expression {ancestor::Country/#name} called using xsl:evaluate.
Found while atomizing the value of variable $input
In template rule with match="#name[xs:string(.) eq "Los Angeles"]" on line 15 of Test.xsl
invoked by xsl:apply-templates at file:/C:/Users/ck3503/Documents/Repositories/outilscrea/specifications/profil/xslt/Test.xsl#11
In template rule with match="(comment()|(processing-instruction()|(element()|text())))" on line 9 of Test.xsl
invoked by xsl:apply-templates at file:/C:/Users/ck3503/Documents/Repositories/outilscrea/specifications/profil/xslt/Test.xsl#11
In template rule with match="(comment()|(processing-instruction()|(element()|text())))" on line 9 of Test.xsl
invoked by xsl:apply-templates at file:/C:/Users/ck3503/Documents/Repositories/outilscrea/specifications/profil/xslt/Test.xsl#11
In template rule with match="(comment()|(processing-instruction()|(element()|text())))" on line 9 of Test.xsl
invoked by built-in template rule (text-only)
Dynamic error in expression {ancestor::Country/#name} called using xsl:evaluate. Found while atomizing the value of variable $input

I think you want
<xsl:variable name="input" as="xs:string">
<xsl:evaluate xpath="$xPathString" context-item="."/>
</xsl:variable>

Related

How to not touch a record if text exist in an XML field but process the record in no text exist using XSLT 2

Using XSLT 2 how can I skip and not touch a record if a field contains text, in this case a date? I want to only process all the record that don't have a <SurveyDate> and don't touch record that already have a <SurveyDate>.
I tried using a choose statement with a test of "not(SurveyDate/text())" but this is not working. here is my complete XSL code:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:lookup="lookup" xmlns:exsl="http://exslt.org/common" exclude-result-prefixes="lookup exsl">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" encoding="utf-8" media-type="xml/plain" />
<xsl:strip-space elements="*" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<xsl:template match="Sub">
<!-- This is the final output -->
<xsl:choose>
<xsl:when test="not(SurveyDate/text())">
<xsl:if test= "count(Request/Phase/Status) = count(Request/Phase/Status[matches(. , 'Sup|Ser|Adm|Can')])">
<Request>
<xsl:copy-of select="Request/Code"/>
<SurveyDate>
<xsl:value-of select="format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H1]:[m01]:[s01]')"/>
</SurveyDate>
</Request>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<!-- just for testing remove when done -->
<Test>Do nothing</Test>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
And this is my test XML data.
<?xml version='1.0' encoding='UTF-8'?>
<document>
<businessobjects>
<Sub>
<Code>1.02</Code>
<Status>UsrWorkOrderCancelled</Status>
<Request>
<Code>1.00</Code>
<Description>Test 1</Description>
<SurveyDate>2022-11-02T22:55:55</SurveyDate>
<Phase>
<Code>1.01</Code>
<Status>UsrWorkOrderSupervisorApproved</Status>
</Phase>
<Phase>
<Code>1.02</Code>
<Status>UsrWorkOrderCancelled</Status>
</Phase>
</Request>
</Sub>
<Sub>
<Code>2.01</Code>
<Status>UsrWorkOrderSupervisorApproved</Status>
<Request>
<Code>2.00</Code>
<Description>Test 2</Description>
<SurveyDate></SurveyDate>
<Phase>
<Code>2.01</Code>
<Status>UsrWorkOrderSupervisorApproved</Status>
</Phase>
<Phase>
<Code>2.02</Code>
<Status>UsrWorkOrderCancelled</Status>
</Phase>
</Request>
</Sub>
</businessobjects>
</document>
The result XML I need is this:
<document>
<businessobjects>
<Request>
<Code>2.00</Code>
<SurveyDate>2022-11-03T21:45:13</SurveyDate>
</Request>
</businessobjects>
</document>
My advice: forget using xsl:choose or xsl:if, and instead put the conditional logic into the template's match expression:
<xsl:template match="Sub[not(Request/SurveyDate/text())]">
<!-- handle Sub without SurveyDate -->
<!-- ... -->
</xsl:template>
Leave the case where a Sub does have a SurveyDate for the identity template to handle, if you want to copy it unchanged. If you want to remove it (it's not clear from your test code what you want to do with it), you could add another template to do so:
<xsl:template match="Sub"/>
Note that template would have a lower priority than the one above, because its match expression is simpler, so it would apply only to Sub elements which did have a SurveyDate descendant.

Copy value of node using reference into other node

I'm trying to copy the value of an XML node referenced by an identifier into another node of the graph.
The orignal file looks like this:
<Root>
<Object id="Id1">
<FileName>file.png</FileName>
</Object>
<Description>
<Content>
<Title>Nice Object</Title>
<ObjectReference>Id1</ObjectReference>
</Content>
</Description></Root>
In XSLT, I use a variable to identify the value of the reference node identifier.
<xsl:template match="Content">
<xsl:variable name="IdObject">
<xsl:value-of select="ObjectReference"/>
</xsl:variable>
<Out>
<Title>
<xsl:value-of select="Title"/>
</Title>
<FileName>
<xsl:value-of select="//Object[#id='$IdObject']/Filename"/>
</FileName>
</Out></xsl:template>
The value of 'FileName' is not copied. I select the wrong reference node, I think. I tried with 'Ancestor::' and 'Parent::'. That doesn't work either.
Do you have an idea?
Thanks
I would like to obtain the following result :
<Out>
<Name>Nice Object</Name>
<FileName>file.png</FileName>
</Out>
XSLT has a built-in key mechanism for resolving cross-references. The following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="obj" match="Object" use="#id" />
<xsl:template match="Content">
<Out>
<xsl:copy-of select="Title"/>
<xsl:copy-of select="key('obj', ObjectReference)/FileName"/>
</Out>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
applied to your input example, will return:
Result
<?xml version="1.0" encoding="UTF-8"?>
<Out>
<Title>Nice Object</Title>
<FileName>file.png</FileName>
</Out>
P.S. Your attempt did not work because:
You quoted the reference to the variable;
You used Filename instead of FileName.

How to create template to match based upon an XSLT parameter

I'm trying to create a standard-use XSLT that will perform a given task based upon a user-provided XPATH expression as an XSLT parameter.
That is, I need something like this:
<xsl:template match="$paramContainingXPATH">
<!-- perform the task on the node(s) in the given xpath -->
</xsl:template>
For example, suppose I have some XML:
<xml>
<nodeA>whatever</nodeA>
<nodeB>whatever</nodeB>
<nodeC>whatever</nodeC>
<nodeD>whatever</nodeD>
<nodeE>whatever</nodeE>
</xml>
The XSLT needs to transform just a node or nodes matching a provided XPATH expression. So, if the xslt parameter is "/xml/nodeC", it processes nodeC. If the xslt parameter is "*[local-name() = 'nodeC' or local-name() = 'nodeE']", it processes nodeC and nodeE.
This should work for absolutely any XML message. That is, the XSLT cannot have any direct knowledge of the content of the XML. So, it could be a raw XML, or a SOAP Envelope.
I was guessing I might need to grab all the nodes matching the xpath, and then looping over them calling a named template, and using the standard identity template for all other nodes.
All advice is appreciated.
If you really need that feature with XSLT 1.0 or 2.0 then I think you should consider writing one stylesheet that takes that string parameter with the XPath expression and then simply generates the code of a second stylesheet where the XPath expression is used as a match pattern and the other needed templates like the identity template are included statically. Dynamic XPath evaluation is only available in XSLT 3.0 or in earlier versions as a proprietary extension mechanism.
You cannot match a template using a parameter - but you can traverse the tree and compare the path of each node with the given path. Here's a simple example:
XSLT 1.0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="path" select="'/world/America/USA/California'"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="*"/>
</root>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="path-to-me">
<xsl:for-each select="ancestor-or-self::node()">
<xsl:value-of select="name()" />
<xsl:if test="position()!=last()">
<xsl:text>/</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:if test="$path=$path-to-me">
<xsl:call-template name="action"/>
</xsl:if>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template name="action">
<return>
<xsl:value-of select="." />
</return>
</xsl:template>
</xsl:stylesheet>
Applied to a slightly more ambitious test input of:
<world>
<Europe>
<Germany>1</Germany>
<France>2</France>
<Italy>3</Italy>
</Europe>
<America>
<USA>
<NewYork>4</NewYork>
<California>5</California>
</USA>
<Canada>6</Canada>
</America>
</world>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<return>5</return>
</root>
This could be made more efficient by passing the accumulated path as a parameter of the recursive template, so that each node needs only to add its own name to the chain.
Note:
The given path must be absolute;
Predicates (including positional predicates) and attributes are not implemented in this. They probably could be, with a bit more effort;
Namespaces are ignored (I don't see how you could pass an XPath as a parameter and include namespaces anyway).
If your processor supports an evaluate() extension function, you could forgo the calculated text path and test for intersection instead.
Edit:
Here's an example using EXSLT dyn:evaluate() and set:intersection():
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dyn="http://exslt.org/dynamic"
xmlns:set="http://exslt.org/sets"
extension-element-prefixes="dyn set">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="path" select="'/world/America/USA/California'"/>
<xsl:variable name="path-set" select="dyn:evaluate($path)" />
<xsl:template match="/">
<root>
<xsl:apply-templates select="*"/>
</root>
</xsl:template>
<xsl:template match="*">
<xsl:if test="set:intersection(. , $path-set)">
<xsl:call-template name="action"/>
</xsl:if>
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template name="action">
<return>
<xsl:value-of select="." />
</return>
</xsl:template>
</xsl:stylesheet>
Note that this will also work with with paths like:
/world/America/USA/*[2]
//California
and many others that the text comparison method could not accommodate.
I'm sending the element name as a param to the XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:output method="xml"/>
<xsl:param name="user"/>
<xsl:template match="/">
<xsl:call-template name="generic" />
</xsl:template>
<xsl:template name="generic">
<count><xsl:value-of select="count(.//*[local-name()=$user])"/></count>
</xsl:template>
</xsl:stylesheet>
I hope this could help!

XSLT 1.0 text nodes printing by default

I have looked at XSL xsl:template match="/" but the match pattern that triggered my question is not mentioned there.
I have a rather complex XML structure:
<?xml version="1.0" encoding="UTF-8"?>
<MATERIAL_DATA>
<LOG>
<USER>Peter</USER>
<DATE>2011-02-18</DATE>
<MATERIALS>
<item>
<MATNR>636207</MATNR>
<TEXTS>
<item>
<TEXT>granola bar 40gx24</TEXT>
</item>
</TEXTS>
<PRICES>
<item>
<MATNR>636207</MATNR>
<COST>125.78</COST>
</item>
</PRICES>
<SALESPRICES>
<item>
<B01>
<MATNR>636207</MATNR>
<CURR>CZK</CURR>
<DATBI>9999-12-31</DATBI>
<DATAB>2010-10-05</DATAB>
</B01>
<B02>
<item>
<PRICE>477.60</PRICE>
<KUNNR>234567</KUNNR>
</item>
</B02>
</item>
</SALESPRICES>
</item>
</MATERIALS>
</LOG>
</MATERIAL_DATA>
Now if I apply the following XSLT, my output looks correct:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:template match="node() | #*">
<xsl:apply-templates select="* | #*" />
</xsl:template>
<xsl:template match="B02">
<xsl:element name="Mi">
<xsl:value-of select="item/KUNNR"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
I get the output:
<?xml version="1.0" encoding="UTF-8"?>
<Mi>234567</Mi>
But if I apply the XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:template match="/*">
<xsl:element name="MenuItems">
<xsl:apply-templates select="LOG/MATERIALS/item/SALESPRICES/item"/>
</xsl:element>
</xsl:template>
<xsl:template match="B02">
<xsl:element name="Mi">
<xsl:value-of select="item/KUNNR"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
the output looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<MenuItems>
636207
CZK
9999-12-31
2010-10-05
<Mi>234567</Mi>
</MenuItems>
All values from the element <B01> are in the output! But why - I am not matching <B01>!?
How does
<xsl:template match="node() | #*">
<xsl:apply-templates select="* | #*" />
</xsl:template>
make the output come out correctly? All I am doing with this is match all nodes or attributes and apply-templates to everything or all attributes.
But in my opinion it should not make a difference to when I exactly match <B01>!
Why is this happening?
XSLT includes the following default templates (among others):
<!-- applies to both element nodes and the root node -->
<xsl:template match="*|/">
<xsl:apply-templates/>
</xsl:template>
<!-- copies values of text and attribute nodes through -->
<xsl:template match="text()|#*">
<xsl:value-of select="."/>
</xsl:template>
In your first stylesheet you're implicitly matching all text nodes with node(), thus overriding the default action. Then, in the B2 template, you output your target value and apply no further templates, which stops processing.
In the second stylesheet, you explicitly apply templates to all children of LOG/MATERIALS/item/SALESPRICES/item, causing the default templates to process the nodes you don't explicitly handle. Because you explicitly handle B2 without applying templates to its children, the default templates are never invoked for those nodes. But the default templates are applied to the children of B1.
Adding the following template to your second stylesheet would override the default action for text nodes:
<xsl:template match="text()|#*"></xsl:template>
With the following result:
<?xml version="1.0" encoding="UTF-8"?>
<MenuItems><Mi>234567</Mi></MenuItems>
More:
http://www.w3.org/TR/xslt#built-in-rule
Looks like you are running into the built in template rules.
Specifically the text rule - this will copy text nodes if not overridden.

can we use dynamic variable name in the select statement in xslt?

I wanted to use a dynamic variable name in the select statement in xslt.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="input" select="input/message" />
<xsl:variable name="Name" select="'MyName'" />
<xsl:variable name="Address" select="MyAddress" />
<xsl:variable name="output" select="concat('$','$input')" /> <!-- This is not working -->
<output>
<xsl:value-of select="$output" />
</output>
</xsl:template>
The possible values for the variable "input" is 'Name' or 'Address'.
The select statement of the output variable should have a dynamic variable name based on the value of input variable. I don't want to use xsl:choose. I wanted to select the value dynamically.
Please provide me a solution.
Thanks,
dhinu
XSLT 1.0 and XSLT 2.0 don't have dynamic evaluation.
Solution for your problem:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<my:values>
<name>MyName</name>
<address>MyAdress</address>
</my:values>
<xsl:template match="/">
<xsl:variable name="vSelector"
select="input/message"/>
<xsl:value-of select=
"document('')/*/my:values/*[name()=$vSelector]"/>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document:
<input>
<message>address</message>
</input>
produces the wanted, correct result:
MyAdress
when the same transformation is applied on this XML document:
<input>
<message>name</message>
</input>
again the wanted, correct result is produced:
MyName
Finally: If you do not wish to use the document() function, but would go for using the xxx:node-set() extension function, then this solution (looking very similar) is what you want, where you may consult your XSLTprocessor documentation for the exact namespace of the extension:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" >
<xsl:output method="text"/>
<xsl:variable name="vValues">
<name>MyName</name>
<address>MyAdress</address>
</xsl:variable>
<xsl:template match="/">
<xsl:variable name="vSelector"
select="input/message"/>
<xsl:value-of select=
"ext:node-set($vValues)/*[name()=$vSelector]"/>
</xsl:template>
</xsl:stylesheet>
Beside #Dimitre's good answer, for this particular case (output string value) you could also use:
<xsl:variable name="output"
select="concat(substring($Name, 1 div ($input = 'Name')),
substring($Address, 1 div ($input = 'Address')))"/>