2

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>
Vindeep Singh
  • 21
  • 1
  • 2
  • xPath returns you the `Node`(s) which match your query, if you need to generate a new document output, then add those node to new `Document` – MadProgrammer Oct 06 '15 at 00:13

1 Answers1

1

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 Nodes 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>
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thanks for the help, but that doesn't solve my problem as i want to just filter the result without losing the xml structure – Vindeep Singh Oct 06 '15 at 00:30
  • Well, this does, but it has to generate a new `Document` in order to generate the full structure (I forgot to insert the `item` node, but I'm sure you can append a new node to the root ;)) – MadProgrammer Oct 06 '15 at 00:32
  • Your Solution is too specific for this problem, what i'm looking is more generic solution. – Vindeep Singh Oct 06 '15 at 00:35
  • If you need to generate a "filtered" version of a `Document`, you have to create a new `Document` and append the resulting nodes it ... is that generic enough for you? – MadProgrammer Oct 06 '15 at 00:38
  • I tried using `/List[Item[Category[Name='123']]]` as the filter, but this simply returned all the elements, it didn't filter out the non-matching elements for `Name=123` – MadProgrammer Oct 06 '15 at 00:53