8

Is there a way to have XmlSlurper get arbitrary elements through a variable? e.g. so I can do something like input file:

<file>
    <record name="some record" />
    <record name="some other record" />
</file>

def xml = new XmlSlurper().parse(inputFile)
String foo = "record"
return xml.{foo}.size()

I've tried using {} and ${} and () how do I escape variables like that? Or isn't there a way? and is it possible to use results from closures as the arguments as well? So I could do something like

String foo = file.record
int numRecords = xml.{foo.find(/.\w+$/)}
Keegan
  • 11,345
  • 1
  • 25
  • 38

3 Answers3

9
import groovy.xml.*

def xmltxt = """<file>
    <record name="some record" />
    <record name="some other record" />
</file>"""

def xml = new XmlSlurper().parseText(xmltxt)
String foo = "record"
return xml."${foo}".size()
John Wagenleitner
  • 10,967
  • 1
  • 40
  • 39
  • This doesn't work for elements not immediate children of the root. Observe: def xmltxt = """ """ def xml = new XmlSlurper().parseText(xmltxt) String foo = "something.record" return xml."${foo}".size() Any other suggestions? – Keegan Sep 11 '09 at 23:11
  • I don't know of any good way, you could split the string: def aNode = xml foo.split("\\.").each { aNode = aNode."${it}" } return aNode.size() – John Wagenleitner Sep 11 '09 at 23:35
  • +1. Thank you, the comment helped me, too. But please, put it into the answer body, as is, it is almost unreadable – Gangnus Apr 03 '13 at 14:03
  • It isn't exactly true that it has to be at the root, more that you can only do one part, not specify multiple parts in a single variable: def xml = new XmlSlurper().parseText(xmltxt) String part1 = "something" String part2 = "record" println(xml."${part1}"."${part2}".size()) – Frank Merrow Jul 07 '14 at 22:15
  • @JohnWagenleitner nice workaround for `node.another` paths in your comment, IMO I think that you must add it to your answer +1 `:)` – albciff May 21 '15 at 11:46
  • @JohnWagenleitner, @KeeganIt didn't work to set values `xml."${foo}"= value` . @Frank I think this case is to assign `partX` dinamically. Any other clue? – Jrr Jul 25 '16 at 13:19
6

This answer offers a code example, and owes a lot to John W's comment in his answer.

Consider this:

import groovy.util.* 

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 getNodes = { doc, path ->
    def nodes = doc
    path.split("\\.").each { nodes = nodes."${it}" }
    return nodes
}

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

xpaths.each { xpath ->
    def trimXPath = xpath.replace("/root/", "").replace("/",".")
    println "xpath = ${trimXPath}"
    getNodes(resource, trimXPath).each { println it }
}
Michael Easter
  • 23,733
  • 7
  • 76
  • 107
  • Folks, what about to assign values? I mean: `String foo = "something.record" xml."${foo}"= value ` which your [even @Keegan and, @John.W] advisement doesn't work :(, any clue? – Jrr Jul 24 '16 at 21:08
  • Folks, I found the solution, I used `def node= getNodes(..) node.replaceBody value` .. I see that I'm a trainee in XMLSlurper :( . – Jrr Jul 25 '16 at 21:10
0

Based on Michael's solution, this provides support for indexes, like '/root/element1[1]'

def getNodes = { doc, path ->
    def nodes = doc
    path.split("\\.").each {
      if (it.endsWith(']'))
      {
        def name_index = it.tokenize('[]') // "attributes[1]" => ["attributes", "1"]
        nodes = nodes."${name_index[0]}"[name_index[1].toInteger()]
      }
      else nodes = nodes."${it}"
    }
    return nodes
}
Pablo Pazos
  • 3,080
  • 29
  • 42