1
votes

I would like to

  1. add/remove/update elements/attributes/values to the "subTree"
  2. be able to save the updated "targetDoc" back to the "target" file location.
  3. determine which tree model would be best for this xpath + tree modification procedure.

I thought I should somehow be able to get a MutableNodeInfo object, but I don't know how to do this. I tried using the processor.setConfigurationProperty(FeatureKeys.TREE_MODEL, Builder.LINKED_TREE); but this still gives me an underlying node of TinyElementImpl. I require xpath 2.0 to avoid having to enter default namespaces, which is why I am using saxon s9api instead of Java's default DOM model. I would also like to avoid using xslt/xquery if possible because these tree modifications are being done dynamically, making xslt/xquery more complicated in my situation.

public static void main(String[] args) {

    // XML File namesspace URIs
    Hashtable<String, String> namespaceURIs = new Hashtable<>();
    namespaceURIs.put("def", "http://www.cdisc.org/ns/def/v2.0");
    namespaceURIs.put("xmlns", "http://www.cdisc.org/ns/odm/v1.3");
    namespaceURIs.put("xsi", "http://www.w3.org/2001/XMLSchema-instance");
    namespaceURIs.put("xlink", "http://www.w3.org/1999/xlink");
    namespaceURIs.put("", "http://www.cdisc.org/ns/odm/v1.3");
    // The source/target xml document
    String target = "Path to file.xml";
    // An xpath string
    String xpath = "/ODM/Study/MetaDataVersion/ItemGroupDef[@OID/string()='IG.TA']";

    Processor processor = new Processor(true);
    // I thought this tells the processor to use something other than
    // TinyTree
    processor.setConfigurationProperty(FeatureKeys.TREE_MODEL,
            Builder.LINKED_TREE);
    DocumentBuilder builder = processor.newDocumentBuilder();
    XPathCompiler xpathCompiler = processor.newXPathCompiler();
    for (Entry<String, String> entry : namespaceURIs.entrySet()) {
        xpathCompiler.declareNamespace(entry.getKey(), entry.getValue());
    }
    try {
        XdmNode targetDoc = builder.build(Paths.get(target).toFile());
        XPathSelector selector = xpathCompiler.compile(xpath).load();
        selector.setContextItem(targetDoc);
        XdmNode subTree = (XdmNode) selector.evaluateSingle();
        // The following prints: class
        // net.sf.saxon.tree.tiny.TinyElementImpl
        System.out.println(subTree.getUnderlyingNode().getClass());

        /*
         * Here, is where I would like to modify subtree and save modified doc
         */

    } catch (SaxonApiException e) {
        e.printStackTrace();
    }

}
2

2 Answers

1
votes

I think you can supply a DOM node to Saxon and run XPath against it but it that case you don't use the document builder for Saxon's native trees, you build a DOM using the javax.xml.parsers.DocumentBuilder and once you have a W3C DOM node you can supply it to Saxon using the wrap method of a Saxon DocumentBuilder. Here is sample code taken from the file S9APIExamples.java in the Saxon 9.6 resources file:

        // Build the DOM document
        File file = new File("data/books.xml");
        DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
        dfactory.setNamespaceAware(true);
        javax.xml.parsers.DocumentBuilder docBuilder;
        try {
            docBuilder = dfactory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new SaxonApiException(e);
        }
        Document doc;
        try {
            doc = docBuilder.parse(new InputSource(file.toURI().toString()));
        } catch (SAXException e) {
            throw new SaxonApiException(e);
        } catch (IOException e) {
            throw new SaxonApiException(e);
        }
        // Compile the XPath Expression
        Processor proc = new Processor(false);
        DocumentBuilder db = proc.newDocumentBuilder();
        XdmNode xdmDoc = db.wrap(doc);
        XPathCompiler xpath = proc.newXPathCompiler();
        XPathExecutable xx = xpath.compile("//ITEM/TITLE");
        // Run the XPath Expression
        XPathSelector selector = xx.load();
        selector.setContextItem(xdmDoc);
        for (XdmItem item : selector) {
            XdmNode node = (XdmNode) item;
            org.w3c.dom.Node element = (org.w3c.dom.Node) node.getExternalNode();
            System.out.println(element.getTextContent());
        }

There are also samples showing how to use Saxon with JDOM and other mutable tree implementations but I think you need Saxon PE or EE to have direct support for those.

0
votes

The MutableNodeInfo interface in Saxon is designed very specifically to meet the needs of XQuery Update, and I would advise against trying to use it directly from Java; the implementation isn't likely to be robust when handling method calls other than those made by XQuery Update.

In fact, it's generally true that the Saxon NodeInfo interface is designed as a target for XPath, rather than for user-written Java code. I would therefore suggest using a third party tree model; the ones I like best are JDOM2 and XOM. Both of these allow you to mix direct Java navigation and update with use of XPath 2.0 navigation using Saxon.