1. 解析 XML
1.1. XmlParser 和 XmlSlurper
使用 Groovy 解析 XML 最常用的方法是使用以下之一:
- 
groovy.xml.XmlParser
- 
groovy.xml.XmlSlurper
两者都采用相同的方法解析 XML。两者都带有一堆重载的解析方法以及一些特殊方法,如 parseText、parseFile 等。在下一个示例中,我们将使用 parseText 方法。它解析一个 XML String 并递归地将其转换为对象列表或映射。
def text = '''
    <list>
        <technology>
            <name>Groovy</name>
        </technology>
    </list>
'''
def list = new XmlSlurper().parseText(text) (1)
assert list instanceof groovy.xml.slurpersupport.GPathResult (2)
assert list.technology.name == 'Groovy' (3)| 1 | 解析 XML 并将根节点作为 GPathResult 返回 | 
| 2 | 检查我们是否正在使用 GPathResult | 
| 3 | 以 GPath 风格遍历树 | 
def text = '''
    <list>
        <technology>
            <name>Groovy</name>
        </technology>
    </list>
'''
def list = new XmlParser().parseText(text) (1)
assert list instanceof groovy.util.Node (2)
assert list.technology.name.text() == 'Groovy' (3)| 1 | 解析 XML 并将根节点作为 Node 返回 | 
| 2 | 检查我们是否正在使用 Node | 
| 3 | 以 GPath 风格遍历树 | 
首先,让我们看看 XMLParser 和 XMLSlurper 之间的相似之处
- 
两者都基于 SAX,因此内存占用都较低
- 
两者都可以更新/转换 XML 
但它们有关键的区别
- 
XmlSlurper惰性地评估结构。因此,如果您更新 XML,则必须再次评估整个树。
- 
XmlSlurper在解析 XML 时返回GPathResult实例
- 
XmlParser在解析 XML 时返回Node对象
何时使用其中一个?
| 在 StackOverflow 上有一个讨论。此处撰写的结论部分基于此条目。 | 
- 
如果您想将现有文档转换为另一个文档,那么 XmlSlurper将是您的选择
- 
如果您想同时更新和读取,那么 XmlParser是您的选择。
这背后的原理是,每次使用 XmlSlurper 创建节点时,除非您使用另一个 XmlSlurper 实例再次解析文档,否则它将不可用。只需要读取几个节点,XmlSlurper 就适合您。
- 
如果您只需要读取几个节点, XmlSlurper应该是您的选择,因为它不必在内存中创建完整的结构。
一般来说,这两个类的性能相似。甚至与它们一起使用 GPath 表达式的方式也相同(两者都使用 breadthFirst() 和 depthFirst() 表达式)。所以我想这取决于写入/读取频率。
1.2. DOMCategory
使用 Groovy 解析 XML 文档还有另一种方法,即使用 groovy.xml.dom.DOMCategory,它是一个类别类,为 Java 的 DOM 类添加了 GPath 风格的操作。
| Java 使用表示 XML 文档各个部分的类(例如 Document、Element、NodeList、Attr等)内置支持 XML 的 DOM 处理。有关这些类的更多信息,请参阅相应的 JavaDocs。 | 
有以下 XML
static def CAR_RECORDS = '''
<records>
  <car name='HSV Maloo' make='Holden' year='2006'>
    <country>Australia</country>
    <record type='speed'>Production Pickup Truck with speed of 271kph</record>
  </car>
  <car name='P50' make='Peel' year='1962'>
    <country>Isle of Man</country>
    <record type='size'>Smallest Street-Legal Car at 99cm wide and 59 kg in weight</record>
  </car>
  <car name='Royale' make='Bugatti' year='1931'>
    <country>France</country>
    <record type='price'>Most Valuable Car at $15 million</record>
  </car>
</records>
'''您可以使用 groovy.xml.DOMBuilder 和 groovy.xml.dom.DOMCategory 对其进行解析。
def reader = new StringReader(CAR_RECORDS)
def doc = DOMBuilder.parse(reader) (1)
def records = doc.documentElement
use(DOMCategory) { (2)
    assert records.car.size() == 3
}| 1 | 解析 XML | 
| 2 | 创建 DOMCategory范围以能够使用辅助方法调用 | 
2. GPath
在 Groovy 中查询 XML 最常用的方法是使用 GPath
GPath 是集成到 Groovy 中的路径表达式语言,它允许识别嵌套结构化数据的部分。从这个意义上说,它与 XPath 对 XML 的目标和范围相似。您使用 GPath 表达式的两个主要地方是处理嵌套的 POJO 或处理 XML 时。
它类似于 XPath 表达式,您不仅可以将其与 XML 一起使用,还可以与 POJO 类一起使用。例如,您可以指定到感兴趣的对象或元素的路径
- 
a.b.c→ 对于 XML,生成<a>中<b>中所有<c>元素
- 
a.b.c→ 所有 POJO,生成<a>的所有<b>属性的<c>属性(有点像 JavaBeans 中的 a.getB().getC())
对于 XML,您还可以指定属性,例如:
- 
a["@href"]→ 所有 a 元素的 href 属性
- 
a.'@href'→ 表达此方式的另一种方式
- 
a.@href→ 使用 XmlSlurper 时表达此方式的另一种方式
让我们用一个例子来说明这一点
static final String books = '''
    <response version-api="2.0">
        <value>
            <books>
                <book available="20" id="1">
                    <title>Don Quixote</title>
                    <author id="1">Miguel de Cervantes</author>
                </book>
                <book available="14" id="2">
                    <title>Catcher in the Rye</title>
                   <author id="2">JD Salinger</author>
               </book>
               <book available="13" id="3">
                   <title>Alice in Wonderland</title>
                   <author id="3">Lewis Carroll</author>
               </book>
               <book available="5" id="4">
                   <title>Don Quixote</title>
                   <author id="4">Miguel de Cervantes</author>
               </book>
           </books>
       </value>
    </response>
'''2.1. 简单遍历树结构
我们能做的第一件事是使用 POJO 的表示法获取一个值。让我们获取第一本书的作者姓名
def response = new XmlSlurper().parseText(books)
def authorResult = response.value.books.book[0].author
assert authorResult.text() == 'Miguel de Cervantes'首先,我们使用 XmlSlurper 解析文档,然后必须将返回值视为 XML 文档的根,因此在本例中是 "response"。
这就是为什么我们从 response 开始遍历文档,然后是 value.books.book[0].author。请注意,在 XPath 中,节点数组从 [1] 开始而不是 [0],但由于 GPath 是基于 Java 的,因此它从索引 0 开始。
最后,我们将拥有 author 节点的实例,因为我们想要该节点内的文本,所以我们应该调用 text() 方法。author 节点是 GPathResult 类型的一个实例,text() 是一个方法,它以字符串形式为我们提供该节点的内容。
当将 GPath 与使用 XmlSlurper 解析的 XML 一起使用时,我们将得到一个 GPathResult 对象作为结果。GPathResult 还有许多其他方便的方法可以将节点内的文本转换为任何其他类型,例如:
- 
toInteger()
- 
toFloat()
- 
toBigInteger()
- 
... 
所有这些方法都尝试将 String 转换为适当的类型。
如果我们使用 XmlParser 解析的 XML,我们可能会处理 Node 类型的实例。但是,在这些示例中应用于 GPathResult 的所有操作也可以应用于 Node。这两个解析器的创建者都考虑了 GPath 兼容性。
下一步是从给定节点的属性中获取一些值。在下面的示例中,我们想获取第一本书作者的 ID。我们将使用两种不同的方法。首先看代码
def response = new XmlSlurper().parseText(books)
def book = response.value.books.book[0] (1)
def bookAuthorId1 = book.@id (2)
def bookAuthorId2 = book['@id'] (3)
assert bookAuthorId1 == '1' (4)
assert bookAuthorId1.toInteger() == 1 (5)
assert bookAuthorId1 == bookAuthorId2| 1 | 获取第一个图书节点 | 
| 2 | 获取图书的 ID 属性 @id | 
| 3 | 使用 map notation['@id']获取图书的 ID 属性 | 
| 4 | 将值作为字符串获取 | 
| 5 | 将属性值作为 Integer获取 | 
如您所见,有两种表示法来获取属性:
- 
直接表示法 使用 @nameoftheattribute
- 
映射表示法 使用 ['@nameoftheattribute']
两者都同样有效。
2.2. 使用 children (*)、depthFirst (**) 和 breadthFirst 进行灵活导航
如果您曾经使用过 XPath,您可能使用过如下表达式:
- 
/following-sibling::othernode: 在同一级别查找节点 "othernode"
- 
//: 在任何地方查找
我们或多或少地在 GPath 中有它们的对应项,使用快捷方式 *(也称为 children())和 **(也称为 depthFirst())。
第一个示例展示了 * 的简单用法,它只迭代节点的直接子节点。
def response = new XmlSlurper().parseText(books)
// .'*' could be replaced by .children()
def catcherInTheRye = response.value.books.'*'.find { node ->
    // node.@id == 2 could be expressed as node['@id'] == 2
    node.name() == 'book' && node.@id == '2'
}
assert catcherInTheRye.title.text() == 'Catcher in the Rye'此测试搜索“books”节点的任何子节点,这些子节点与给定条件匹配。更详细地说,表达式表示:查找直接位于“books”节点下,标签名为“book”,ID 值为“2”的任何节点。
此操作大致对应于 breadthFirst() 方法,不同之处在于它只停止在一层而不是继续到内部层。
如果我们想查找一个给定值而不需要确切知道它在哪里,该怎么办?假设我们只知道作者“Lewis Carroll”的 ID。我们将如何找到那本书?使用 ** 是解决方案。
def response = new XmlSlurper().parseText(books)
// .'**' could be replaced by .depthFirst()
def bookId = response.'**'.find { book ->
    book.author.text() == 'Lewis Carroll'
}.@id
assert bookId == 3** 就像从这一点向下在树的任何地方查找某物一样。在这种情况下,我们使用了 find(Closure cl) 方法来查找第一个匹配项。
如果我们想收集所有书名怎么办?这很容易,只需使用 findAll
def response = new XmlSlurper().parseText(books)
def titles = response.'**'.findAll { node -> node.name() == 'title' }*.text()
assert titles.size() == 4在最后两个示例中,** 用作 depthFirst() 方法的快捷方式。它在从给定节点向下遍历树时尽可能深入地遍历树。breadthFirst() 方法在遍历到下一层之前完成给定级别的所有节点。
以下示例显示了这两种方法之间的区别
def response = new XmlSlurper().parseText(books)
def nodeName = { node -> node.name() }
def withId2or3 = { node -> node.@id in [2, 3] }
assert ['book', 'author', 'book', 'author'] ==
        response.value.books.depthFirst().findAll(withId2or3).collect(nodeName)
assert ['book', 'book', 'author', 'author'] ==
        response.value.books.breadthFirst().findAll(withId2or3).collect(nodeName)在此示例中,我们搜索具有 ID 属性值为 2 或 3 的任何节点。同时存在匹配该条件的 book 和 author 节点。不同的遍历顺序在每种情况下都会找到相同的节点,但顺序不同,这取决于树的遍历方式。
值得再次提及的是,有一些有用的方法可以将节点的值转换为整数、浮点数等。这些方法在进行类似这样的比较时可能很方便:
def response = new XmlSlurper().parseText(books)
def titles = response.value.books.book.findAll { book ->
    /* You can use toInteger() over the GPathResult object */
    book.@id.toInteger() > 2
}*.title
assert titles.size() == 2在这种情况下,数字 2 已被硬编码,但想象该值可能来自任何其他来源(数据库等)。
3. 创建 XML
使用 Groovy 创建 XML 最常用的方法是使用构建器,即以下之一:
- 
groovy.xml.MarkupBuilder
- 
groovy.xml.StreamingMarkupBuilder
3.1. MarkupBuilder
下面是使用 Groovy 的 MarkupBuilder 创建新 XML 文件的示例
def writer = new StringWriter()
def xml = new MarkupBuilder(writer) (1)
xml.records() { (2)
    car(name: 'HSV Maloo', make: 'Holden', year: 2006) {
        country('Australia')
        record(type: 'speed', 'Production Pickup Truck with speed of 271kph')
    }
    car(name: 'Royale', make: 'Bugatti', year: 1931) {
        country('France')
        record(type: 'price', 'Most Valuable Car at $15 million')
    }
}
def records = new XmlSlurper().parseText(writer.toString()) (3)
assert records.car.first().name.text() == 'HSV Maloo'
assert records.car.last().name.text() == 'Royale'| 1 | 创建 MarkupBuilder实例 | 
| 2 | 开始创建 XML 树 | 
| 3 | 创建 XmlSlurper实例以遍历和测试生成的 XML | 
让我们仔细看看
def xmlString = "<movie>the godfather</movie>" (1)
def xmlWriter = new StringWriter() (2)
def xmlMarkup = new MarkupBuilder(xmlWriter)
xmlMarkup.movie("the godfather") (3)
assert xmlString == xmlWriter.toString() (4)| 1 | 我们正在创建一个参考字符串用于比较 | 
| 2 | MarkupBuilder使用xmlWriter实例最终将 XML 表示转换为 String 实例 | 
| 3 | xmlMarkup.movie(…)调用将创建一个标签为movie且内容为the godfather的 XML 节点。 | 
def xmlString = "<movie id='2'>the godfather</movie>"
def xmlWriter = new StringWriter()
def xmlMarkup = new MarkupBuilder(xmlWriter)
xmlMarkup.movie(id: "2", "the godfather") (1)
assert xmlString == xmlWriter.toString()| 1 | 这次为了创建属性和节点内容,您可以创建任意数量的映射条目,最后添加一个值来设置节点的内容 | 
| 值可以是任何 Object,该值将被序列化为其String表示形式。 | 
def xmlWriter = new StringWriter()
def xmlMarkup = new MarkupBuilder(xmlWriter)
xmlMarkup.movie(id: 2) { (1)
    name("the godfather")
}
def movie = new XmlSlurper().parseText(xmlWriter.toString())
assert movie.@id == 2
assert movie.name.text() == 'the godfather'| 1 | 闭包表示给定节点的子元素。请注意,这次我们使用数字而不是字符串作为属性。 | 
有时您可能希望在 XML 文档中使用特定的命名空间
def xmlWriter = new StringWriter()
def xmlMarkup = new MarkupBuilder(xmlWriter)
xmlMarkup
        .'x:movies'('xmlns:x': 'https://groovy-lang.cn') { (1)
    'x:movie'(id: 1, 'the godfather')
    'x:movie'(id: 2, 'ronin')
}
def movies =
        new XmlSlurper() (2)
                .parseText(xmlWriter.toString())
                .declareNamespace(x: 'https://groovy-lang.cn')
assert movies.'x:movie'.last().@id == 2
assert movies.'x:movie'.last().text() == 'ronin'| 1 | 创建具有给定命名空间 xmlns:x的节点 | 
| 2 | 创建 XmlSlurper并注册命名空间,以便能够测试我们刚刚创建的 XML | 
更多有意义的例子呢?我们可能希望生成更多元素,以便在创建 XML 时有一些逻辑
def xmlWriter = new StringWriter()
def xmlMarkup = new MarkupBuilder(xmlWriter)
xmlMarkup
        .'x:movies'('xmlns:x': 'https://groovy-lang.cn') {
    (1..3).each { n -> (1)
        'x:movie'(id: n, "the godfather $n")
        if (n % 2 == 0) { (2)
            'x:movie'(id: n, "the godfather $n (Extended)")
        }
    }
}
def movies =
        new XmlSlurper()
                .parseText(xmlWriter.toString())
                .declareNamespace(x: 'https://groovy-lang.cn')
assert movies.'x:movie'.size() == 4
assert movies.'x:movie'*.text().every { name -> name.startsWith('the') }| 1 | 从范围生成元素 | 
| 2 | 使用条件语句创建给定元素 | 
当然,构建器实例可以作为参数传递,以重构/模块化您的代码
def xmlWriter = new StringWriter()
def xmlMarkup = new MarkupBuilder(xmlWriter)
(1)
Closure<MarkupBuilder> buildMovieList = { MarkupBuilder builder ->
    (1..3).each { n ->
        builder.'x:movie'(id: n, "the godfather $n")
        if (n % 2 == 0) {
            builder.'x:movie'(id: n, "the godfather $n (Extended)")
        }
    }
    return builder
}
xmlMarkup.'x:movies'('xmlns:x': 'https://groovy-lang.cn') {
    buildMovieList(xmlMarkup) (2)
}
def movies =
        new XmlSlurper()
                .parseText(xmlWriter.toString())
                .declareNamespace(x: 'https://groovy-lang.cn')
assert movies.'x:movie'.size() == 4
assert movies.'x:movie'*.text().every { name -> name.startsWith('the') }| 1 | 在这种情况下,我们创建了一个闭包来处理电影列表的创建 | 
| 2 | 仅在必要时使用 buildMovieList函数 | 
3.2. StreamingMarkupBuilder
类 groovy.xml.StreamingMarkupBuilder 是一个用于创建 XML 标记的构建器类。此实现使用 groovy.xml.streamingmarkupsupport.StreamingMarkupWriter 处理输出。
def xml = new StreamingMarkupBuilder().bind { (1)
    records {
        car(name: 'HSV Maloo', make: 'Holden', year: 2006) { (2)
            country('Australia')
            record(type: 'speed', 'Production Pickup Truck with speed of 271kph')
        }
        car(name: 'P50', make: 'Peel', year: 1962) {
            country('Isle of Man')
            record(type: 'size', 'Smallest Street-Legal Car at 99cm wide and 59 kg in weight')
        }
        car(name: 'Royale', make: 'Bugatti', year: 1931) {
            country('France')
            record(type: 'price', 'Most Valuable Car at $15 million')
        }
    }
}
def records = new XmlSlurper().parseText(xml.toString()) (3)
assert records.car.size() == 3
assert records.car.find { it.@name == 'P50' }.country.text() == 'Isle of Man'| 1 | 请注意, StreamingMarkupBuilder.bind返回一个Writable实例,该实例可用于将标记流式传输到 Writer | 
| 2 | 我们正在将输出捕获到一个 String 中,以便再次解析它并使用 XmlSlurper检查生成的 XML 的结构。 | 
3.3. MarkupBuilderHelper
groovy.xml.MarkupBuilderHelper,顾名思义,是 groovy.xml.MarkupBuilder 的辅助类。
通常可以从 groovy.xml.MarkupBuilder 实例或 groovy.xml.StreamingMarkupBuilder 实例内部访问此帮助器。
在以下情况下,此帮助器可能会派上用场:
- 
在输出中生成注释 
- 
在输出中生成 XML 处理指令 
- 
在输出中生成 XML 声明 
- 
在当前标签的主体中打印数据,转义 XML 实体 
- 
在当前标签的主体中打印数据 
在 MarkupBuilder 和 StreamingMarkupBuilder 中,此帮助器通过属性 mkp 访问
def xmlWriter = new StringWriter()
def xmlMarkup = new MarkupBuilder(xmlWriter).rules {
    mkp.comment('THIS IS THE MAIN RULE') (1)
    rule(sentence: mkp.yield('3 > n')) (2)
}
(3)
assert xmlWriter.toString().contains('3 > n')
assert xmlWriter.toString().contains('<!-- THIS IS THE MAIN RULE -->')| 1 | 使用 mkp在 XML 中创建注释 | 
| 2 | 使用 mkp生成转义值 | 
| 3 | 检查两个假设是否都正确 | 
这是另一个示例,展示了在使用 StreamingMarkupBuilder 时,从 bind 方法作用域内访问的 mkp 属性的用法
def xml = new StreamingMarkupBuilder().bind {
    records {
        car(name: mkp.yield('3 < 5')) (1)
        car(name: mkp.yieldUnescaped('1 < 3')) (2)
    }
}
assert xml.toString().contains('3 < 5')
assert xml.toString().contains('1 < 3')| 1 | 如果我们想用 mkp.yield为 name 属性生成一个转义值 | 
| 2 | 稍后使用 XmlSlurper检查值 | 
3.4. DOMToGroovy
假设我们有一个现有的 XML 文档,并且我们想自动化标记的生成,而无需手动输入所有内容?我们只需使用 org.codehaus.groovy.tools.xml.DOMToGroovy,如下例所示
def songs = """
    <songs>
      <song>
        <title>Here I go</title>
        <band>Whitesnake</band>
      </song>
    </songs>
"""
def builder =
        javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder()
def inputStream = new ByteArrayInputStream(songs.bytes)
def document = builder.parse(inputStream)
def output = new StringWriter()
def converter = new DomToGroovy(new PrintWriter(output)) (1)
converter.print(document) (2)
String xmlRecovered =
        new GroovyShell()
                .evaluate("""
           def writer = new StringWriter()
           def builder = new groovy.xml.MarkupBuilder(writer)
           builder.${output}
           return writer.toString()
        """) (3)
assert new XmlSlurper().parseText(xmlRecovered).song.title.text() == 'Here I go' (4)| 1 | 创建 DOMToGroovy实例 | 
| 2 | 将 XML 转换为 MarkupBuilder调用,这些调用在输出StringWriter中可用 | 
| 3 | 使用 output变量创建整个 MarkupBuilder | 
| 4 | 回到 XML 字符串 | 
4. 操作 XML
在本章中,您将看到使用 XmlSlurper 或 XmlParser 添加/修改/删除节点的不同方法。我们将要处理的 xml 如下所示
def xml = """
<response version-api="2.0">
    <value>
        <books>
            <book id="2">
                <title>Don Quixote</title>
                <author id="1">Miguel de Cervantes</author>
            </book>
        </books>
    </value>
</response>
"""4.1. 添加节点
XmlSlurper 和 XmlParser 的主要区别在于,前者创建节点后,在文档再次评估之前它们不会可用,因此您应该再次解析转换后的文档才能看到新节点。因此,在选择这两种方法时请记住这一点。
如果您需要在创建节点后立即查看它,那么 XmlParser 应该是您的选择,但如果您打算对 XML 进行许多更改并将结果发送到另一个进程,那么 XmlSlurper 可能更高效。
您不能直接使用 XmlSlurper 实例创建新节点,但可以使用 XmlParser。从 XmlParser 创建新节点的方法是通过其方法 createNode(..)。
def parser = new XmlParser()
def response = parser.parseText(xml)
def numberOfResults = parser.createNode(
        response,
        new QName("numberOfResults"),
        [:]
)
numberOfResults.value = "1"
assert response.numberOfResults.text() == "1"createNode() 方法接收以下参数:
- 
父节点(可以为 null) 
- 
标签的限定名称(在这种情况下,我们只使用本地部分,不带任何命名空间)。我们正在使用 groovy.namespace.QName的实例
- 
一个包含标签属性的映射(在此特定情况下没有) 
无论如何,您通常不会从解析器实例创建节点,而是从解析的 XML 实例创建节点。也就是说,从 Node 或 GPathResult 实例创建节点。
请看下一个示例。我们使用 XmlParser 解析 XML,然后从解析的文档实例创建新节点(请注意,这里的方法在接收参数的方式上略有不同)
def parser = new XmlParser()
def response = parser.parseText(xml)
response.appendNode(
        new QName("numberOfResults"),
        [:],
        "1"
)
response.numberOfResults.text() == "1"使用 XmlSlurper 时,GPathResult 实例没有 createNode() 方法。
4.2. 修改/删除节点
我们知道如何解析文档、添加新节点,现在我想更改给定节点的内容。让我们开始使用 XmlParser 和 Node。此示例将第一本书的信息更改为另一本书。
def response = new XmlParser().parseText(xml)
/* Use the same syntax as groovy.xml.MarkupBuilder */
response.value.books.book[0].replaceNode { (1)
    book(id: "3") {
        title("To Kill a Mockingbird")
        author(id: "3", "Harper Lee")
    }
}
def newNode = response.value.books.book[0]
assert newNode.name() == "book"
assert newNode.@id == "3"
assert newNode.title.text() == "To Kill a Mockingbird"
assert newNode.author.text() == "Harper Lee"
assert newNode.author.@id.first() == "3"使用 replaceNode() 时,我们作为参数传递的闭包应遵循与使用 groovy.xml.MarkupBuilder 相同的规则
这是使用 XmlSlurper 的相同示例
def response = new XmlSlurper().parseText(books)
/* Use the same syntax as groovy.xml.MarkupBuilder */
response.value.books.book[0].replaceNode {
    book(id: "3") {
        title("To Kill a Mockingbird")
        author(id: "3", "Harper Lee")
    }
}
assert response.value.books.book[0].title.text() == "Don Quixote"
/* That mkp is a special namespace used to escape away from the normal building mode
   of the builder and get access to helper markup methods
   'yield', 'pi', 'comment', 'out', 'namespaces', 'xmlDeclaration' and
   'yieldUnescaped' */
def result = new StreamingMarkupBuilder().bind { mkp.yield response }.toString()
def changedResponse = new XmlSlurper().parseText(result)
assert changedResponse.value.books.book[0].title.text() == "To Kill a Mockingbird"请注意,使用 XmlSlurper 时,我们必须再次解析转换后的文档才能找到创建的节点。在这个特定的例子中,这可能有点烦人,不是吗?
最后,两个解析器也使用相同的方法向给定属性添加新属性。这次的区别在于您是否希望新节点立即可用。首先是 XmlParser
def parser = new XmlParser()
def response = parser.parseText(xml)
response.@numberOfResults = "1"
assert response.@numberOfResults == "1"和 XmlSlurper
def response = new XmlSlurper().parseText(books)
response.@numberOfResults = "2"
assert response.@numberOfResults == "2"使用 XmlSlurper 时,添加新属性不需要执行新的评估。
4.3. 打印 XML
4.3.1. XmlUtil
有时,不仅获取给定节点的值,还获取节点本身(例如,将此节点添加到另一个 XML)很有用。
为此,您可以使用 groovy.xml.XmlUtil 类。它有几个静态方法可以将 XML 片段从多种类型的源(Node、GPathResult、String…)序列化
def response = new XmlParser().parseText(xml)
def nodeToSerialize = response.'**'.find { it.name() == 'author' }
def nodeAsText = XmlUtil.serialize(nodeToSerialize)
assert nodeAsText ==
        XmlUtil.serialize('<?xml version="1.0" encoding="UTF-8"?><author id="1">Miguel de Cervantes</author>')