Dom parsing xml problems - java

I have a simple .xml file and need to parse it. The file is the following:
<table name="agents">
<row name="agent" password="pass" login="agent" ext_uid="133"/>
</table>
I need to get values of name, password, login, ext_uid to create a DB record.
What I have done for this:
created an or.w3c.dom.Document:
public Document getDocument(String fileName){
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
f.setValidating(false);
DocumentBuilder builder = f.newDocumentBuilder();
return builder.parse(new File(fileName));
}
next I'm trying to print values:
document = getDocument(fileName);
NodeList nodes = document.getChildNodes();
for (int i=0; i<nodes.getLength(); i++){
Node node = nodes.item(i);
if(node.getNodeType() == Node.ELEMENT_NODE){
NodeList listofNodes = node.getChildNodes();
for(int j=0; j<listofNodes.getLength(); j++){
if(node.getNodeType() == Node.ELEMENT_NODE){
Node childNode = listofNodes.item(j);
System.out.println(childNode.getNodeValue()+" " + childNode.getNodeName());
}
}
}
}
I use this because I'm trying to find out how to get values: childNode.getNodeValue()+" " + childNode.getNodeName()
but the result is the following:
#text
null row
#text
in the first and te third cases the NodeValue is empty and in the second case it is null, that means, I guess that there no NodeValue at all.
So my question is how to get values of name, password, login, ext_uid?

childNode.getNodeValue() is obviously null as its an empty tag. You have to look for attributes
Node childNode = listofNodes.item(j);
Element e = (Element)childNode;
String name = e.getAttribute("name");
String password= e.getAttribute("password");
String login= e.getAttribute("login");
String ext_uid= e.getAttribute("ext_uid");

The <row> element has no value, it only has attributes. If it had a value it would look more like <row>this would be the value returned from getNodeValue()</row>.
One way to get the data is to iterate the XML node attributes, for example:
NamedNodeMap attrs = childNode.getAttributes();
if (attrs != null) {
for (int k = 0; k < attrs.getLength(); k++) {
System.out.println("Attribute: "
+ attrs.item(k).getNodeName() + " = "
+ attrs.item(k).getNodeValue());
}
}
The output of your code is showing #text due to the carriage returns (\n characters) in the example XML file, which, according the specification, should be preserved. The null in the example output is the empty node value from the value-less <row> element.

Use XPath instead:
XPath xp = XPathFactory.newInstance().newXPath();
System.out.println(xp.evaluate("/table/row/#name", doc));
System.out.println(xp.evaluate("/table/row/#password", doc));
System.out.println(xp.evaluate("/table/row/#login", doc));
System.out.println(xp.evaluate("/table/row/#ext_uid", doc));

Related

Attempting to use Java & Xpath to updated the value of any selected node inside an XML doc

I have developed GUI tool the displays an XML document as an editable JTree, and the user can select a node in the JTree and attempt to change the actual nodes value in the XML document.
The problem that I'm having is with constructing the correct Xpath query that attempts the actual update.
Here is GUI of the JTree showing which element was selected & should be edited:
Its a very large XMl, so here the collapsed snippet of the XML:
UPDATE (IGNORE ATTEMPT 1 & 2, 1ST ISSUE WAS RESOLVED, GO TO ATTEMPTS 3 & 4)
Attempt 1 # (relevant Java method that attempts to create XPath query to update a nodes value):
public void updateXmlData(JTree jTree, org.w3c.dom.Document doc, TreeNode parentNode, String oldValue, String newValue) throws XPathExpressionException {
System.out.println("Selected path=" + jTree.getSelectionPath().toString());
String[] pathTockens = jTree.getSelectionPath().toString().split(",");
StringBuilder sb = new StringBuilder();
//for loop to construct xpath query
for (int i = 0; i < pathTockens.length - 1; i++) {
if (i == 0) {
sb.append("//");
} else {
sb.append(pathTockens[i].trim());
sb.append("/");
}
}//end for loop
sb.append("text()");
System.out.println("Constructed XPath Query:" + sb.toString());
//new xpath
XPath xpath = XPathFactory.newInstance().newXPath();
//compile query
NodeList nodes = (NodeList) xpath.compile(sb.toString()).evaluate(doc, XPathConstants.NODESET);
//Make the change on the selected nodes
for (int idx = 0; idx < nodes.getLength(); idx++) {
Node value = nodes.item(idx).getAttributes().getNamedItem("value");
String val = value.getNodeValue();
value.setNodeValue(val.replaceAll(oldValue, newValue));
}
//set the new updated xml doc
SingleTask.currentTask.setDoc(doc);
}
Console logs:
Selected path=[C:\Users\xyz\Documents\XsdToXmlFiles\sampleIngest.xml, Ingest, Property_Maps, identifier, identifieXYZ]
Constructed XPath Query://Ingest/Property_Maps/identifier/text()
Jan 26, 2021 2:04:16 PM com.xyz.XmlToXsdValidator.Views.EditXmlTreeNodeDialogJFrame jButtonOkEditActionPerformed
SEVERE: null
javax.xml.transform.TransformerException: Unable to evaluate expression using this context
at com.sun.org.apache.xpath.internal.XPath.execute(XPath.java:368)
As you can see in the logs:
Selected path=[C:\Users\xyz\Documents\XsdToXmlFiles\sampleIngest.xml, Ingest, Property_Maps, identifier, identifieXYZ]
Constructed XPath Query://Ingest/Property_Maps/identifier/text()
The paths are correct, basically Ingest->Property_Maps->identifier->text()
But Im getting:
javax.xml.transform.TransformerException: Unable to evaluate expression using this context
Attempt 2 # (relevant Java method that attempts to create XPath query to update a nodes value):
public void updateXmlData(JTree jTree, org.w3c.dom.Document doc, TreeNode parentNode, String oldValue, String newValue) throws XPathExpressionException {
// Locate the node(s) with xpath
System.out.println("Selected path=" + jTree.getSelectionPath().toString());
String[] pathTockens = jTree.getSelectionPath().toString().split(",");
StringBuilder sb = new StringBuilder();
//loop to construct xpath query
for (int i = 0; i < pathTockens.length - 1; i++) {
if (i == 0) {
sb.append("//");
} else {
sb.append(pathTockens[i].trim());
sb.append("/");
}
}//end loop
sb.append("[text()=");
sb.append("'");
sb.append(oldValue);
sb.append("']");
int lastIndexOfPathChar = sb.lastIndexOf("/");
sb.replace(lastIndexOfPathChar, lastIndexOfPathChar + 1, "");
System.out.println("Constructed XPath Query:" + sb.toString());
//new xpath instance
XPath xpath = XPathFactory.newInstance().newXPath();
NodeList nodes = (NodeList) xpath.evaluate(sb.toString(), doc, XPathConstants.NODESET);
//Make the change on the selected nodes
for (int idx = 0; idx < nodes.getLength(); idx++) {
Node value = nodes.item(idx).getAttributes().getNamedItem("value");
String val = value.getNodeValue();
value.setNodeValue(val.replaceAll(oldValue, newValue));
}
SingleTask.currentTask.setDoc(doc);
}
I was able to resolve the exception based Andreas comment, and there are no more exceptions/errors, however the XPath query does not find selected nodes. Returns empty
New updated code:
Attempt # 3 Using custom namespace resolver. References: https://www.kdgregory.com/index.php?page=xml.xpath
public boolean updateXmlData(JTree jTree, org.w3c.dom.Document doc, TreeNode parentNode, String oldValue, String newValue) throws XPathExpressionException {
System.out.println("Selected path=" + jTree.getSelectionPath().toString());
boolean changed = false;
// Locate the node(s) with xpath
String[] pathTockens = jTree.getSelectionPath().toString().split(",");
StringBuilder sb = new StringBuilder();
//loop to construct xpath query
for (int i = 0; i < pathTockens.length - 1; i++) {
if (i == 0) {
//do nothing
} else if (i == 1) {
sb.append("/ns:" + pathTockens[i].trim());
} else if (i > 1 && i != pathTockens.length - 1) {
sb.append("/ns:" + pathTockens[i].trim());
} else {
//sb.append("/" + pathTockens[i].trim());
}
}//end loop
sb.append("[text()=");
sb.append("'");
sb.append(oldValue);
sb.append("']");
System.out.println("Constructed XPath Query:" + sb.toString());
//new xpath instance
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
xpath.setNamespaceContext(new UniversalNamespaceResolver(SingleTask.currentTask.getXsdFile().getXsdNameSpace()));
NodeList nodes = (NodeList) xpath.evaluate(sb.toString(), doc, XPathConstants.NODESET);
//start for
Node node;
String val = null;
for (int idx = 0; idx < nodes.getLength(); idx++) {
if (nodes.item(idx).getAttributes() != null) {
node = nodes.item(idx).getAttributes().getNamedItem("value");
if (node != null) {
val = node.getNodeValue();
node.setNodeValue(val.replaceAll(oldValue, newValue));
changed = true;
break;
}//end if node is found
}
}//end for
//set the new updated xml doc
SingleTask.currentTask.setDoc(doc);
return changed;
}
Class that implements custom namespace resolver:
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import org.w3c.dom.Document;
/**
*
* References:https://www.kdgregory.com/index.php?page=xml.xpath
*/
//custom NamespaceContext clss implementation
public class UniversalNamespaceResolver implements NamespaceContext
{
private String _prefix = "ns";
private String _namespaceUri=null;
private List<String> _prefixes = Arrays.asList(_prefix);
public UniversalNamespaceResolver(String namespaceResolver)
{
_namespaceUri = namespaceResolver;
}
#Override
#SuppressWarnings("rawtypes")
public Iterator getPrefixes(String uri)
{
if (uri == null)
throw new IllegalArgumentException("UniversalNamespaceResolver getPrefixes() URI may not be null");
else if (_namespaceUri.equals(uri))
return _prefixes.iterator();
else if (XMLConstants.XML_NS_URI.equals(uri))
return Arrays.asList(XMLConstants.XML_NS_PREFIX).iterator();
else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(uri))
return Arrays.asList(XMLConstants.XMLNS_ATTRIBUTE).iterator();
else
return Collections.emptyList().iterator();
}
#Override
public String getPrefix(String uri)
{
if (uri == null)
throw new IllegalArgumentException("nsURI may not be null");
else if (_namespaceUri.equals(uri))
return _prefix;
else if (XMLConstants.XML_NS_URI.equals(uri))
return XMLConstants.XML_NS_PREFIX;
else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(uri))
return XMLConstants.XMLNS_ATTRIBUTE;
else
return null;
}
#Override
public String getNamespaceURI(String prefix)
{
if (prefix == null)
throw new IllegalArgumentException("prefix may not be null");
else if (_prefix.equals(prefix))
return _namespaceUri;
else if (XMLConstants.XML_NS_PREFIX.equals(prefix))
return XMLConstants.XML_NS_URI;
else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix))
return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
else
return null;
}
}
Console Output:
Selected path=[C:\Users\xyz\DocumentsIngest_LDD.xml, Ingest_LDD, Property_Maps, identifier, identifier1]
Constructed XPath: Query:/ns:Ingest_LDD/ns:Property_Maps/ns:identifier[text()='identifier1']
Attempt #4 (Without custom namespace resolver):
public boolean updateXmlData(JTree jTree, org.w3c.dom.Document doc, TreeNode parentNode, String oldValue, String newValue) throws XPathExpressionException {
System.out.println("Selected path=" + jTree.getSelectionPath().toString());
boolean changed = false;
// Locate the node(s) with xpath
String[] pathTockens = jTree.getSelectionPath().toString().split(",");
StringBuilder sb = new StringBuilder();
//loop to construct xpath query
for (int i = 0; i < pathTockens.length - 1; i++) {
if (i == 0) {
//do nothing
} else if (i == 1) {
sb.append("/" + pathTockens[i].trim());
} else if (i > 1 && i != pathTockens.length - 1) {
sb.append("/" + pathTockens[i].trim());
} else {
//sb.append("/" + pathTockens[i].trim());
}
}//end loop
sb.append("[text()=");
sb.append("'");
sb.append(oldValue);
sb.append("']");
System.out.println("Constructed XPath Query:" + sb.toString());
//new xpath instance
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
//WITHOUT CUSTOM NAMESPACE CONTEXT xpath.setNamespaceContext(new UniversalNamespaceResolver(SingleTask.currentTask.getXsdFile().getXsdNameSpace()));
NodeList nodes = (NodeList) xpath.evaluate(sb.toString(), doc, XPathConstants.NODESET);
//start for
Node node;
String val = null;
for (int idx = 0; idx < nodes.getLength(); idx++) {
if (nodes.item(idx).getAttributes() != null) {
node = nodes.item(idx).getAttributes().getNamedItem("value");
if (node != null) {
val = node.getNodeValue();
node.setNodeValue(val.replaceAll(oldValue, newValue));
changed = true;
break;
}//end if node is found
}
}//end for
//set the new updated xml doc
SingleTask.currentTask.setDoc(doc);
return changed;
}
Console Output:
Selected path=[C:\Users\anaim\Documents\XsdToXmlFiles\sampleIngest_LDD.xml, Ingest_LDD, Property_Maps, identifier, identifier1]
Constructed XPath Query:/Ingest_LDD/Property_Maps/identifier[text()='identifier1']
I actually manually wrote the XPath query online using (https://www.freeformatter.com/xpath-tester.html#ad-output)
Sorry, I cant provide the sample XMl, its way too large.
The manual XPath query was:
/Ingest_LDD/Property_Maps/identifier[text()='identifier1']
And the online tool successfully found the text & outputted:
Element='<identifier xmlns="http://pds.nasa.gov/pds4/pds/v1">identifier1</identifier>'
Therefore my code under attempt #4 & the query should work?
UPDATED ATTEMPTS AFTER USER INPUT:
Attempt #5 (based on response from user, namespace aware = TRUE ), relevant code is below
factory.setNamespaceAware(true);
doc = dBuilder.parse(xmlFile);
if (doc!=null)
{
//***NOTE program comes meaning doc is NOT null, however inspecting it shows [#document: null]
doc.getDocumentElement().normalize();
}
xpath.setNamespaceContext(new UniversalNamespaceResolver(SingleTask.currentTask.getXsdFile().getXsdNameSpace()));
Node node = (Node) xpath.evaluate(sb.toString(), doc, XPathConstants.NODE);
if (node!=null)
{
// See https://docs.oracle.com/javase/9/docs/api/org/w3c/dom/Node.html#setTextContent-java.lang.String-
node.setTextContent(newValue);
SingleTask.currentTask.setDoc(doc);
}
Output (again unable to find the node):
Selected path=[C:\Users\xyz\Documents\XsdToXmlFiles\sampleIngest_LDD.xml, Ingest_LDD, name, name1]
Constructed XPath Query:/Ingest_LDD/name[text()='name1']
Error changing value!
Attempt #6 (based on response from user, namespace aware = FALSE )
factory.setNamespaceAware(false);
doc = dBuilder.parse(xmlFile);
if (doc!=null)
{
//***NOTE program comes meaning doc is NOT null, however inspecting it shows [#document: null]
doc.getDocumentElement().normalize();
}
//COMMENTED OUT , SINCE NAMESPACE AWARE FALSE xpath.setNamespaceContext(new UniversalNamespaceResolver(SingleTask.currentTask.getXsdFile().getXsdNameSpace()));
Node node = (Node) xpath.evaluate(sb.toString(), doc, XPathConstants.NODE);
if (node!=null)
{
// See https://docs.oracle.com/javase/9/docs/api/org/w3c/dom/Node.html#setTextContent-java.lang.String-
node.setTextContent(newValue);
SingleTask.currentTask.setDoc(doc);
}
Output (again unable to find the node):
Selected path=[C:\Users\xyz\Documents\XsdToXmlFiles\sampleIngest_LDD.xml, Ingest_LDD, name, name1]
Constructed XPath Query:/Ingest_LDD/name[text()='name1']
Error changing value!
The document that is being returned as [#document: null] may not actually be the problem according to(DocumentBuilder.parse(InputStream) returns null)???
Attempt # 7 (namespace aware FALSE)
Also NamedNodeMap namedNodeMap = doc.getAttributes(); returns NULL.
However, Node firstChild = doc.getFirstChild() actually returns valid element!
I passed firstChild to xpath.evaluate(sb.toString(), firstChild , XPathConstants.NODE); but again the node desired node was not found.
Output (again unable to find the node):
Selected path=[C:\Users\xyz\Documents\XsdToXmlFiles\sampleIngest_LDD.xml, Ingest_LDD, name, name1]
Constructed XPath Query:/Ingest_LDD/name[text()='name1']
Error changing value!
Attempt # 8 (namespace aware false)
I also attemped to pass in doc.getChildNodes() to xpath.evaluate() rather than doc object as final desperate atteempt, see snippet below.
if (doc != null) {
NodeList nodes = (NodeList) xpath.evaluate(sb.toString(), doc.getChildNodes(), XPathConstants.NODESET);
String val = null;
Node node;
for (int idx = 0; idx < nodes.getLength(); idx++) {
if (nodes.item(idx).getAttributes() != null) {
node = nodes.item(idx).getAttributes().getNamedItem("value");
if (node != null) {
val = node.getNodeValue();
node.setNodeValue(val.replaceAll(oldValue, newValue));
changed = true;
break;
}//end if node is found
}
}//end for
}
Output (again unable to find the node):
Selected path=[C:\Users\xyz\Documents\XsdToXmlFiles\sampleIngest_LDD.xml, Ingest_LDD, name, name1]
Constructed XPath Query:/Ingest_LDD/name[text()='name1']
Error changing value!
For the test you performed online it seems your XML file contains namespace information.
With that information in mind, probably both of your examples of XPath evaluation would work, or not, dependent on several things.
For example, you probably can use the attempt #4, and the XPath evaluation will be adequate, if you are using a non namespace aware (the default) DocumentBuilderFactory and you o not provide any namespace information in your XPath expression.
But the XPath evaluation in attempt #3 can also be adequate if the inverse conditions apply, i.e., you are using a namespace aware DocumentBuilderFactory:
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
f.setNamespaceAware(true);
and you provide namespace information in your XPath expression and a convenient NamespaceContext implementation. Please, see this related SO question and this great IBM article.
Be aware that you do not need to provide the same namespace prefixes in both your XML file an XPath expression, the only requirement is namespace awareness in XML (XPath is always namespace aware).
Given that conditions, I think you can apply both approaches.
In any case, I think the problem may have to do with the way you are dealing with the actual text replacement: you are looking for a node with a value attribute, and reviewing the associated XML Schema this attribute does not exist.
Please, consider instead the following approach:
// You can get here following both attempts #3 an #4
Node node = (Node) xpath.evaluate(sb.toString(), doc, XPathConstants.NODE);
boolean changed = node != null;
if (changed) {
// See https://docs.oracle.com/javase/9/docs/api/org/w3c/dom/Node.html#setTextContent-java.lang.String-
node.setTextContent(newValue);
SingleTask.currentTask.setDoc(doc);
}
return changed;
This code assumes that the selected node will be unique to work properly.
Although probably unlike, please, be aware that the way in which you are constructing the XPath selector from the JTree model can provide duplicates if you define the same value for repeated elements in your XML. Consider the elements external_id_property_maps in your screenshot, for instance.
In order to avoid that, you can take a different approach when constructing the XPath selector.
It is unclear for your code snippet, but probably you are using DefaultMutableTreeNode as the base JTree node type. If that is the case, you can associate with every node the arbitrary information you need to.
Consider for example the creation of a simple POJO with two fields, the name of the Element that the node represents, and some kind of unique, generated, id, let's name it uid or uuid to avoid confusion with the id attribute, most likely included in the original XML document.
This uid should be associated with every node. Maybe you can take advantage of the JTree creation process and, while processing every node of your XML file, include this attribute as well, generated using the UUID class, for example.
Or you can apply a XSLT transform to the original XML document prior to representation:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:attribute name="uid">
<xsl:value-of select="generate-id(.)"/>
</xsl:attribute>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With this changes, your XPath query should looks like:
/ns:Ingest_LDD[#uid='w1ab1']/ns:Property_Maps[#uid='w1ab1a']/ns:identifier[#uid='w1ab1aq']
Of course, it will be necessary to modify the code devoted to the construction of this expression from the selected path of the JTree to take the custom object into account.
You can take this approach to the limit and use a single selector based solely in this uid attribute, although I think that for performance reasons it will be not appropriate:
//*[#uid='w1ab1']
Putting it all together, you can try something like the following.
Please, consider this XML file:
<?xml version="1.0" encoding="utf-8" ?>
<Ingest_LDD xmlns="http://pds.nasa.gov/pds4/pds/v1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pds.nasa.gov/pds4/pds/v1 https://pds.nasa.gov/pds4/pds/v1/PDS4_PDS_1700.xsd">
<!-- Please, forgive me, I am aware that the document is not XML Schema conformant,
only for exemplification of the default namespace -->
<Property_Maps>
<identifier>identifier1</identifier>
</Property_Maps>
</Ingest_LDD>
First, let's parse the document:
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
// As the XML contains namespace, let's configure the parser namespace aware
// This will be of relevance when evaluating XPath
builderFactory.setNamespaceAware(true);
DocumentBuilder builder = builderFactory.newDocumentBuilder();
// Parse the document from some source
Document document = builder.parse(...);
// See http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-how-does-it-work
document.getDocumentElement().normalize();
Now, create a JTree structure corresponding to the input XML file. First, let's create a convenient POJO to store the required tree node information:
public class NodeInformation {
// Node name
private String name;
// Node uid
private String did;
// Node value
private String value;
// Setters and getters
// Will be reused by DefaultMutableTreeNode
#Override
public String toString() {
return this.name;
}
}
Convert the XML file to its JTree counterpart:
// Get a reference to root element
Element rootElement = document.getDocumentElement();
// Create root tree node
DefaultMutableTreeNode rootTreeNode = getNodeInformation(rootElement);
// Traverse DOM
traverse(rootTreeNode, rootElement);
// Create tree and tree model based on the computed root tree node
DefaultTreeModel treeModel = new DefaultTreeModel(rootTreeNode);
JTree tree = new JTree(treeModel);
Where:
private NodeInformation getNodeInformation(Node childElement) {
NodeInformation nodeInformation = new NodeInformation();
String name = childElement.getNodeName();
nodeInformation.setName(name);
// Provide a new unique identifier for every node
String uid = UUID.randomUUID().toString();
nodeInformation.setUid(uid);
// Uhnn.... We need to associate the new uid with the DOM node as well.
// There is nothing wrong with it but mutating the DOM in this way in
// a method that should be "read-only" is not the best solution.
// It would be interesting to study the above-mentioned XSLT approach
chilElement.setAttribute("uid", uid);
// Compute node value
StringBuffer buffer = new StringBuffer();
NodeList childNodes = childElement.getChildNodes();
boolean found = false;
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if (node.getNodeType() == Node.TEXT_NODE) {
String value = node.getNodeValue();
buffer.append(value);
found = true;
}
}
if (found) {
nodeInformation.setValue(buffer.toString());
}
}
And:
// Finds all the child elements and adds them to the parent node recursively
private void traverse(DefaultMutableTreeNode parentTreeNode, Node parentXMLElement) {
NodeList childElements = parentXMLElement.getChildNodes();
for(int i=0; i<childElements.getLength(); i++) {
Node childElement = childElements.item(i);
if (childElement.getNodeType() == Node.ELEMENT_NODE) {
DefaultMutableTreeNode childTreeNode =
new DefaultMutableTreeNode
(getNodeInformation(childElement));
parentTreeNode.add(childTreeNode);
traverse(childTreeNode, childElement);
}
}
}
Although the NamespaceContext implementation you provided looks fine, please, at a first step, try something simpler, to minimize the possibility of error. See the provided implementation below.
Then, your updateXMLData method should looks like:
public boolean updateXmlData(JTree tree, org.w3c.dom.Document doc, TreeNode parentNode, String oldValue, String newValue) throws XPathExpressionException {
boolean changed = false;
TreePath selectedPath = tree.getSelectionPath();
int count = getPathCount();
StringBuilder sb = new StringBuilder();
NodeInformation lastNodeInformation;
if (count > 0) {
for (int i = 1; i < trp.getPathCount(); i++) {
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) trp.getPathComponent(i);
NodeInformation nodeInformation = (NodeInformation) treeNode.getUserObject();
sb.append(String.format("/ns:%s[#uid='%s']", nodeInformation.getName(), nodeInformation.getUid());
lastNodeInformation = nodeInformation;
}
}
System.out.println("Constructed XPath Query:" + sb.toString());
// Although the `NamespaceContext` implementation you provided looks
// fine, please, at a first step, try something simpler, to minimize the
// possibility of error. For example:
NamespaceContext nsContext = new NamespaceContext() {
public String getNamespaceURI(String prefix) {
if (prefix == null) {
throw new IllegalArgumentException("No prefix provided!");
} else if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) {
return "http://pds.nasa.gov/pds4/pds/v1";
} else if (prefix.equals("ns")) {
return "http://pds.nasa.gov/pds4/pds/v1";
} else {
return XMLConstants.NULL_NS_URI;
}
}
public String getPrefix(String namespaceURI) {
// Not needed in this context.
return null;
}
public Iterator getPrefixes(String namespaceURI) {
// Not needed in this context.
return null;
}
};
//new xpath instance
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
// As the parser is namespace aware, we can safely use XPath namespaces
xpath.setNamespaceContext(nsContext);
Node node = (Node) xpath.evaluate(sb.toString(), doc, XPathConstants.NODE);
boolean changed = node != null;
if (changed) {
// See https://docs.oracle.com/javase/9/docs/api/org/w3c/dom/Node.html#setTextContent-java.lang.String-
node.setTextContent(newValue);
SingleTask.currentTask.setDoc(doc);
// Probably the information has been updated in the node, but just in case:
lastNodeInformation.setValue(newValue);
}
return changed;
}
The generated XPath expression will look like:
/ns:Ingest_LDD[#uid='w1ab1']/ns:Property_Maps[#uid='w1ab1a']/ns:identifier[#uid='w1ab1aq']
If you want to use the default namespace, you can also try with:
/:Ingest_LDD[#uid='w1ab1']/:Property_Maps[#uid='w1ab1a']/:identifier[#uid='w1ab1aq']
Please, be aware that I haven't tested the code, but I hope you get the idea.
Just for clarification, in order to give you a proper answer, as mentioned before, if you now remove or comment this line of code:
builderFactory.setNamespaceAware(true);
Then, the XPath expression:
/ns:Ingest_LDD[#uid='w1ab1']/ns:Property_Maps[#uid='w1ab1a']/ns:identifier[#uid='w1ab1aq']
will no longer find the required node. Now, if you remove the namespace information from the XPath expression:
/Ingest_LDD[#uid='w1ab1']/Property_Maps[#uid='w1ab1a']/identifier[#uid='w1ab1aq']
It will find the right node again.

Java, XPath Expression to read all node names, node values, and attributes

I need help in make an xpath expression to read all node names, node values, and attributes in an xml string. I made this:
private List<String> listOne = new ArrayList<String>();
private List<String> listTwo = new ArrayList<String>();
public void read(String xml) {
try {
// Turn String into a Document
Document document = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes()));
// Setup XPath to retrieve all tags and values
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList nodeList = (NodeList) xPath.evaluate("//text()[normalize-space()='']", document, XPathConstants.NODESET);
// Iterate through nodes
for(int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
listOne.add(node.getNodeName());
listTwo.add(node.getNodeValue());
// Another list to hold attributes
}
} catch(Exception e) {
LogHandle.info(e.getMessage());
}
}
I found the expression //text()[normalize-space()=''] online; however, it doesn't work. When I get try to get the node name from listOne, it is just #text. I tried //, but that doesn't work either. If I had this XML:
<Data xmlns="Somenamespace.nsc">
<Test>blah</Test>
<Foo>bar</Foo>
<Date id="2">12242016</Date>
<Phone>
<Home>5555555555</Home>
<Mobile>5555556789</Mobile>
</Phone>
</Data>
listOne[0] should hold Data, listOne[1] should hold Test, listTwo[1] should hold blah, etc... All the attributes will be saved in another parallel list.
What expression should xPath evaluate?
Note: The XML String can have different tags, so I can't hard code anything.
Update: Tried this loop:
NodeList nodeList = (NodeList) xPath.evaluate("//*", document, XPathConstants.NODESET);
// Iterate through nodes
for(int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
listOne.add(i, node.getNodeName());
// If null then must be text node
if(node.getChildNodes() == null)
listTwo.add(i, node.getTextContent());
}
However, this only gets the root element Data, then just stops.
//* will select all element nodes, //#* all attribute nodes. However, an element node does not have a meaningful node value in the DOM, so you would need to read out getTextContent() instead of getNodeValue.
As you seem to consider an element with child elements to have a "null" value I think you need to check whether there are any child elements:
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
Document doc = docBuilder.parse("sampleInput1.xml");
XPathFactory fact = XPathFactory.newInstance();
XPath xpath = fact.newXPath();
NodeList allElements = (NodeList)xpath.evaluate("//*", doc, XPathConstants.NODESET);
ArrayList<String> elementNames = new ArrayList<>();
ArrayList<String> elementValues = new ArrayList<>();
for (int i = 0; i < allElements.getLength(); i++)
{
Node currentElement = allElements.item(i);
elementNames.add(i, currentElement.getLocalName());
elementValues.add(i, xpath.evaluate("*", currentElement, XPathConstants.NODE) != null ? null : currentElement.getTextContent());
}
for (int i = 0; i < elementNames.size(); i++)
{
System.out.println("Name: " + elementNames.get(i) + "; value: " + (elementValues.get(i)));
}
For the sample input
<Data xmlns="Somenamespace.nsc">
<Test>blah</Test>
<Foo>bar</Foo>
<Date id="2">12242016</Date>
<Phone>
<Home>5555555555</Home>
<Mobile>5555556789</Mobile>
</Phone>
</Data>
the output is
Name: Data; value: null
Name: Test; value: blah
Name: Foo; value: bar
Name: Date; value: 12242016
Name: Phone; value: null
Name: Home; value: 5555555555
Name: Mobile; value: 5555556789

Xpath expression evaluates to an empty nodelist

I am having trouble parsing an xml file and retrieve data from it. Below is the xml and code snippet.
-----XML (test.xml)-----
<?xml version="1.0" encoding="utf-8"?>
<root>
<Server>
<IPAddress>xxx.xxx.xxx.xxx</IPAddress>
<UserName>admin</UserName>
<Password>admin</Password>
</Server>
-----Code Snippet: -----
public static String getInput(String element)
{
String value = "";
try {
File inputFile = new File("test.xml");
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dbFactory.newDocumentBuilder();
Document inputData = builder.parse(inputFile);
inputData.getDocumentElement().normalize();
String[] elementArray = element.split("/");
XPath xPath = XPathFactory.newInstance().newXPath();
String xpathExpression = element;
System.out.println("Xpath Expression:" + xpathExpression);
NodeList node = (NodeList) xPath.compile(xpathExpression).evaluate(inputData, XPathConstants.NODESET);
System.out.println(node.getLength());
if (null != node){
System.out.println(node.getLength());
for (int i=0; i<node.getLength(); i++){
System.out.println(i);
System.out.println("Node count =" + node.getLength() + ";" +
"Node Name =" + node.item(i).getNodeName());
if (node.item(i).getNodeName() == elementArray[1]){
System.out.println(node.item(i).getNodeName()+ "=" + node.item(i).getNodeValue());
value = node.item(i).getNodeValue();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
The code compiles OK. While running, it just doesn't seem to find the nodes "Server" and it's child "IPAddress". The call to getInput() above would come from main in the format below:
getInput("Server/IPAddress");
Not sure where it's going wrong and I am really new to Xpath. I was wondering if someone can help.
Thanks!
The outermost element is <root/>, not <server/>. Your query needs to be
getInput("root/Server/IPAddress")
if you want to use the full path, or even
getInput("/root/Server/IPAddress")
to indicate you're starting at the root element. Alternatively, you could have XPath to search for all server elements all over the document:
getInput("//Server/IPAddress")
All of those will output
Xpath Expression:root/Server/IPAddress
1
1
0
Node count =1;Node Name =IPAddress
instead of
Xpath Expression:Server/IPAddress
0
0
You could somehow prepend one of the prefixes of your choice in the getInput() function, of course.

java.lang.ClassCastException, DeepNodeListImpl cannot be cast

here is my code :
public void Login() {
try{
DocumentBuilderFactory builderfactory = DocumentBuilderFactory.newInstance();
DocumentBuilder db = builderfactory.newDocumentBuilder();
File path = new File("src/dataPengguna/dataPengguna.xml");
Document doc = db.parse(path);
Element pengguna = (Element) doc.getElementsByTagName("pengguna");
NodeList list = pengguna.getElementsByTagName("user");
for (int i = 0; i < list.getLength(); i++) {
Element user = (Element) list.item(i);
Node username = user.getElementsByTagName("username").item(i);
Node password = user.getElementsByTagName("password").item(i);
if(loginuser.getText().equals(username.getTextContent())
&& loginpass.getText().equals(password.getTextContent())){
JOptionPane.showMessageDialog(rootPane, "welcome");
}
}
}catch(Exception e){
e.printStackTrace();
}
}
here is my xml :
<?xml version="1.0" encoding="UTF-8"?>
<pengguna>
<user>
<nama>septian</nama>
<username>septiansykes</username>
<password>1234</password>
<status>belumpinjam</status>
</user>
<user>
<nama>koko</nama>
<username>kokosan</username>
<password>12er</password>
<status>belumpinjam</status>
</user>
<user>
<nama>tamrin</nama>
<username>tamrincs</username>
<password>gt234</password>
<status>belumpinjam</status>
</user>
</pengguna>
and here is my error :
java.lang.ClassCastException:com.sun.org.apache.xerces.internal.dom.DeepNodeListImpl cannot be cast to org.w3c.dom.Element
i try to get the element at the xml file, i want to check the element username and password, but there is an error about the cast class, it's seem difficult for me,... thanks before
This is the problem:
Element pengguna = (Element) doc.getElementsByTagName("pengguna");
getElementsByTagName doesn't return a single element - it returns multiple elements. You probably want something like:
NodeList penggunas = doc.getElementsByTagName("pengguna");
if (penggunas.getLength() != 1) {
// Handle this - e.g. throw an exception
}
Element pengguna = (Element) penggunas.item(0);
EDIT: Later, you've got a bug here:
Node username = user.getElementsByTagName("username").item(i);
Node password = user.getElementsByTagName("password").item(i);
This should be:
Node username = user.getElementsByTagName("username").item(0);
Node password = user.getElementsByTagName("password").item(0);
You're already within the user element - so you always want the first username and password elements within that element. Otherwise you're asking for the second username element within the second user element, the third username element within the third user element etc. The numbering is relevant to the element that you're in, not some global count.
getElementByTagName() returns a NodeList and you try to cast it to an Element. This line is incorrect and will give you the ClassCastException:
Element pengguna = (Element) doc.getElementsByTagName("pengguna");

Getting child's value as a String

Best way to explain myself is to show you a piece of code:
This is my XML file I'm parsing:
<module>
<name>name1</name>
<type>type</type>
<content>
<p>This is some piece of code that should be treated as a full string, even that 'p' tag, because I want to use all content inside p tag for a webview in android.
</p>
<h1>This is a big classy title in html</h1>
</content>
</module>
As you can read in the p tag, basically I want to get the <content> tag's content and save it into a String to be treated. So at the end, I want to have a String initializated like:
String content = "<p> This is some piece.......</p> <h1>This is....</h1>";
This is my code that I'm using to get <name>, <type> values:
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(contingut);
doc.getDocumentElement().normalize();
NodeList nodes = doc.getElementsByTagName("module");
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
Element element = (Element) node;
if(element.getNodeType() == Element.ELEMENT_NODE){
System.out.println(getValue("name",element));
System.out.println(getContent("content",element));
}
}
private static String getValue(String tag, Element element) {
String value="";
try {
NodeList nodes = element.getElementsByTagName(tag).item(0).getChildNodes();
Node node = (Node) nodes.item(0);
value=node.getNodeValue();
} catch (Exception e){
value=null;
}
return value;
}
So for instance, when parsing, Name is printed correctly name1, but content is returning blank.
Any idea how can I get <content>'s content as a String?
Thank you.
EDIT
private static String getContent(String tag, Element element) {
String value="";
try {
Node nodes = element.getElementsByTagName(tag).item(0);
value = nodes.getTextContent();
} catch (Exception e){
value=null;
}
return value;
}
Log.d("debugging",getContent("content",element));
And this is printing this:
%20%20%20%20%20This%20some%20piece ....
It seems that it's not returning the string <p>.
Since getTextContent doesn't return any markup I think it won't be possible using any of the Node-methods.
The only way I see (if you want to use DocumentBuilder) is, that you write some code to rebuild the string out of the nodelists (iterate through nodes and node-attributes).
As a small sketch on what I mean: (only javalike pseudocode)
string rebuild(NodeList nodeList) {
string result = "";
for (Node n : nodeList) {
result += "<" + node.getNodeName() + " ";
NamedNodeMap aMap = node.getAttributes();
if (aMap != null) {
int aMapLength = aMap.getLength();
for (int i=0; i<aMapLength; ++i) {
Node a = aMap.item(i);
result += a.getNodeName() + "=" + a.getValue() + " ";
}
}
NodeList nList = node.getChildNodes();
if (nList == null) {
result += "/>";
} else {
result += ">";
result += rebuild(nList);
result += "</" + node.getNodeName() + ">";
}
}
return result;
}
You could also create a xsd file and to use xjc (JAXB) to create Java-classes. There are a lot of good tutorials out their on how to do this (depending on your IDE).
Then you could have everything marshaled/ unmarshaled by JAXB as you like.
Another way would be that you implement your own SaxHandler instead and use SAXParser and SAXParserFactory, which will be quite some work.
Use getTextContent() instead of getValue() function. Following is an example(same as yours getValue function).
private static String getContent(String tag, Element element) {
String value="";
try {
NodeList nodes = element.getElementsByTagName(tag).item(0).getChildNodes();
Node node = (Node) nodes.item(0);
value=node.getTextContent(); // notice getTextContent()
} catch (Exception e){
value=null;
}
return value;
}
It will work with well formatted xml
<module>
<name>name1</name>
<type>type</type>
<content>
<p>This is some piece of code that should be treated as a full string, even that 'p' tag, because I want to use all content inside p tag for a webview in android.
</p>
<h1>This is a big classy title in html</h1>
</content>
</module>

Categories

Resources