GroovyでXPath

Groovyはあまり関係ないかもしれない。
読み込みたいデータ。

String books = '''
<books>
  <book>
    <id>01</id>
    <name>book1</name>
  </book>
  <book>
    <id>02</id>
    <name>book2</name>
  </book>
  <book>
    <id>03</id>
    <name>book3</name>
  </book>
  <book>
    <id>04</id>
    <name>book4</name>
  </book>
</books>
'''
// この辺のやり方はGroovyのドキュメントから
def builder     = DocumentBuilderFactory.newInstance().newDocumentBuilder()
def inputStream = new ByteArrayInputStream(books.bytes)
def bookData     = builder.parse(inputStream).documentElement

def factory = XPathFactory.newInstance()
def xpath = factory.newXPath()

// 「//」は全要素に対して
// NODESET指定すると、text()の取得でも#textノードの取得になったので、ループ内でtextを再取得。NODESET指定の時は普通はtext()はつけなくて良い
// 第三引数のNODESETがないと最初のnodeだけ取得される。(book1のnode)

def nameStrs = xpath.evaluate("//name/text()",bookData,XPathConstants.NODESET)
nameStrs.each{
  Node n = it
  println "name:" + n.getTextContent()
}

// 「/」は絶対パス
def bookNames = xpath.evaluate("/books/book/name",bookData,XPathConstants.NODESET)
bookNames.each{
  // 先頭のスラッシュなしは相対パス
  println "bookName:" + xpath.evaluate("text()",it)
}

XMLのノードがbooks配下にbookの繰り返しようにきれいに並んでいない場合。

String settingXml = '''
<settings>
  <id>id01</id>
  <name>name01</name>
  <category>cat01</category>
</settings>
'''

settingsが親という共通の条件を指定することでループ

def inputStream = new ByteArrayInputStream(settingXml.bytes)
def settings     = builder.parse(inputStream).documentElement
def elems = xpath.evaluate("//*[parent::settings]",settings,XPathConstants.NODESET)
elems.each{
  println it.getNodeName() + ":" + xpath.evaluate("text()",it)
// 上と同じ
//  println it.getNodeName() + ":" + it.getTextContent()
}

参考:
XPathについて
http://pearl-white.hp.infoseek.co.jp/xpath/
https://developer.mozilla.org/ja/DOM/element