Reputation: 20394
I have some XML that roughly looks like this:
<project type="mankind">
<suggestion>Build the enterprise</suggestion>
<suggestion>Learn Esperanto</suggestion>
<problem>Solve world hunger</suggestion>
<discussion>Do Vulcans exist</discussion>
</project>
I want to use XPath to find out the names of the second level elements (there can be elements I won't know upfront) using Java. This is the code I tried:
public NodeList xpath2NodeList(Document doc, String xPathString) throws XPathExpressionException {
XPath xpath = XPathFactory.newInstance().newXPath();
MagicNamespaceContext nsc = new MagicNamespaceContext();
xpath.setNamespaceContext(nsc);
Object exprResult = xpath.evaluate(xPathString, doc, XPathConstants.NODESET);
return (NodeList) exprResult;
}
My XPath is /project/*/name()
. I get the error:
javax.xml.transform.TransformerException: Unknown nodetype: name
A query like /project/suggestion
works as expected. What am I missing? I'd like to get a list with the tag names.
Upvotes: 1
Views: 3554
Reputation: 23
Below code may answer your original question.
package com.example.xpath;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class XPathReader {
static XPath xPath = XPathFactory.newInstance().newXPath();
public static void main(String[] args) {
try {
FileInputStream file = new FileInputStream(new File("c:/mankind.xml"));
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document xmlDocument = builder.parse(file);
XPathExpression expr = xPath.compile("//project/*");
NodeList list= (NodeList) expr.evaluate(xmlDocument, XPathConstants.NODESET);
for (int i = 0; i < list.getLength(); i++) {
Node node = list.item(i);
System.out.println(node.getNodeName() + "=" + node.getTextContent());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (XPathExpressionException e) {
e.printStackTrace();
}
}
}
Assuming the input is corrected (Solve world hunger), the code should print:
suggestion=Build the enterprise
suggestion=Learn Esperanto
problem=Solve world hunger
discussion=Do Vulcans exist
Upvotes: 1
Reputation: 22647
Java6 (don't ask).
I think your implementation only supports XPath 1.0. If that were true, only the following would work:
"name(/project/*)"
The reason for this is that in the XPath 1.0 model, you cannot use functions (like name()
) as a step in a path expression. Your code throws an exception and in this case, the processor mistakes your function name()
for an unknown node test (like comment()
). But there is nothing wrong with using a path expression as the argument of the name()
function.
Unfortunately, if an XPath 1.0 function that can only handle a single node as an argument is given a sequence of nodes, only the first one is used. Therefore, it is likely that you will only get the first element name as a result.
XPath 1.0's capability to manipulate is very limited and often the easiest way to get around such problems is to reach for the higher-level language that uses XPath as the query language (in your case Java). Or put another way: Write an XPath expression to retrieve all relevant nodes and iterate over the result, returning the element names, in Java.
With XPath 2.0, your inital query would be fine. Also see this related question.
Upvotes: 3