Java: How to execute an XPath query on a node - java

So I'm reading from an XML file with many layers of nesting in Java using xPath.
At the moment I have a method that takes the path to XML file and a xpath query as parameters, and returns a NodeIterator.
Then I iterate through those node, and for some of the nodes (if their name matches) I need to execute another query on them and get a NodeIterator of their children etc
Is it possible to have a function with 2 parameters, one an already existing Node and the other an xPath query to execute on that Node?
So replacing:NodeIterator ni = XPathAPI.selectNodeIterator(document,xpathQuery);
With some like : NodeIterator ni2 = xPathAPI.selectNodeIterator(parentNode, query);
I've searched on the internet and I can't find any examples, and I'm not sure what the syntax to do the above would be, or if it's even possible?
Many thanks in advance :)

Presumably your XPathAPI class is the Apache/Xalan org.apache.xpath.XPathAPI?
In that case, what's wrong with
static NodeIterator selectNodeIterator(Node contextNode, java.lang.String str)
It seems to do exactly what you want.

Related

How do I create an xPath statement from a NodeInfo?

I'm using the S9API with Saxon 9.7 HE, and I have a NodeInfo object. I need to create an xPath statement that uniquely corresponds to this NodeInfo. It's easy enough to get the prefix, the node name, and the parent:
String prefix = node.getPrefix();
String localPart = node.getLocalPart();
NodeInfo parent = node.getParent();
So I can walk up the tree, building the xPath as I go. But what I can't find is any way to get the positional predicate info. IOW, it's not sufficient to create:
/persons/person/child
because it might match multiple child elements. I need to create:
/persons/person[2]/child[1]
which will match only one child element. Any ideas on how to get the positional predicate info? Or maybe there's a better way to do this altogether?
BTW, for those who use the generic DOM and not the S9API, here's an easy solution to this problem: http://practicalxml.sourceforge.net/apidocs/net/sf/practicalxml/DomUtil.html#getAbsolutePath(org.w3c.dom.Element)
Edit: #Michael Kay's answer works. To add some meat to it:
XPathExpression xPathExpression = xPath.compile("./path()");
List matches = (List) xPathExpression.evaluate(node, XPathConstants.NODESET);
String pathToNode = matches.get(0).toString();
// If you want to remove the expanded QName syntax:
pathToNode = pathToNode.replaceAll("Q\\{.*?\\}", "");
This must be done using the same xPath object that was previously used to acquire the NodeInfo object.
In XPath 3.0 you can use fn:path().
Earlier Saxon releases offer saxon:path().
The challenge here is handling namespaces. fn:path() returns a path that's not sensitive to namespace-prefix bindings by using the new expanded-QName syntax
/Q{}persons/Q{}person[2]/Q{}child[1]

DOM Parser Example for Objects within Objects

So say I have an XML file that looks like this:
<Object1s>
<Object1>
<Field1></Field1>
<Object2s>
<Object2>
<Field1a></Field1a>
<Field1b></Field1b>
</Object2>
<Object2>
<Field1a></Field1a>
<Field1b></Field1b>
</Object2>
</Object2s>
</Object1>
<Object1>
<Field1></Field1>
<Object2s>
<Object2>
<Field1a></Field1a>
<Field1b></Field1b>
</Object2>
</Object2s>
</Object1>
</Object1s>
The DOM tutorials I've found have not worked when I try and do the same sort of thing. For instance, I want to be able to separate the Object2s by the Object1 that they are in. When following the example given by DOM tutorials where this type of thing doesn't exist in their XML files, I get all the Object2s that are in any Object1 when I try to find them.
Can someone show me an example that handles something like this?
Okay, figured it out. What I do is use the element I declare for each element, and within that call .getElementsBytagName() to get the elements within that element.

Retrieve value of attribute using XPath

I am trying to retrieve the value of an attribute from an xmel file using XPath and I am not sure where I am going wrong..
This is the XML File
<soapenv:Envelope>
<soapenv:Header>
<common:TestInfo testID="PI1" />
</soapenv:Header>
</soapenv:Envelope>
And this is the code I am using to get the value. Both of these return nothing..
XPathBuilder getTestID = new XPathBuilder("local-name(/*[local-name(.)='Envelope']/*[local-name(.)='Header']/*[local-name(.)='TestInfo'])");
XPathBuilder getTestID2 = new XPathBuilder("Envelope/Header/TestInfo/#testID");
Object doc2 = getTestID.evaluate(context, sourceXML);
Object doc3 = getTestID2.evaluate(context, sourceXML);
How can I retrieve the value of testID?
However you're iterating within the java, your context node is probably not what you think, so remove the "." specifier in your local-name(.) like so:
/*[local-name()='Header']/*[local-name()='TestInfo']/#testID worked fine for me with your XML, although as akaIDIOT says, there isn't an <Envelope> tag to be seen.
The XML file you provided does not contain an <Envelope> element, so an expression that requires it will never match.
Post-edit edit
As can be seen from your XML snippet, the document uses a specific namespace for the elements you're trying to match. An XPath engine is namespace-aware, meaning you'll have to ask it exactly what you need. And, keep in mind that a namespace is defined by its uri, not by its abbreviation (so, /namespace:element doesn't do much unless you let the XPath engine know what the namespace namespace refers to).
Your first XPath has an extra local-name() wrapped around the whole thing:
local-name(/*[local-name(.)='Envelope']/*[local-name(.)='Header']
/*[local-name(.)='TestInfo'])
The result of this XPath will either be the string value "TestInfo" if the TestInfo node is found, or a blank string if it is not.
If your XML is structured like you say it is, then this should work:
/*[local-name()='Envelope']/*[local-name()='Header']/*[local-name()='TestInfo']/#testID
But preferably, you should be working with namespaces properly instead of (ab)using local-name(). I have a post here that shows how to do this in Java.
If you don't care for the namespaces and use an XPath 2.0 compatible engine, use * for it.
//*:Header/*:TestInfo/#testID
will return the desired input.
It will probably be more elegant to register the needed namespaces (not covered here, depends on your XPath engine) and query using these:
//soapenv:Header/common:TestInfo/#testID

Inserting nodes into an existing XML document in GWT client using XMLParser

I have an xml document (using the Document class in the XMLParser library of GWT client) with a format like follows:
<document><node id="0">content</node><node id="1">more content</node></document>
Given an ID, I need to insert a new node immediately after the node with that ID.
So far I've tried using insertBefore (as there is no insertAfter), but I must be using it incorrectly as nothing happens (apart from an UmbrellaException in the js console). I can't find any example usage via search engines.
My attempt is as follows (where n is the node I want to insert after):
Node nNext = n.getNextSibling(); //To get the next sibling to use it with insertBefore
Element newNode = doc.createElement("node");
newNode.appendChild(doc.createTextNode("new content")); //seems to work up until here
n.insertBefore(newNode, nNext); //so this line could be the problem?
insertBefore must be called on the parent node, so:
n.getParentNode().insertBefore(newNode, n.getNextSibling());

Move/Copy XMLBean object to another Bean

<xml-fragment>
<currentClinicalNote>
<patientFamilyHistory disorderName="CurrentCN" id="23423"/>
<patientFamilyHistory disorderName="CurrentCN1" id="23424"/>
<patientFamilyHistory disorderName="CurrentCN1" id="23424"/>
</currentClinicalNote>
</xml-fragment>
I have an XMLBean like above, now I want to replace the node[#id=23423] with a new same type node. How can I do that?
Below is sample code I tried to work..
XmlCursor xmlCursor = cursor.execQuery(nameSpace + pathExpression1);
I found the node with above code, now I have that node in cursor, How do I replace that with another?
Any replies would be appreciated.
DOM3 is not yet implemented in XML Beans,so need to work with DOM Nodes directly to fetch by finding first child in cursor.

Categories

Resources