0

I think what I am trying to do is simple: Add child nodes dynamically to a node (without even knowing the name of the node to be added - developing some framework) using XmlSlurper.

For ease of explaining, something like this:

def colorsNode = new XmlSlurper().parseText("""
    <colors>
        <color>red</color>
        <color>green</color>
    </colors>""") 

NodeChild blueNode = new XmlSlurper().parseText("<color>blue</color>")  // just for  illustration. The actual contents are all dynamic

colorsNode.appendNode(blueNode) // In real life, I need to be be able to take in any node and append to a given node as child.

I was expecting the resulting node to be the same as slurping the following:

“””
<colors>
    <color>red</color>
    <color>green</color> 
    <color>blue</color>
</colors>"""

However the result of appending is:

colorsNode
    .node
        .children => LinkedList[Node('red') ->   Node('green')   ->   <b>NodeChild</b>(.node='blue')]

In other words, what gets appended to the LinkedList is the NodeChild that wraps the new node, not the node itself.

Not surprising, looking at the source code for NodeChild.java:

protected void appendNode(final Object newValue) { 
    this.node.appendNode(newValue, this);
}

Well, I would gladly modify my code into:

colorsNode.appendNode(blueNode<b>.node</b>)

Unfortunately NodeChild.node is private :(, don't know why! What would be a decent way of achieving what I am trying? I couldn’t see any solutions online.

I was able to complete my prototyping work by tweaking Groovy source and exposing NodeChild.node, but now need to find a proper solution.

Any help would be appreciated.

Thanks, Aby Mathew

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • Not sure I understand... Adding `XmlUtil.serialize( colorsNode )` to the end of your original script outputs the results you'd expect... – tim_yates Jan 03 '14 at 11:17
  • The issue is that the object structure resulting from appending is different. The appended element, instead of being a *Node* in the linkedList, is a *NodeChild* that wraps the Node. That causes problems with the XQuery that I use to locate the element later on. I just want to be able to get hold of the Node from the NodeChild and append it. – user3155388 Jan 03 '14 at 18:40
  • Ahhh. Maybe that massively important bit of info should go in the question? With an example of what fails? – tim_yates Jan 03 '14 at 19:40
  • Sorry, I missspoke. XQuery was something I was looking at, but not used anywhere in this context. My problem is that I don't see the new node when I iterate through colorsNode.nodeChilds(). Almost looks like a bug to me. That is when I ended up looking at the internal structure and noticed the difference. – user3155388 Jan 06 '14 at 22:23
  • XmlSlurper does not do live updates. See https://groups.google.com/forum/#!msg/groovy-user/YORdAuREtTk/7oNye4BAsNcJ Quote: _"This is by-design behavior for XmlSlurper. Its internal structures are mostly immutable and queries and updates are carried out in a lazy fashion. To cut a long story short, when you attempt to make changes, rather than actually making them on the immutable tree structure, it simply remembers the changes you want to make. Eventually when you have finished all of your changes you serialize it and all the changes are made at once..."_ – tim_yates Jan 07 '14 at 09:00
  • Thank you. Good to know there was something behind this behavior. By adding the extra step of serializing and parsing again I am able to get by. Would be a huge waste if performance mattered. I am still left with the feeling that GPathResult API is somewhat screwy rather than groovy. Even without serializing I do see a NodeChild added to the linkedList, so it IS getting mutated. But NodeChild.childNodes() fails to get it. Instead of appending the NodeChild if I append NodeChild.node (by tweaking groovy source to make it non-private) everything works fine without the need to serlaize. – user3155388 Jan 07 '14 at 23:20

1 Answers1

0

It would be easier if you use XmlParser:

@Grab('xmlunit:xmlunit:1.1')
import org.custommonkey.xmlunit.Diff
import org.custommonkey.xmlunit.XMLUnit

def xml = """
    <colors>
        <color>red</color>
        <color>green</color>
    </colors>
"""

def expectedResult = """
    <colors>
        <color>red</color>
        <color>green</color>
        <color>blue</color>
    </colors>
"""

def root = new XmlParser().parseText(xml)
root.appendNode("color", "blue")

def writer = new StringWriter()
new XmlNodePrinter(new PrintWriter(writer)).print(root)
def result = writer.toString()

XMLUnit.setIgnoreWhitespace(true)
def xmlDiff = new Diff(result, expectedResult)
assert xmlDiff.identical()
dmahapatro
  • 49,365
  • 7
  • 88
  • 117
  • Thanks for the response, but it doesn't exactly address my issue. I won't be able to do: root.appendNode("color", "blue") I need to be able to take in an XML string like "blue" at run time, parse it and append. – user3155388 Jan 03 '14 at 18:15
  • 2
    I think @tim_yates has your answer suggesting `XmlUtil.serialize( colorsNode )` to be added as the last line of your script. – dmahapatro Jan 03 '14 at 18:16