處理 XML
1. 解析 XML
1.1. XmlParser 和 XmlSlurper
使用 Groovy 解析 XML 最常使用的方法是使用其中一種
-
groovy.xml.XmlParser
-
groovy.xml.XmlSlurper
這兩種方法解析 XML 的方式相同。這兩種方法都附帶一堆過載的解析方法,以及一些特殊方法,例如 parseText
、parseFile 等。在接下來的範例中,我們將使用 parseText
方法。它會解析 XML 字串
,並遞迴地將其轉換為物件清單或對應。
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 並將根節點傳回為節點 |
2 | 檢查我們是否正在使用節點 |
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.dom.DOMCategory
解析 Groovy 中的 XML 文件的另一種方式,它是一個類別類別,可將 GPath 樣式操作新增至 Java 的 DOM 類別。
Java 內建對 XML 的 DOM 處理支援,使用代表 XML 文件各個部分的類別,例如 Document 、Element 、NodeList 、Attr 等。有關這些類別的更多資訊,請參閱相關的 JavaDoc。
|
有以下 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()
-
…
所有這些方法都會嘗試將 字串
轉換為適當的類型。
如果我們使用 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 | 取得第一個 book 節點 |
2 | 取得 book 的 id 屬性 @id |
3 | 使用 map 表示法 ['@id'] 取得 book 的 id 屬性 |
4 | 將值取得為字串 |
5 | 將屬性的值取得為 整數 |
正如您所見,有兩種表示法可以取得屬性,
-
直接標記使用
@nameoftheattribute
-
對應標記使用
['@nameoftheattribute']
兩者同樣有效。
2.2. 具有子節點的彈性導覽 (*), 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」節點的任何子節點,以符合給定的條件。更詳細地說,表達式表示:尋找任何標籤名稱等於「book」,且在「books」節點下具有值為「2」的 id 的節點。
此操作大致對應於 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 | xmlWriter 執行個體由 MarkupBuilder 使用,以將 XML 表示轉換成字串執行個體 |
3 | xmlMarkup.movie(…) 呼叫會建立一個 XML 節點,其標籤稱為 movie ,內容為 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.dev.org.tw') { (1)
'x:movie'(id: 1, 'the godfather')
'x:movie'(id: 2, 'ronin')
}
def movies =
new XmlSlurper() (2)
.parseText(xmlWriter.toString())
.declareNamespace(x: 'https://groovy.dev.org.tw')
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.dev.org.tw') {
(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.dev.org.tw')
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.dev.org.tw') {
buildMovieList(xmlMarkup) (2)
}
def movies =
new XmlSlurper()
.parseText(xmlWriter.toString())
.declareNamespace(x: 'https://groovy.dev.org.tw')
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 | 檢查兩個假設是否都成立 |
以下提供另一個範例,說明在使用 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 片段(節點、GPathResult、字串…)
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>')