Reputation: 21
I have following XML Document which i need to filter using xpath expression :
<List>
<Item>
<Name>abc</Name>
<Category><Name>123</Name><value>aaa</value></Category>
</Item>
<Item>
<Name>def</Name>
<Category><Name>456</Name><value>bbb</value></Category>
</Item>
<Item>
<Name>xyz</Name>
<Category><Name>123</Name><value>ccc</value></Category>
</Item>
</List>
I am providing the Following Expression : "/List/Item/Category[Name='123']", which gives me :
<Category>
<Name>123</Name>
<value>aaa</value>
<Category>
<Name>123</Name>
<value>ccc</value>
</Category>
</Category>
But I need this Output :
<List>
<Item>
<Name>abc</Name>
<Category><Name>123</Name><value>aaa</value></Category>
</Item>
<Item>
<Name>xyz</Name>
<Category><Name>123</Name><value>ccc</value></Category>
</Item>
</List>
Upvotes: 2
Views: 6703
Reputation: 347204
xPath will return you the node's that match your criteria from within the current Document
If you want to generate a "filtered" representation of the Document
, then you need to create a second Document
and append the matching Node
s to it
For example...
try {
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
DocumentBuilder b = f.newDocumentBuilder();
Document original = b.parse(...);
original.getDocumentElement().normalize();
Document filtered = b.newDocument();
Node root = filtered.createElement("List");
filtered.appendChild(root);
String expression = "/List/Item/Category[Name='123']";
XPath xPath = XPathFactory.newInstance().newXPath();
Object result = xPath.compile(expression).evaluate(original, XPathConstants.NODESET);
NodeList nodes = (NodeList) result;
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
filtered.adoptNode(node);
root.appendChild(node);
}
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Transformer tf = TransformerFactory.newInstance().newTransformer();
tf.setOutputProperty(OutputKeys.INDENT, "yes");
tf.setOutputProperty(OutputKeys.METHOD, "xml");
tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
DOMSource domSource = new DOMSource(filtered);
StreamResult sr = new StreamResult(os);
tf.transform(domSource, sr);
String text = new String(os.toByteArray());
System.out.println(text);
} catch (TransformerException ex) {
ex.printStackTrace();
}
} catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException | DOMException exp) {
exp.printStackTrace();
}
So, based on your original XML, this will output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<List>
<Category>
<Name>123</Name>
<value>aaa</value>
</Category>
<Category>
<Name>123</Name>
<value>ccc</value>
</Category>
</List>
Just beware, that this will be removing the nodes from the original document when they are appended to the new one.
i want to just filter the result without losing the xml structure
XPath is a lot like SQL, it's a query language, it's job isn't to generate a new structure. Having said that, you can control which node it returns, for example, if you change the query to something more like this...
String expression = "/List/Item[Category[Name='123']]";
Then the output becomes something more like...
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<List>
<Item>
<Name>abc</Name>
<Category>
<Name>123</Name>
<value>aaa</value>
</Category>
</Item>
<Item>
<Name>xyz</Name>
<Category>
<Name>123</Name>
<value>ccc</value>
</Category>
</Item>
</List>
Upvotes: 1