Xpath error with not() and ends-with() - java

I have the following Xpath expression:
//*[not(input)][ends-with(#*, 'Copyright')]
I expect it to give me all elements - except input - with any attribute value which ends with "Copyright".
I execute it in the Selenium 2 Java API with webDriver.findElements(By.xpath(expression)) and get the following error:
The expression is not a legal
expression
But these expressions work without trouble:
//*[not(input)][starts-with(#*, 'Copyright')]
//*[ends-with(#*, 'Copyright')]
Any ideas?

I have the following Xpath expression:
//*[not(input)][ends-with(#*, 'Copyright')]
I expect it to give me all elements -
except input - with any attribute
value which ends with "Copyright".
There are a few issues here:
ends-with() is a standard XPath 2.0 function only, so the chances are you are using an XPath 1.0 engine and it correctly raises an error because it doesn't know about a function called ends-with().
Even if you are working with an XPath 2.0 processor, the expression ends-with(#*, 'Copyright') results in error in the general case, because the ends-with() function is defined to accept atmost a single string (xs:string?) as both of its operands -- however #* produces a sequence of more than one string in the case when the element has more than one attribute.
//*[not(input)] doesn't mean "select all elements that are not named input. The real meaning is: "Select all elements that dont have a child element named "input".
Solution:
Use this XPath 2.0 expression: //*[not(self::input)][#*[ends-with(.,'Copyright')]]
In the case of XPath 1.0 use this expression:
....
//*[not(self::input)]
[#*[substring(., string-length() -8) = 'Copyright']]
Here is a short and complete verification of the last XPath expression, using XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:copy-of select=
"//*[not(self::input)]
[#*[substring(., string-length() -8)
= 'Copyright'
]
]"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<html>
<input/>
<a x="Copyright not"/>
<a y="This is a Copyright"/>
</html>
the wanted, correct result is produced:
<a y="This is a Copyright"/>
In the case of the XML document being in a default namespace:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://www.w3.org/1999/xhtml"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:copy-of select=
"//*[not(self::x:input)]
[#*[substring(., string-length() -8)
= 'Copyright'
]
]"/>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<html xmlns="http://www.w3.org/1999/xhtml">
<input z="This is a Copyright"/>
<a x="Copyright not"/>
<a y="This is a Copyright"/>
</html>
the wanted, correct result is produced:
<a xmlns="http://www.w3.org/1999/xhtml" y="This is a Copyright"/>

I don't know Selenium but if //*[not(input)][starts-with(#*, 'Copyright')] is parsed successfully and if additionally the XPath 2.0 function ends-with is supported then I don't see any reason why //*[not(input)][ends-with(#*, 'Copyright')] is not accepted as a legal expression. Your verbal description however sounds as if you want //*[not(self::input)][#*[ends-with(., 'Copyright')]].
//*[not(input)] selects any elements not having any input child element while //*[not(self::input)] selects any elements not being themselves input elements. As for comparing [#*[ends-with(., 'Copyright')]] with what you have, my suggestion is true as long as there is any attribute node which ends with 'Copyright' while your test would only work if there is a single attribute which ends with 'Copyright', as ends-with http://www.w3.org/TR/xquery-operators/#func-ends-with allow a sequence with a single item as its first argument or an empty sequence but not several items.

Most likely explanation is that you are using an XPath 1.0 processors. The ends-with() function requires XPath 2.0 support.

//*[not(self::input)][#*[substring(., string-length(.) -8) = 'Copyright']]
May be small correction with string-length(.)
Now, it may work.

Related

Removing both single and double quotes using XSLT translate function or better approach

I am working with XSLT 1.0 and XML. I am new to the topics but I have been reading and trying out how XSLT is applied to XML.
Now, I have given a project where I need to filter out invalid fields from XML element. The java Transformer class is used to apply the XSLT on the XML.
The java code is similar to the oracle tutorial page under section "Writing an XSLT Transform". I have added the below xml and xsl and run the code like described in the tutorial page.My target is to extract single and bouble quotes and the following characters #60;•^#x6;
The XML file
<?xml version="1.0" encoding="UTF-8"?>
<Author>
<Name>
<FirstName>Ch#60;•^#x6;'""ris</FirstName>
<LastName>Banville</LastName>
</Name>
</Author>
The XSL file
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<xsl:variable name="invalid">#60;•^#x6;"'</xsl:variable>
<div>
<xsl:value-of select="translate(/Author/Name/FirstName,$invalid,'')" />
</div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
The end output is
<body>
<div>Chris</div>
</body>
My question is what is the right approach while using XSLT 1.0 to escape both single and double quotes and other invalid characters together? I have also tried nesting
translate
XSLT function as shown below after creating a "single" and "double" variable.
<xsl:value-of select="translate(translate(/Author/Name/FirstName,$single,''),$double,'')" />
I am still not sure pros and cons using any of my current implemenation or even if it the right approach to perform such tasks.
The XPath 1.0 specification states:
Within expressions, literal strings are delimited by single or double quotation marks, which are also used to delimit XML attributes. To avoid a quotation mark in an expression being interpreted by the XML processor as terminating the attribute value the quotation mark can be entered as a character reference (" or &apos;). Alternatively, the expression can use single quotation marks if the XML attribute is delimited with double quotation marks or vice-versa.
This means that within a single expression using only literal string, you can either remove double quote characters by:
<xsl:value-of select="translate(input, '"'')"/>
or single quote (apostrophe) characters by:
<xsl:value-of select='translate(input, "&apos;", "")'/>
but not both.
Defining a variable containing all the unwanted characters as literal text (as you did) is probably the best way around this limitation.

How to use Parameters in xsl:apply-templates which are set from java?

I have a question on how to dynamically set the xpath expression in apply-templates select=?
<xsl:template match="CDS">
<xsl:result-document href="{$fileName}">
<xsl:copy>
<xsl:apply-templates select="$xpathCondition"/>
</xsl:copy>
</xsl:result-document>
</xsl:template>
This $xpathCondition am trying to set from java from properties file and setting to param in xsl.
transformer.setParameter("fileName", "Test.xml");
transformer.setParameter("xpathCondition", "CD[contains(Title/text(),'TEST')]");
$fileName is working as expected. But $xpathCondition is not working as expected.
There's no standard way of parsing a string as a dynamic XPath expression and executing it until you get to the xsl:evaluate instruction in XSLT 3.0. You really need to tell us which version you are using - the fact that you use xsl:result-document tells us that it's 2.0 or later, but beyond that we are guessing.
Many XSLT processors have an extension function called xx:eval() or similar.
The problem can be tackled in XSLT 3.0 using static parameters and shadow attributes. You can write:
<xsl:param name="xpathCondition" static="yes"/>
and then:
<xsl:apply-templates _select="{$xpathCondition}"/>
(Note the underscore in _select)
With 2.0 (or indeed 1.0) you can simulate this approach by doing a transformation on the stylesheet before executing it.

How to Debug Extended Functions in XSLT Using Saxon?

I have been reading through programming blogs about how to use Extended Functions with XSLT and Saxon, and can't seem to reference external Java functions in the XSLT successfully. I am getting the following error whenever I run the transform:
Cannot find a matching 0-argument function named {come.acme.javaxslt.business.CarBusiness}getModel()
at xsl:apply-templates (file:/C:/Users/Dave/workspace/acme-javaXSLT-demo/cars.xsl#18)
processing /cars/car[1]/model[1]
in built-in template rule
at xsl:apply-templates (file:/C:/Users/Dave/workspace/acme-javaXSLT-demo/cars.xsl#10)
processing /cars
EXCEPTION: net.sf.saxon.trans.XPathException: Cannot find a matching 0-argument function named {com.acme.javaxslt.business.CarBusiness}getModel()
; SystemID: file:/C:/Users/Dave/workspace/acme-javaXSLT-demo/cars.xsl; Line#: 30; Column#: -1
net.sf.saxon.trans.XPathException: Cannot find a matching 0-argument function named {com.acme.javaxslt.business.CarBusiness}getModel()
at net.sf.saxon.expr.ErrorExpression.evaluateItem(ErrorExpression.java:58)
at net.sf.saxon.expr.ErrorExpression.iterate(ErrorExpression.java:71)
at net.sf.saxon.expr.Atomizer.iterate(Atomizer.java:180)
...
Referenced Java Class:
package com.acme.javaxslt.business;
public class CarBusiness {
public static String getModel(){
return "/";
}
}
XSLT:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cars="com.acme.javaxslt.business.CarBusiness">
<xsl:template match="/">
<html>
<body>
<h2>My Car Collection</h2>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="car">
<p>
<xsl:apply-templates select="make"/>
<xsl:apply-templates select="model"/>
</p>
</xsl:template>
<xsl:template match="make">
Make: <span style="color:#ff0000">
<xsl:value-of select="."/></span>
<br />
</xsl:template>
<xsl:template match="model">
Artist: <span style="color:#00ff00">
<xsl:value-of select="cars:getModel()"/></span>
<br />
</xsl:template>
</xsl:stylesheet>
Use of Saxon in Java:
TransformerFactory tfactory = TransformerFactory.newInstance();
Transformer transformer = tfactory.newTransformer(new StreamSource(new File(xslID)));
transformer.transform(new StreamSource(new File(sourceID)), new StreamResult(new File("html/report.html")));
As soon as the above is run, the errors occur.
Even if I substitute my custom Java class for an inbuilt java method in the XSLT, I get the same error. So it's obvious that the XSLT is not hooking up with Java.
The recommended approach is to add -TJ to the JVM options, or do something with FeatureKeys.TRACE_EXTERNAL_FUNCTIONS, but I've searched high and low on the web to find out more information on how to do this so that I can see why my XSLT is not hooking up with external Java functions, to no avail.
Please, can someone help me figure out what my problem is?
This is happening for Saxon PE and HE, I haven't tried other versions yet.
Thanks!
I should have used an earlier version of Saxon, Saxon-B 9.1.0.8, as pointed out here:
http://sourceforge.net/p/saxon/discussion/94027/thread/33492ab5/
As soon as I put the older jar files in the path, then the above XSLT called the java function correctly.

Formatting decimal values for XML

I have a problem currently where a system we are connecting to expects to receive XML which contains, among other things, three double fields formatted to one decimal place. Personally I feel that our system should just be able to send values in default format and then it's up to other systems to format their own representation as they please, but alas this doesn't seem to be an option.
My Java-based system is currently converting objects to XML through the use of XStream. We have an XSD which accompanies the XML and defines the various elements as string, double, dateTime, etc.
I have three double fields which hold values like 12.5, 100.123, 5.23445 etc. Right now they are converted pretty much as-is into the XML. What I need is these values to be formatted in the XML to one decimal place; 12.5, 100.1, 5.2, etc.
I have briefly thought up options to accomplish this:
Somehow have Java format these values to this precision before it goes to the XML. Perhaps NumberFormat can do this, although I thought that was mainly for using with String output.
Hope that the XSD can do this for me; I know you can place limits on precision in the XSD, but I am unsure whether it actually handles the rounding itself or will just say 'this value of 123.123 is invalid for this schema'?
Use XSLT to somehow accomplish this for me.
I'd to pick your collective brains as to what would be the 'accepted' way / best practice to use in a situation like this.
Thanks,
Dave.
XStream has converters (tutorial). You would have to register your own Double converter that will handle this. In the converter use DecimalFormat to limit the number of decimal places.
This can be done in a single XPath expression.
Use:
floor(.) + round(10*(. -floor(.))) div 10
Verification using XSLT as a host of XPath:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()[contains(.,'.')]">
<xsl:value-of select=
"floor(.) + round(10*(. -floor(.))) div 10"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<t>
<n>12.5</n>
<n>100.123</n>
<n>5.26445</n>
</t>
the wanted, correct result is produced:
<t>
<n>12.5</n>
<n>100.1</n>
<n>5.3</n>
</t>
Explanation: Use of the standard XPath functions floor(), round() and the XPath operator div and your logic.
Generalized expression:
floor(.) + round($vFactor*(. -floor(.))) div $vFactor
where $vFactor is 10^N, where N is the number of digits after the decimal point we want.
Using this expression, the modified XSLT transformation is this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pPrecision" select="4"/>
<xsl:variable name="vFactor" select=
"substring('10000000000000000000000',
1, $pPrecision+1
)
"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()[contains(.,'.')]">
<xsl:value-of select=
"floor(.) + round($vFactor*(. -floor(.))) div $vFactor"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the same XML document (above), we produce the wanted output for any meaningful value of $pPrecision. In the above example it is set to 4 and the result contains all numbers rounded up to four digits after the decimal point:
<t>
<n>12.5</n>
<n>100.123</n>
<n>5.2645</n>
</t>

Replace an attribute in xml with xpath

I want to take an attribute found thru xpath and replace it in the Document.
This is the xml:
<MineX STATE="add">
<Desc F_CREATOR="admin" F_ENTRYDATE="2010-12-24" F_HEIGHT="0.875" F_ID="1" F_LEFT="1.15625" F_LINE_COLOR="255" F_FORECOLOR="0">
<F_CUSTOM_BYTES></F_CUSTOM_BYTES>
</Desc>
</MineX>
With Java, I can retrieve the value like this:
org.w3c.dom.Document xmlDoc = getDoc(path);
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
XPathExpression myExp = xpath.compile("//MineX/Desc/#F_LINE_COLOR");
System.out.println("Line color:" + (String)myExp.evaluate(xmlDoc, XPathConstants.STRING) + "\n");
This prints out: 255
So, what XPath function will allow me to replace the 255, for another string?
Or do I need something other than XPath for this?
So, what XPath function will allow me
to replace the 255, for another
string? Or do I need something other
than XPath for this?
XPath is the query language for XML and as such cannot modify an XML document.
In order to modify an XML document one needs to use the programming language (such as XSLT, C#, JS, PHP, ..., etc) that is hosting XPath.
Here is a solution, where the hosting language is XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pNewLineColor" select="123"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#F_LINE_COLOR">
<xsl:attribute name="{name()}">
<xsl:value-of select="$pNewLineColor"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<MineX STATE="add">
<Desc F_CREATOR="admin"
F_ENTRYDATE="2010-12-24"
F_HEIGHT="0.875"
F_ID="1"
F_LEFT="1.15625"
F_LINE_COLOR="255"
F_FORECOLOR="0">
<F_CUSTOM_BYTES></F_CUSTOM_BYTES>
</Desc>
</MineX>
the wanted, correct result is produced:
<MineX STATE="add">
<Desc F_CREATOR="admin"
F_ENTRYDATE="2010-12-24"
F_HEIGHT="0.875"
F_ID="1"
F_LEFT="1.15625"
F_LINE_COLOR="123"
F_FORECOLOR="0">
<F_CUSTOM_BYTES></F_CUSTOM_BYTES>
</Desc>
</MineX>
XPath is a query language for extracting information out of an XML file. As far as I know it is not suited for replacing or editing data in an XML. One way to transform an XML is via XSLT.

Categories

Resources