Replace an attribute in xml with xpath - java

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.

Related

How to transform an xml file by searching for some nodes and replacing the values

This is the input xml -
<payload id="001">
<termsheet>
<format>PDF</format>
<city>New York</city>
</termsheet>
</payload>
We are using Xalan for most of our xml transformations and we are on XSLT 1.0
I want to write a XSLT template which would convert the input to the below output -
<payload id="001">
<termsheet>
<format>pdf</format>
<city>Mr. ABC</city>
</termsheet>
</payload>
I tried lot of answers on SO, but can't get around this problem.
Apologies for not being clear, toLower was an over simplification. I want to use the city name and invoke a java method which will return a business contact from that city. I have updated the original question
I think that the simplest way is to use java extension with Xalan, you can write a simple java class that implements the business logic you need, and then call it from your xslt. The stylesheet is quite simple
<xsl:stylesheet version="1.0"
xmlns:java="http://xml.apache.org/xalan/java"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="java">
<xsl:template match='node() | #*'>
<xsl:copy>
<xsl:apply-templates select ='node()|#*'></xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="termsheet/city">
<xsl:copy>
<xsl:value-of select='java:org.example.Card.getName(.)'/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
you also neeed to write the java class invoked
package org.example
public class Card {
public static String getName(String id) {
// put here your code to get what you need
return "Mr. ABC"
}
}
there are other ways to do that and you should really give an eye to the documentation about xalan extensions

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>

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

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.

How to edit XML with XSL?

I'm writing a dummy "MyAgenda" application in Java which has to allow maintenance of the XML file that stores the data.
Say I have a XML file like:
<myagenda>
<contact>
<name>Matthew Blake</name>
<phone>12345678</phone>
</contact>
</myagenda>
How can I add a new <contact> by using XSLT ?
Thanks.
Start with the identity transform, which transforms any XML document into itself.
The identity transform is a simple machine: given a tree, it copies every node it finds recursively. You're going to override its behavior for one specific node - the myagenda element - which it's going to copy in a different way.
To do this, add a template that matches the element that you want to update and duplicates it. In your case:
<xsl:template match="myagenda">
<xsl:copy-of select=".">
<xsl:apply-templates select="node() | #*"/>
</xsl:copy-of>
</xsl:template>
You might think, "wait isn't that the identity transform?" It is, but it's not going to stay that way.
Now decide on how you're going to get the new contact information into the transform. There are basically two ways: read it from a separate XML document using the document function, or pass the values into the transform using parameters. Let's assume that you're using parameters; in this case, you'd add the following to the top of your XSLT (right after the xsl:output element):
<xsl:param name="contactName"/>
<xsl:param name="contactPhone"/>
Now, instead of transforming myagenda into a copy of itself, you want to transform it into a copy of itself that has a new contact in it. So modify the template to do this:
<xsl:template match="myagenda">
<xsl:copy-of select=".">
<xsl:apply-templates select="node() | #*"/>
<contact>
<name><xsl:value-of select="$contactName"/></name>
<phone><xsl:value-of select="$contactPhone"/></phone>
</contact>
</xsl:copy-of>
</xsl:template>
If you wanted to get the name and phone out of a separate XML document in the file system, you'd start the XSLT with something like this:
<xsl:variable name="contact" value="document('contact.xml')"/>
<xsl:variable name="contactName" value="$contact/*/name[1]'/>
<xsl:variable name="contactPhone" value=$contact/*/phone[1]'>
That reads in contact.xml and finds the first name and phone element under the top-level element (using * in the pattern means that you don't care what the top-level element's name is).
use the xsl:param as a global parameter in the header of your xsl stylesheet.
<xsl:param name="newname"/>
<xsl:param name="newphone"/>
fill the new params with your xslt engine and then add the new item via a template:
(...)
<xsl:template match="myagenda">
<xsl:apply-templates select="contact"/>
<xsl:if test="string-length($newname)>0">
<xsl:element name="contact">
<xsl:element name="name">
<xsl:value-of select="$newname"/>
</xsl:element>
<xsl:element name="phone">
<xsl:value-of select="$newphone"/>
</xsl:element>
</xsl:element>
</xsl:if>
</xsl:template>
(...)
XSLT converts 1 xml file to another xml or text file.

How can we convert XML file to CSV?

I am having an XML file
<?xml version="1.0" encoding="ISO-8859-1"?>
<Results>
<Row>
<COL1></COL1>
<COL2>25.00</COL2>
<COL3>2009-07-06 15:49:34.984</COL3>
<COL4>00001720</COL4>
</Row>
<Row>
<COL1>RJ</COL1>
<COL2>26.00</COL2>
<COL3>2009-07-06 16:04:16.156</COL3>
<COL4>00001729</COL4>
</Row>
<Row>
<COL1>SD</COL1>
<COL2>28.00</COL2>
<COL3>2009-07-06 16:05:04.375</COL3>
<COL4>00001721</COL4>
</Row>
</Results>
I have to convert this XML into CSV file. I have heard we can do such thing using XSLT. How can i do this in Java ( with/without XSLT )?
Using XSLT is often a bad idea. Use Apache Commons Digester. It's fairly easy to use - here's a rough idea::
Digester digester = new Digester();
digester.addObjectCreate("Results/Row", MyRowHolder.class);
digester.addCallMethod("Results/Row/COL1","addCol", 0);
// Similarly for COL2, etc.
digester.parse("mydata.xml");
This will create a MyRowHolder instance (where this is a class you provide). This class would have a addCol() method which would be called for each <COLn> with the contents of that tag.
In pseudo code:
loop through the rows:
loop through all children of `Row`:
write out the text
append a comma
new line
That quick little loop will write a comma at the end of each line, but I'm sure you can figure out how to remove that.
For actually parsing the XML, I suggest using JDOM. It has a pretty intuitive API.
In XSLT 1.0:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="ISO-8859-1" />
<xsl:template match="/Results">
<xsl:apply-templates select="Row" />
</xsl:template>
<xsl:template match="Row">
<xsl:apply-templates select="*" />
<xsl:if test="not(last())">
<xsl:value-of select="'
'" />
</xsl:if>
</xsl:template>
<xsl:template match="Row/*">
<xsl:value-of select="." />
<xsl:if test="not(last())">
<xsl:value-of select="','" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
If your COL* values can contain commas, you could wrap the values in double quotes:
<xsl:template match="Row/*">
<xsl:value-of select="concat('"', ., '"')" />
<!-- ... --->
If they can contain commas and double quotes, things could get a bit more complex due to the required escaping. You know your data, you'll be able to decide how to best format the output. Using a different separator (e.g. TAB or a pipe symbol) is also an option.
Read the XML file in.
Loop throught each record and add it to a csv file.
With XSLT you can use the JAXP interface to the XSLT processor and then use <xsl:text> in your stylesheet to convert to text output.
<xsl:text>
</xsl:text>
generates a newline. for example.
Use the straightforward SAX API via the standard Java JAXP package. This will allow you to write a class that receives events for each XML element your reader encounters.
Briefly:
read your XML in using SAX
record text values via the SAX DefaultHandler characters() method
when you get an end event for a COL, record this string value
when you get the ROW end event, simply write out a comma separated line of previously recorded values

Categories

Resources