處理 XML

1. 解析 XML

1.1. XmlParser 和 XmlSlurper

使用 Groovy 解析 XML 最常使用的方法是使用其中一種

  • groovy.xml.XmlParser

  • groovy.xml.XmlSlurper

這兩種方法解析 XML 的方式相同。這兩種方法都附帶一堆過載的解析方法,以及一些特殊方法,例如 parseText、parseFile 等。在接下來的範例中,我們將使用 parseText 方法。它會解析 XML 字串,並遞迴地將其轉換為物件清單或對應。

XmlSlurper
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 樣式遍歷樹狀結構
XmlParser
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 樣式遍歷樹狀結構

讓我們先看看 XMLParserXMLSlurper 之間的相似之處

  • 兩者都基於 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 文件各個部分的類別,例如 DocumentElementNodeListAttr 等。有關這些類別的更多資訊,請參閱相關的 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.DOMBuildergroovy.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() 方法會在向下導覽到下一層之前,完成給定層級中的所有節點。

以下範例顯示這兩個方法之間的差異

depthFirst() vs .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 的節點。符合此條件的節點有 bookauthor。不同的遍歷順序會在每個案例中找到相同的節點,但順序不同,對應於樹狀結構的遍歷方式。

再次值得一提的是,有一些有用的方法可以將節點的值轉換為整數、浮點數等。在進行像這樣的比較時,這些方法可能會很方便

輔助函式
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 檔案的範例

使用 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

讓我們仔細看看

建立 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
建立帶有屬性的 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 表示。
建立 XML 巢狀元素
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 來處理輸出。

使用 StreamingMarkupBuilder
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 實體

  • 在目前標籤的主體中列印資料

MarkupBuilderStreamingMarkupBuilder 中,都可以透過屬性 mkp 存取此輔助程式

使用 MarkupBuilder 的「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 &gt; n')
assert xmlWriter.toString().contains('<!-- THIS IS THE MAIN RULE -->')
1 使用 mkp 在 XML 中建立註解
2 使用 mkp 產生跳脫值
3 檢查兩個假設是否都成立

以下提供另一個範例,說明在使用 StreamingMarkupBuilder 時,如何使用 bind 方法範圍內可存取的 mkp 屬性

使用 StreamingMarkupBuilder 的「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 &lt; 5')
assert xml.toString().contains('1 < 3')
1 如果我們想要使用 mkp.yield 產生 name 屬性的跳脫值
2 稍後使用 XmlSlurper 檢查值

3.4. DOMToGroovy

假設我們有一個現有的 XML 文件,而且我們想要自動產生標記,而不必全部輸入?我們只需要使用 org.codehaus.groovy.tools.xml.DOMToGroovy,如下列範例所示

從 DOMToGroovy 建立 MarkupBuilder
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

在本章節中,您將看到使用 XmlSlurperXmlParser 新增/修改/移除節點的不同方式。我們將處理的 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. 新增節點

XmlSlurperXmlParser 之間的主要差異在於,當前者建立節點時,這些節點在文件再次評估之前將不可用,因此您應再次剖析轉換後的文件,才能看到新的節點。因此,在選擇這兩種方法之一時,請務必記住這一點。

如果您需要在建立節點後立即看到節點,則應選擇 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 執行個體建立。也就是說,從 NodeGPathResult 執行個體建立。

看看下一個範例。我們使用 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. 修改/移除節點

我們知道如何剖析文件、新增新節點,現在我想變更特定節點的內容。讓我們開始使用 XmlParserNode。此範例將第一本書的資訊實際變更為另一本書。

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>')