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 表示法 ['@id'] 获取书籍的 ID 属性 |
4 | 将值作为字符串获取 |
5 | 将属性的值作为 Integer 获取 |
如你所见,有两种类型的表示法可以获取属性:
-
直接表示法,使用
@nameoftheattribute
-
map 表示法,使用
['@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 */
[email protected]() > 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 | xmlWriter 实例由 MarkupBuilder 用于将 xml 表示最终转换为 String 实例 |
3 | xmlMarkup.movie(…) 调用将创建一个带有名为 movie 的标签的 XML 节点,其内容为 the godfather 。 |
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 | 我们正在字符串中捕获输出以再次解析它并使用 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 | 检查两个假设是否都为真 |
以下是一个使用 mkp
属性的示例,该属性在使用 StreamingMarkupBuilder
时可在 bind
方法范围内访问
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()
方法接收以下参数
-
父节点(可以为空)
-
标签的限定名称(在本例中,我们只使用本地部分,没有任何命名空间)。我们使用
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 [email protected]() == "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
类。它具有几个静态方法来从多种类型源(节点、GPathResult、字符串等)序列化 xml 片段。
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>')