views:

11684

answers:

6

In Java how can you convert a String that represents a fragment of XML for insertion into an XML document?

e.g.

String newNode =  "<node>value</node>"; // Convert this to XML

Then insert this node into an org.w3c.dom.Document at as the child of a given node?

+5  A: 
Element node =  DocumentBuilderFactory
    .newInstance()
    .newDocumentBuilder()
    .parse(new StringInputStream("<node>value</node>"))
    .getDocumentElement();

... though I confess that I haven't actually tried that.

izb
the .parse( new StringInputStream(....should read.parse( new ByteArrayInputStream( new String( "xml" ).getBytes() ) );
Steen
I just hate these commentboxes and their lack of markup ( or markdown, for that matter)
Steen
+4  A: 

You can use the document's import (or adopt) method to add XML fragments:

  /**
   * @param docBuilder
   *          the parser
   * @param parent
   *          node to add fragment to
   * @param fragment
   *          a well formed XML fragment
   */
  public static void appendXmlFragment(
      DocumentBuilder docBuilder, Node parent,
      String fragment) throws IOException, SAXException {
    Document doc = parent.getOwnerDocument();
    Node fragmentNode = docBuilder.parse(
        new InputSource(new StringReader(fragment)))
        .getDocumentElement();
    fragmentNode = doc.importNode(fragmentNode, true);
    parent.appendChild(fragmentNode);
  }
McDowell
Hmm. If this is the simplest solution, I must say it's rather complicated for such a small problem.
Jonik
I've pared it down to the minimum - it still uses what you get in the JRE API, though, so a bit of verbosity is unavoidable.
McDowell
That is exactly what I was looking for. I didn't realize that I had to import the fragment into the dom before appending it to the parent node!
Tony Eichelberger
+2  A: 

For what it's worth, here's a solution I came up with using the dom4j library. (I did check that it works.)

Read the XML fragment into a org.dom4j.Document (note: all the XML classes used below are from org.dom4j; see Appendix):

  String newNode = "<node>value</node>"; // Convert this to XML
  SAXReader reader = new SAXReader();
  Document newNodeDocument = reader.read(new StringReader(newNode));

Then get the Document into which the new node is inserted, and the parent Element (to be) from it. (Your org.w3c.dom.Document would need to be converted to org.dom4j.Document here.) For testing purposes, I created one like this:

    Document originalDoc = 
      new SAXReader().read(new StringReader("<root><given></given></root>"));
    Element givenNode = originalDoc.getRootElement().element("given");

Adding the new child element is very simple:

    givenNode.add(newNodeDocument.getRootElement());

Done. Outputting originalDoc now yields:

<?xml version="1.0" encoding="utf-8"?>

<root>
    <given>
        <node>value</node>
    </given>
</root>

Appendix: Because your question talks about org.w3c.dom.Document, here's how to convert between that and org.dom4j.Document.

// dom4j -> w3c
DOMWriter writer = new DOMWriter();
org.w3c.dom.Document w3cDoc = writer.write(dom4jDoc);

// w3c -> dom4j
DOMReader reader = new DOMReader();
Document dom4jDoc = reader.read(w3cDoc);

(If you'd need both kind of Documents regularly, it might make sense to put these in neat utility methods, maybe in a class called XMLUtils or something like that.)

Maybe there are better ways to do this, even without any 3rd party libraries. But out of the solutions presented so far, in my view this is the easiest way, even if you need to do the dom4j <-> w3c conversions.

Jonik
+1  A: 

Here's yet another solution, using the XOM library, that competes with my dom4j answer. (This is part of my quest to find a good dom4j replacement where XOM was suggested as one option.)

First read the XML fragment into a nu.xom.Document:

String newNode = "<node>value</node>"; // Convert this to XML
Document newNodeDocument = new Builder().build(newNode, "");

Then, get the Document and the Node under which the fragment is added. Again, for testing purposes I'll create the Document from a string:

Document originalDoc = new Builder().build("<root><given></given></root>", "");
Element givenNode = originalDoc.getRootElement().getFirstChildElement("given");

Now, adding the child node is simple, and similar as with dom4j (except that XOM doesn't let you add the original root element which already belongs to newNodeDocument):

givenNode.appendChild(newNodeDocument.getRootElement().copy());

Outputting the document yields the correct result XML (and is remarkably easy with XOM: just print the string returned by originalDoc.toXML()):

<?xml version="1.0"?>
<root><given><node>value</node></given></root>

(If you wanted to format the XML nicely (with indentations and linefeeds), use a Serializer; thanks to Peter Štibraný for pointing this out.)

So, admittedly this isn't very different from the dom4j solution. :) However, XOM may be a little nicer to work with, because the API is better documented, and because of its design philosophy that there's one canonical way for doing each thing.

Appendix: Again, here's how to convert between org.w3c.dom.Document and nu.xom.Document. Use the helper methods in XOM's DOMConverter class:

// w3c -> xom
Document xomDoc = DOMConverter.convert(w3cDoc);

// xom -> w3c
org.w3c.dom.Document w3cDoc = DOMConverter.convert(xomDoc, domImplementation);  
// You can get a DOMImplementation instance e.g. from DOMImplementationRegistry
Jonik
Note that instead of new Builder().build(new StringReader("<root><given></given></root>")); you can also use new Builder().build("<root><given></given></root>", "test.xml"); (where "test.xml" is some random base URI)
Peter Štibraný
"If you wanted to format the XML nicely (with indentations and linefeeds), I'm not sure how to do that with XOM." -- using Serializer class. Configure it using setIndent and setMaxLength, and call write(document).
Peter Štibraný
Serializer is also easy to customize by subclassing.
Peter Štibraný
Thanks! I didn't really understand what exactly is the meaning of the baseURI param; passing an empty string also works, so I'm using that. In any case, that does simplify the code somewhat. For formatting, Serializer indeed works fine.
Jonik
I think baseURI would be used for resolving relative references to DTD or XInclude (http://lists.ibiblio.org/pipermail/xom-interest/2004-November/001498.html)
Peter Štibraný
+1  A: 

If you're using dom4j, you can just do:

Document document = DocumentHelper.parseText(text);

(from http://www.dom4j.org/dom4j-1.6.1/guide.html)

ronz
Just went to their web site. They place Google Ads right into the typical Maven-generated navigation bar! Incredible!
Thilo
Apparently, the site is no longer operated by the dom4j guys, but some domain grabbers took over...
Thilo
A: 

...and if you're using purely XOM, something like this:

    String xml = "<fakeRoot>" + xml + "</fakeRoot>";
    Document doc = new Builder( false ).build( xml, null );
    Nodes children = doc.getRootElement().removeChildren();
    for( int ix = 0; ix < children.size(); ix++ ) {
        otherDocumentElement.appendChild( children.get( ix ) );
    }

XOM uses fakeRoot internally to do pretty much the same, so it should be safe, if not exactly elegant.

atamar