3

I have an XML file that has the following:

<contexts>
    <context name="a">
        <xi:include xmlns:xi="http://www.w3.org/2003/XInclude" href="reuse.xml"/>
        <other_stuff/>
    </context>
    <context name="b">
        <xi:include xmlns:xi="http://www.w3.org/2003/XInclude" href="reuse.xml"/>
        <other_stuff/>
    </context>
</contexts>

... and the reuse.xml referenced by the XInclude above which contains something like this:

<good_stuff/><!-- and a bunch of complex stuff ->

I'm trying to write some Java code which can generate some string output for each context element (easy) but instead of the xi:include line, replace that with the contents of the included file i.e. give me an 'exploded' String for each Context like this:

<context name="a">
    <good_stuff/><!-- and a bunch of complex stuff ->
    <other_stuff/>
</context>

Rather than what I'm getting now:

<context name="a">
    <xi:include xmlns:xi="http://www.w3.org/2003/XInclude" href="reuse.xml"/> 
    <other_stuff/>
</context>

Hopefully I've explained myself clearly that I don't want the xi:include text in my output, but rather the contents of the other file it should be replaced by.

I'm setting the parser to be XInclude and Namespace aware:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
factory.setXIncludeAware(true);

...

Then getting the child nodes of the type I want (type is giving me the Context in this case).

NodeList result = doc.getElementsByTagName(type).item(0).getChildNodes();

But when I iterate over this, and pass each element to the StringWriter with the following, it isn't replacing the xi:include;

StringWriter sw = new StringWriter();
Transformer serializer = TransformerFactory.newInstance().newTransformer();
serializer.setOutputProperty(OutputKeys.INDENT, "no");
serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
serializer.setOutputProperty(OutputKeys.STANDALONE, "yes");
serializer.transform(new DOMSource(element), new StreamResult(sw));
String result = sw.toString();

Is there a way to do this with the JDK 7/8 built-in XML parsers, other than String manipulation to replace the xi:include myself?

Thanks for any assistance!

loizos
  • 31
  • 3
  • I would replace the xi:include myself, but I don't see the need for String manipulation. However there might be an xi:include facility provided, I don't know. – LarsH Aug 06 '16 at 02:41
  • 1
    I would suspect that it is a namespace issue, the W3C XInclude namespace is `http://www.w3.org/2001/XInclude` and not `http://www.w3.org/2003/XInclude`. – Martin Honnen Aug 06 '16 at 10:02

1 Answers1

2

I think you have used the wrong namespace for the XInclude reference, when I change the XML to use

<?xml version="1.0" encoding="UTF-8"?>
<contexts>
    <context name="a">
        <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="reuse.xml"/>
        <other_stuff/>
    </context>
    <context name="b">
        <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="reuse.xml"/>
        <other_stuff/>
    </context>
</contexts>

which uses the namespace http://www.w3.org/2001/XInclude defined in the W3C XInclude spec https://www.w3.org/TR/xinclude/#syntax, and use a sample XML document

<?xml version="1.0" encoding="UTF-8"?>
<good_stuff/>

then the Java code outputs e.g.

<context name="a">
        <good_stuff xml:base="reuse.xml"/>
        <other_stuff/>
    </context>
<context name="b">
        <good_stuff xml:base="reuse.xml"/>
        <other_stuff/>
    </context>

when using a default transformer to output the context elements:

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(true);
    factory.setXIncludeAware(true);

    DocumentBuilder docBuilder = factory.newDocumentBuilder();

    Document doc = docBuilder.parse("file1.xml");

    NodeList contextElements = doc.getElementsByTagName("context");

    for (int i = 0, l = contextElements.getLength(); i < l; i++) {
        Node element = contextElements.item(i);
        StringWriter sw = new StringWriter();
        Transformer serializer = TransformerFactory.newInstance().newTransformer();
        serializer.setOutputProperty(OutputKeys.INDENT, "no");
        serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        serializer.setOutputProperty(OutputKeys.STANDALONE, "yes");

        serializer.transform(new DOMSource(element), new StreamResult(sw));
        String result = sw.toString();
        System.out.println(result);
    }
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Thanks for the suggestions. Unfortunately, modifying the XML is not in my control but maybe I can change do a quick search and replace from 2003 to 2001 before I parse. For now, I went with the option of parsing both documents and adopting the node and cloning it with a deep copy whenever I find an xi:include with that href to replace. Thanks everyone! – loizos Aug 07 '16 at 08:27
  • You should inform the provider of the XML that the namespace is bad. There was some confusion about the XInclude namespace URI several years ago. First it was defined as `http://www.w3.org/2001/XInclude`. Then it was changed to `http://www.w3.org/2003/XInclude`, and a little later reverted back to `http://www.w3.org/2001/XInclude`. See https://www.w3.org/TR/2004/CR-xinclude-20040413/. – mzjn Aug 07 '16 at 16:49