Deleting nodes in XML using Xpath in Groovy

3.9k Views Asked by At

I've got this Groovy code working to delete nodes using xpath strings, but I'm having problems deleting nodes where the xpath results in multiple node instances.

Sample XML...

<root>
  <element1>foo</element1>
  <element2>bar</element2>
  <items>
     <item>
       <name>a</name>
       <desc>b</desc>
     <item>
     <item>
        <name>c</name>
        <desc>x</desc>
     </item>
  </items>
</root>

Code to delete nodes...

def resource = XmlSlurper().parseText(xml)
def xpathsToDelete = ['/root/element1','/root/items/item/name']
 xpathsToDelete.each {
     def pathTokens = it.path.tokenize '/'
     def currentNode = resource
     if ( currentNode.name() == pathTokens.first() ) { 
       def xpath = pathTokens.tail().join '/'
       currentNode = currentNode."${xpath}"
       currentNode.replaceNode{}
     }
}

The above code removes the node element1 using xpath /root/element1, which evaluates to a single node, but does not work for /root/items/name which evaluates to multiple nodes.

2

There are 2 best solutions below

2
On BEST ANSWER

This is a tricky one. It is related to this question, which is vital to my answer.

Here is a solution:

import groovy.util.* 
import groovy.xml.* 

def xml = """<root>
  <element1>foo</element1>
  <element2>bar</element2>
  <items>
     <item>
       <name>a</name>
       <desc>b</desc>
     </item>
     <item>
        <name>c</name>
        <desc>x</desc>
     </item>
  </items>
</root>"""

def removeNodes = { doc, path ->
    def nodes = doc
    path.split("\\.").each { nodes = nodes."${it}" }
    nodes.each { it.replaceNode{} }    
}

def resource = new XmlSlurper().parseText(xml)
def xpathsToDelete = ['/root/element1','/root/items/item/name']

xpathsToDelete.each { xpath ->
    def trimXPath = xpath.replaceFirst( "/root/", "").replace("/",".")
    removeNodes(resource, trimXPath)
}

println XmlUtil.serialize(new StreamingMarkupBuilder().bind {
    mkp.yield resource
})
2
On

This seems to work as well:

import groovy.xml.*

def xml = '''<root>
            |  <element1>foo</element1>
            |  <element2>bar</element2>
            |  <items>
            |     <item>
            |       <name>a</name>
            |       <desc>b</desc>
            |     </item>
            |     <item>
            |        <name>c</name>
            |        <desc>x</desc>
            |     </item>
            |  </items>
            |</root>'''.stripMargin()

def newxml = new XmlSlurper().parseText( xml ).with { x ->
  [ '/root/element1', '/root/items/item/name' ].each { path ->
    def s = path.split( '/' ).drop( 2 ).inject( x ) { element, p ->
      element."$p"
    }?.replaceNode {}
  }
  x
}

println XmlUtil.serialize(new StreamingMarkupBuilder().bind {
    mkp.yield newxml
})