I have been using the following xpath selection in xpath 1.0
<xsl:variable name="id"><xsl:value-of select="./#id" /></xsl:variable>
preceding::exm:messageFlow[#sourceRef = $id]/#targetRef
This worked very well and did exactly what I wanted.
However I started using XSLT 2.0 in combination with xPath 2.0 and this does not work anymore
(Using the Altova XML processor)
I get the following error:
Kann nicht mit Ziel-Typ besetzt werden - Aktuelles Element ist 'sid-B3FD7EE5-043
3-4939-A69F-E74B30FDEB1C' vom Typ xs:untypedAtomic, Typ xs:QName erwartet - =
which translates roughly to:
Cannot be set to target-type - Current element is 'sid-B3FD7EE5-043
3-4939-A69F-E74B30FDEB1C' of type xs:untypedAtomic, type xs:QName
expected -
Previously I had a similar problem with this selection:
following::exm:*[exm:incoming = $out] | preceding::exm:*[exm:incoming = $out]
Which again worked in xpath 1.0, but returned a similar error in xPath 2.0
After changing it to
following::exm:*[exm:incoming/text() = $out] | preceding::exm:*[exm:incoming/text() = $out]
It worked in xPath 2.0 as well. I did try doing something similar with the attributes, but it did not work
A small extract of the corresponding XML:
<messageFlow id="sid-80B618A4-E6BF-4438-AF5D-5111AD308FE6" name="" sourceRef="sid-B3FD7EE5-0433-4939-A69F-E74B30FDEB1C" targetRef="sid-6EB2DB76-CC19-48AD-A073-D37C7489F211"/>
<task completionQuantity="1" id="sid-B3FD7EE5-0433-4939-A69F-E74B30FDEB1C" isForCompensation="false" name="call service" startQuantity="1">
<incoming>sid-2B2BA651-B5BA-4195-9B5B-E6855B1138F4</incoming>
<incoming>sid-DA4B86E8-C3F2-497C-8C0D-218E95CE9FD1</incoming>
<outgoing>sid-008948DE-BA59-4897-AC37-2C3AA63DCD82</outgoing>
</task>
I wonder if you have a schema that declares the #id attribute as being of type QName?
With this variable declaration:
<xsl:variable name="id"><xsl:value-of select="./#id" /></xsl:variable>
you are creating a copy of the #id attribute in a new result tree fragment. This is quite unnecessary; your needs would almost certainly be better served by making the variable be simply a reference to the existing attribute, thus:
<xsl:variable name="id" select="#id" />
By making the copy, you are not only writing unnecessary code and incurring unnecessary run-time cost (building a new tree is an expensive operation), but you are also losing the type information. If my conjecture is correct that #id is of type xs:QName, then the variable $id after atomization will be of type xs:untypedAtomic, and comparing it to an xs:QName is likely to fail with a message similar to the one cited.
I can't reproduce the problem.
This transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="task">
<xsl:variable name="id"><xsl:value-of select="./#id" /></xsl:variable>
<xsl:variable name="vResult"
select="preceding::messageFlow[#sourceRef = $id]/#targetRef"/>
<xsl:value-of select="$vResult"/>
</xsl:template>
</xsl:stylesheet>
when applied on the following document (the provided fragment wrapped in a single top element and stripped off any undefined namespaces):
<t>
<messageFlow id="sid-80B618A4-E6BF-4438-AF5D-5111AD308FE6"
name="" sourceRef="sid-B3FD7EE5-0433-4939-A69F-E74B30FDEB1C"
targetRef="sid-6EB2DB76-CC19-48AD-A073-D37C7489F211"/>
<task completionQuantity="1" id="sid-B3FD7EE5-0433-4939-A69F-E74B30FDEB1C"
isForCompensation="false" name="call service" startQuantity="1">
<incoming>sid-2B2BA651-B5BA-4195-9B5B-E6855B1138F4</incoming>
<incoming>sid-DA4B86E8-C3F2-497C-8C0D-218E95CE9FD1</incoming>
<outgoing>sid-008948DE-BA59-4897-AC37-2C3AA63DCD82</outgoing>
</task>
</t>
when run with both Altova2011 and Saxon 9.1.07, produces the expected, correct result and no error is raised:
sid-6EB2DB76-CC19-48AD-A073-D37C7489F211
Related
Looking at this XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
root
<child>
child 1
<grandchild>
grandchild 1
</grandchild>
<yetanothergrandchild>
yetanothergrandchild 1
</yetanothergrandchild>
</child>
<child>
child 2
<grandchild>
grandchild 2
</grandchild>
<yetanothergrandchild>
yetanothergrandchild 2
</yetanothergrandchild>
</child>
</root>
and that XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output media-type="text" omit-xml-declaration="yes"/>
<xsl:template match="/">
<fo:root>
<fo:layout-master-set>
<fo:simple-page-master master-name="simple"
page-height="29.7cm"
page-width="21cm"
margin-top="1cm"
margin-bottom="2cm"
margin-left="2.5cm"
margin-right="2.5cm">
<fo:region-body margin-top="3cm"/>
<fo:region-before extent="3cm"/>
<fo:region-after extent="1.5cm"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="simple">
<fo:flow flow-name="xsl-region-body">
<fo:block font-size="12pt"
font-family="sans-serif"
line-height="15pt"
space-after.optimum="3pt"
text-align="justify">
<xsl:value-of select="root/child/grandchild"/>
<xsl:value-of select="root/child/yetanothergrandchild"/>
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
If I put the xsl:stylesheet version to 1.0, the output is:
grandchild 1 yetanothergrandchild 1
If I put it to 2.0, the output is:
grandchild 1 grandchild 2 yetanothergrandchild 1 yetanothergrandchild 2
Of course, I read already through various lists of differences in between XSL T 1 and 2 but I cannot find any hint of a change which could cause that.
Can somebody tell me how and why that behaves that differently?
See https://www.w3.org/TR/xslt20/#backwards and then https://www.w3.org/TR/xslt20/#incompatibilities saying
J.1.3 Backwards Compatibility Behavior Some XSLT constructs behave
differently under XSLT 2.0 depending on whether backwards compatible
behavior is enabled. In these cases, the behavior may be made
compatible with XSLT 1.0 by ensuring that backwards compatible
behavior is enabled (which is done using the [xsl:]version attribute).
These constructs are as follows:
If the xsl:value-of instruction has no separator attribute, and the
value of the select expression is a sequence of more than one item,
then under XSLT 2.0 all items in the sequence will be output, space
separated, while in XSLT 1.0, all items after the first will be
discarded.
...
In XSLT 1.0 the xsl:value-of instruction returns the string-value of the first node in the selected node-set.
In XSLT 2.0 the instruction returns the value of every node in the selected sequence, separated by a space or by the string specified in the separator attribute.
These are my formulations, the specs are more difficult to follow.
I use elementpath to handle some XPath queries. I have an XML with linear structure which contains a unique id attribute.
<items>
<item id="1">...</item>
<item id="2">...</item>
<item id="3">...</item>
... 500k elements
<item id="500003">...</item>
</items>
I want the parser to find the first occurence without traversing all the nodes. For example, I want to select //items/item[#id = '3'] and stop after iterating over 3 nodes only (not over 500k of nodes). It would be a nice optimization for many cases.
An example using XSLT 3 streaming with a static parameter for the XPath, then using xsl:iterate with xsl:break to produce the "early exit" once the first item sought has been found would be
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all">
<xsl:param name="path" static="yes" as="xs:string" select="'items/item[#id = ''3'']'"/>
<xsl:output method="xml"/>
<xsl:mode on-no-match="shallow-copy" streamable="yes"/>
<xsl:template match="/" name="xsl:initial-template">
<xsl:iterate _select="{$path}">
<xsl:if test="position() = 1">
<xsl:copy-of select="."/>
<xsl:break/>
</xsl:if>
</xsl:iterate>
</xsl:template>
</xsl:stylesheet>
You can run it with SaxonC EE (unfortunately streaming is only supported by EE) and Python with e.g.
import saxonc
with saxonc.PySaxonProcessor(license=True) as proc:
print("Test SaxonC on Python")
print(proc.version)
xslt30proc = proc.new_xslt30_processor()
xslt30proc.set_parameter('path', proc.make_string_value('/items/item[#id = "2"]'))
transformer = xslt30proc.compile_stylesheet(stylesheet_file='iterate-items-early-exit1.xsl')
xdm_result = transformer.apply_templates_returning_value(source_file='items-sample1.xml')
if transformer.exception_occurred:
print(transformer.error_message)
print(xdm_result)
I am trying to output duplicate values across different nodes and value by using XSLT. I want the node element to be dynamic so it can track different value after the namespace prefix, for example: car:ID, car:Name, car:Location_name, or more. I know i can use the function Local-Name(.) but I am not sure how to apply to my XSLT logic. please help
the sample XML as follow:
<car:root xmlns:car="com.sample">
<Car_Input_Request>
<car:Car_Details>
<car:ID>Car_001</car:ID>
<car:Name>Fastmobile</car:Name>
<car:Local_Name>New York</car:Local_Name>
<car:Transmission_Reference_Type>
<car:ID car:type="Transmission_Reference_Type">Automatic</car:ID>
</car:Transmission_Reference_Type>
</car:Car_Details>
</Car_Input_Request>
<Car_Input_Request>
<car:Car_Details>
<car:ID>Car_002</car:ID>
<car:Name>Slowmobile</car:Name>
<car:Local_Name>New York</car:Local_Name>
<car:Transmission_Reference_Type>
<car:ID car:type="Transmission_Reference_Type">Manual</car:ID>
</car:Transmission_Reference_Type>
</car:Car_Details>
</Car_Input_Request>
<Car_Input_Request>
<car:Car_Details>
<car:ID>Car_001</car:ID>
<car:Name>Fastmobile</car:Name>
<car:Local_Name>New York</car:Local_Name>
<car:Transmission_Reference_Type>
<car:ID car:type="Transmission_Reference_Type">Automatic</car:ID>
</car:Transmission_Reference_Type>
</car:Car_Details>
</Car_Input_Request>
</car:root>
The XSLT used:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:car="com.sample"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="3.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:value-of select="//car:ID[ let $v:=string(.),$t:=#car:type return not( preceding::car:ID[string(.) = $v and #car:type=$t]) ]/(let $v:=string(.), $t:=#car:type,$c:=1+count(following::car:ID[string(.)=$v and $t=#car:type]) ,$c:=1+count(following::car:*[string(.)=$v]) return if ($c > 1) then concat( string(.), ' occurs ', $c, ' times for type ', $t, '
') else () )"/>
</xsl:template>
</xsl:stylesheet>
output shown from xslt:
Car_001 occurs 2 times for type
Automatic occurs 2 times for type Transmission_Reference_Type
But I want it to show
Car_001 occurs 2 times for type ID
Fastmobile occurs 2 times for type Name
Automatic occurs 2 times for type Transmission_Reference_Type
New York occurs 3 times for type Local_Name
If you are looking for an XSLT solution (rather than a single line XPath expression), you could make use of xsl:for-each-group with a composite key:
<xsl:stylesheet
xmlns:car="com.sample"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
expand-text="yes"
version="3.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each-group select="//car:Car_Details/*" group-by="local-name(), normalize-space()" composite="yes">
<xsl:if test="current-group()[2]">
<xsl:text>{normalize-space()} occurs {count(current-group())} times for {local-name()}
</xsl:text>
</xsl:if>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
With the following XML, the top level is returning the nodes of all levels. There are no ancestors for the top level, so why am I getting it’s children?
XML
<?xml version="1.0" encoding="ISO-8859-1"?>
<WBSs>
<WBS GUID="2">
<Name>work</Name>
<WBSs>
<WBS GUID="1">
<Name>Wall</Name>
<ParentWBS>2</ParentWBS>
</WBS>
<WBS GUID="2">
<Name>South Wall</Name>
<ParentWBS>2</ParentWBS>
</WBS>
<WBS GUID="3">
<Name>North Wall</Name>
<ParentWBS>2</ParentWBS>
</WBS>
</WBSs>
</WBS>
</WBSs>
XPATH
Note: Apply template is on .//WBS
<xsl:variable name="wbsCode" select=".//ancestor-or-self::WBS/#GUID[1]"/>
Note: I have an xslt instruction immediately following the xpath expression to strinify the nodes and include ‘.’.
Result
2.1.2.3
2.1
2.2
2.3
Desired result
2
2.1
2.2
2.3
XSLT
<xsl:variable name="WBS_ELEMENT_TABLE">
<xsl:apply-templates select=".//WBS" mode="I_WBS_ELEMENT">
<xsl:with-param name="ProjectId" select="$ProjectId"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:template match="WBS" mode="I_WBS_ELEMENT">
<xsl:param name="ProjectId"/>
<xsl:variable name="wbsCode" select=".//ancestor-or-self::WBS/#GUID[1]"/>
<xsl:variable name="temp2" select="string-join(($wbsCode), '.')"/>
<WBS_ELEMENT>
<xsl:value-of select="$temp2"/>
</WBS_ELEMENT>
</xsl:template>
// means /descendant-or-self::node()/, so //ancestor::* means ./descendant-or-self::node()/ancestor::x which finds all ancestors of all descendants, i.e. everything.
Get out of that habit of using // without thinking about what it means!
How can we check value for value inside node retun i.e
Hurricane's Grill Darling Harbour
Actually, i am not able to pick apostrophe sign with below code
exists(//ns1:name[text()='Hurricane's Grill Darling Harbour']).
Getting error message:
RuntimeException:net.sf.saxon.trans.XPathException: XPath syntax error at char 33 on line 2 in {...name[text()='Hurricane's Gr...}: expected "]", found name "s"
i am not able to pick apostrophe sign with below code
exists(//ns1:name[text()='Hurricane's Grill Darling Harbour']).
Getting error message: RuntimeException:net.sf.saxon.trans.XPathException: XPath syntax error at char 33 on line 2 in {...name[text()='Hurricane's Gr...}:
expected "]", found name "s
In XPath 2.0 an apostrophe or a double quote can be escaped by simply doubling that character. See rules [75] and [76] here: http://www.w3.org/TR/xpath20/#terminal-symbols
Use:
exists(//ns1:name[text()='Hurricane''s Grill Darling Harbour'])
XSLT 2.0 - based verification:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="ns1">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:sequence
select="exists(//ns1:name[text()='Hurricane''s Grill Darling Harbour'])"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<ns1:name xmlns:ns1="ns1">Hurricane's Grill Darling Harbour</ns1:name>
the wanted, correct result is produced:
true
In case you only can use an XPath 1.0 expression within an XML document and the string contains both kinds of quotes, use:
boolean(//ns1:name
[text()=concat('Hurricane "Mathew"', " strength's value")])
Here is a full XSLT 1.0 - based verification:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="ns1">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:value-of select=
"boolean(//ns1:name
[text()=concat('Hurricane "Mathew"', " strength's value")])"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<ns1:name xmlns:ns1="ns1">Hurricane "Mathew" strength's value</ns1:name>
the wanted, correct result is produced:
true
Saxon starts to support some of the XPath 2.0 requeriments in 7.0 version.
SOAPUI version 5.1.2 use Saxon 9.1 version (Saxon change history for new version can be viewed here)
Then as #DimitreNovatchev perfectly explain, a possible option to escape ' for XPath 2.0 in text is to use double the symbol as '':
exists(//ns1:name[text()='Hurricane''s Grill Darling Harbour'])
I only want to add another possibility; since " quotes are also valid you can wrap the whole text with " an use ' inside. So in your SOAPUI XPath match assertion you can use:
exists(//ns1:name[text()="Hurricane's Grill Darling Harbour"])
Note that I purpose this way, since I'm assuming that you've to use only ' in your XPath finding node texts. If you need to use also " inside your the text wrapped with " you've to double the " as happens with '.
Additionally note that in SOAPUI it's possible to use * as a wildcard for namespaces so you can reduce your expression:
declare namespace ns='http://somenamespace/';
exists(//ns1:name[text()="Hurricane's Grill Darling Harbour"])
To:
exists(//*:name[text()="Hurricane's Grill Darling Harbour"])