1. 序章

Groovy は、XMLコンテンツのトラバースと操作専用の多数のメソッドを提供します。

このチュートリアルでは、さまざまなアプローチを使用して、Groovy でXMLから要素を追加、編集、または削除する方法を示します。 また、XML構造を最初から作成する方法も示します。

2. モデルの定義

例全体で使用するリソースディレクトリにXML構造を定義しましょう。

<articles>
    <article>
        <title>First steps in Java</title>
        <author id="1">
            <firstname>Siena</firstname>
            <lastname>Kerr</lastname>
        </author>
        <release-date>2018-12-01</release-date>
    </article>
    <article>
        <title>Dockerize your SpringBoot application</title>
        <author id="2">
            <firstname>Jonas</firstname>
            <lastname>Lugo</lastname>
        </author>
        <release-date>2018-12-01</release-date>
    </article>
    <article>
        <title>SpringBoot tutorial</title>
        <author id="3">
            <firstname>Daniele</firstname>
            <lastname>Ferguson</lastname>
        </author>
        <release-date>2018-06-12</release-date>
    </article>
    <article>
        <title>Java 12 insights</title>
        <author id="1">
            <firstname>Siena</firstname>
            <lastname>Kerr</lastname>
        </author>
        <release-date>2018-07-22</release-date>
    </article>
</articles>

そしてそれをInputStream変数に読み込みます。

def xmlFile = getClass().getResourceAsStream("articles.xml")

3. XmlParser

XmlParserクラスでこのストリームの探索を始めましょう。

3.1. 読む

XMLファイルの読み取りと解析は、おそらく開発者が実行しなければならない最も一般的なXML操作です。 XmlParser は、まさにそのための非常に単純なインターフェースを提供します。

def articles = new XmlParser().parse(xmlFile)

この時点で、GPath式を使用してXML構造の属性と値にアクセスできます。 

次に、 Spock を使用して簡単なテストを実装し、articlesオブジェクトが正しいかどうかを確認します。

def "Should read XML file properly"() {
    given: "XML file"

    when: "Using XmlParser to read file"
    def articles = new XmlParser().parse(xmlFile)

    then: "Xml is loaded properly"
    articles.'*'.size() == 4
    articles.article[0].author.firstname.text() == "Siena"
    articles.article[2].'release-date'.text() == "2018-06-12"
    articles.article[3].title.text() == "Java 12 insights"
    articles.article.find { it.author.'@id'.text() == "3" }.author.firstname.text() == "Daniele"
}

XML値にアクセスする方法とGPath式を使用する方法を理解するために、 XmlParser#parse操作の結果の内部構造に少し焦点を当てましょう。

The 記事オブジェクトはのインスタンスです groovy.util.Node。 毎日ノード名前、属性マップ、値、および親(どちらでもかまいません)で構成されますヌルまたは別のノード)

この場合、articlesの値はgroovy.util.NodeListインスタンスであり、Nodeのコレクションのラッパークラスです。 NodeList は、 java.util.ArrayList クラスを拡張し、インデックスによる要素の抽出を提供します。 ノードの文字列値を取得するには、 groovy.util.Node#text()。を使用します。

上記の例では、いくつかのGPath式を導入しました。

  • articles.article [0] .author.firstname —最初の記事の著者の名を取得します– articles.article[n]nに直接アクセスします記事
  • ‘*’ article の子のリストを取得します– groovy.util.Node#children()と同等です
  • author。’@id’ author要素のid属性を取得–author。’@attributeName’ は、その属性値にアクセスします。名前(同等のものは次のとおりです: author [‘@id’]およびauthor。@id

3.2. ノードの追加

前の例と同様に、最初にXMLコンテンツを変数に読み込みます。 これにより、新しいノードを定義し、 groovy.util.Node#append。を使用して記事リストに追加できるようになります。

ここで、私たちのポイントを証明するテストを実装しましょう。

def "Should add node to existing xml using NodeBuilder"() {
    given: "XML object"
    def articles = new XmlParser().parse(xmlFile)

    when: "Adding node to xml"
    def articleNode = new NodeBuilder().article(id: '5') {
        title('Traversing XML in the nutshell')
        author {
            firstname('Martin')
            lastname('Schmidt')
        }
        'release-date'('2019-05-18')
    }
    articles.append(articleNode)

    then: "Node is added to xml properly"
    articles.'*'.size() == 5
    articles.article[4].title.text() == "Traversing XML in the nutshell"
}

上記の例でわかるように、プロセスは非常に簡単です。

また、 groovy.util.NodeBuilder、を使用したことにも注意してください。これは、Node定義にNodeコンストラクターを使用する代わりに使用できます。

3.3. ノードの変更

XmlParserを使用してノードの値を変更することもできます。 そのために、XMLファイルの内容をもう一度解析してみましょう。 次に、Nodeオブジェクトのvalueフィールドを変更して、コンテンツノードを編集できます。

XmlParser はGPath式を使用しますが、常に NodeList、のインスタンスを取得するため、最初の(そして唯一の)要素を変更するには、そのインデックスを使用してアクセスする必要があることに注意してください。 。

簡単なテストを書いて、仮定を確認しましょう。

def "Should modify node"() {
    given: "XML object"
    def articles = new XmlParser().parse(xmlFile)

    when: "Changing value of one of the nodes"
    articles.article.each { it.'release-date'[0].value = "2019-05-18" }

    then: "XML is updated"
    articles.article.findAll { it.'release-date'.text() != "2019-05-18" }.isEmpty()
}

上記の例では、 Groovy CollectionsAPIを使用してNodeListをトラバースしました。

3.4. ノードの交換

次に、値の1つを変更するだけでなく、ノード全体を置き換える方法を見てみましょう。

新しい要素を追加するのと同様に、NodeBuilderNode定義に使用し、 groovy.util.Node#replaceNodeを使用してその中の既存のノードの1つを置き換えます。

def "Should replace node"() {
    given: "XML object"
    def articles = new XmlParser().parse(xmlFile)

    when: "Adding node to xml"
    def articleNode = new NodeBuilder().article(id: '5') {
        title('Traversing XML in the nutshell')
        author {
            firstname('Martin')
            lastname('Schmidt')
        }
        'release-date'('2019-05-18')
    }
    articles.article[0].replaceNode(articleNode)

    then: "Node is added to xml properly"
    articles.'*'.size() == 4
    articles.article[0].title.text() == "Traversing XML in the nutshell"
}

3.5. ノードの削除

XmlParserを使用してノードを削除するのは非常に注意が必要です。 Nodeクラスはremove(Node child)メソッドを提供しますが、ほとんどの場合、それを単独で使用することはありません。

代わりに、値が特定の条件を満たすノードを削除する方法を示します。

デフォルトでは、 Node.NodeList 参照のチェーンを使用してネストされた要素にアクセスすると、対応する子ノードのコピーが返されます。 そのため、articleコレクションでjava.util.NodeList#removeAllメソッドを直接使用することはできません。

述語でノードを削除するには、最初に条件に一致するすべてのノードを見つけてから、それらを反復処理し、親に対して毎回 java.util.Node#removeメソッドを呼び出す必要があります。[ X196X]

著者が3以外のIDを持つすべての記事を削除するテストを実装しましょう。

def "Should remove article from xml"() {
    given: "XML object"
    def articles = new XmlParser().parse(xmlFile)

    when: "Removing all articles but the ones with id==3"
    articles.article
      .findAll { it.author.'@id'.text() != "3" }
      .each { articles.remove(it) }

    then: "There is only one article left"
    articles.children().size() == 1
    articles.article[0].author.'@id'.text() == "3"
}

ご覧のとおり、削除操作の結果、記事が1つだけのXML構造を受け取り、そのIDは3です。

4. XmlSlurper

Groovyは、XMLの操作専用の別のクラスも提供します。 このセクションでは、XmlSlurper。を使用してXML構造を読み取って操作する方法を示します。

4.1. 読む

前の例のように、ファイルからXML構造を解析することから始めましょう。

def "Should read XML file properly"() {
    given: "XML file"

    when: "Using XmlSlurper to read file"
    def articles = new XmlSlurper().parse(xmlFile)

    then: "Xml is loaded properly"
    articles.'*'.size() == 4
    articles.article[0].author.firstname == "Siena"
    articles.article[2].'release-date' == "2018-06-12"
    articles.article[3].title == "Java 12 insights"
    articles.article.find { it.author.'@id' == "3" }.author.firstname == "Daniele"
}

ご覧のとおり、インターフェイスはXmlParserのインターフェイスと同じです。 ただし、出力構造は、Nodeのラッパークラスであるgroovy.util.slurpersupport.GPathResultを使用します。 GPathResult は、 Node#text()。をラップすることにより、 equals() toString()などのメソッドの簡略化された定義を提供します。その結果、名前だけを使用してフィールドとパラメーターを直接読み取ることができます。

4.2. ノードの追加

ノードの追加も、XmlParserの使用と非常によく似ています。 ただし、この場合、groovy.util.slurpersupport。 GPathResult#appendNode は、java.lang.Objectのインスタンスを引数として取るメソッドを提供します。 その結果、 Node Builder で導入されたのと同じ規則に従って、新しいNode定義を簡略化できます。

def "Should add node to existing xml"() {
    given: "XML object"
    def articles = new XmlSlurper().parse(xmlFile)

    when: "Adding node to xml"
    articles.appendNode {
        article(id: '5') {
            title('Traversing XML in the nutshell')
            author {
                firstname('Martin')
                lastname('Schmidt')
            }
            'release-date'('2019-05-18')
        }
    }

    articles = new XmlSlurper().parseText(XmlUtil.serialize(articles))

    then: "Node is added to xml properly"
    articles.'*'.size() == 5
    articles.article[4].title == "Traversing XML in the nutshell"
}

XmlSlurper、を使用してXMLの構造を変更する必要がある場合は、articlesオブジェクトを再初期化して結果を確認する必要があります。 groovy.util.XmlSlurper#parseTextメソッドとgroovy.xmlXmlUtil#serializeメソッドの組み合わせを使用してこれを実現できます。

4.3. ノードの変更

前に述べたように、 GPathResult は、データ操作への単純化されたアプローチを導入します。 そうは言っても、 XmlSlurper、とは対照的に、ノード名またはパラメーター名を使用して値を直接変更できます。

def "Should modify node"() {
    given: "XML object"
    def articles = new XmlSlurper().parse(xmlFile)

    when: "Changing value of one of the nodes"
    articles.article.each { it.'release-date' = "2019-05-18" }

    then: "XML is updated"
    articles.article.findAll { it.'release-date' != "2019-05-18" }.isEmpty()
}

XMLオブジェクトの値のみを変更する場合、構造全体を再度解析する必要がないことに注意してください。

4.4. ノードの交換

次に、ノード全体の置き換えに移りましょう。 ここでも、GPathResultが役に立ちます。 groovy.util.slurpersupport.NodeChild#replaceNode を使用してノードを簡単に置き換えることができます。これは、 GPathResult を拡張し、Object値を引数として使用するのと同じ規則に従います。 :

def "Should replace node"() {
    given: "XML object"
    def articles = new XmlSlurper().parse(xmlFile)

    when: "Replacing node"
    articles.article[0].replaceNode {
        article(id: '5') {
            title('Traversing XML in the nutshell')
            author {
                firstname('Martin')
                lastname('Schmidt')
            }
            'release-date'('2019-05-18')
        }
    }

    articles = new XmlSlurper().parseText(XmlUtil.serialize(articles))

    then: "Node is replaced properly"
    articles.'*'.size() == 4
    articles.article[0].title == "Traversing XML in the nutshell"
}

ノードを追加する場合と同様に、XMLの構造を変更しているため、再度解析する必要があります。

4.5. ノードの削除

XmlSlurper、を使用してノードを削除するには、空の Node 定義を指定するだけで、 groovy.util.slurpersupport.NodeChild#replaceNodeメソッドを再利用できます。

def "Should remove article from xml"() {
    given: "XML object"
    def articles = new XmlSlurper().parse(xmlFile)

    when: "Removing all articles but the ones with id==3"
    articles.article
      .findAll { it.author.'@id' != "3" }
      .replaceNode {}

    articles = new XmlSlurper().parseText(XmlUtil.serialize(articles))

    then: "There is only one article left"
    articles.children().size() == 1
    articles.article[0].author.'@id' == "3"
}

この場合も、XML構造を変更するには、articlesオブジェクトを再初期化する必要があります。

5. XmlParserXmlSlurper

例で示したように、XmlParserXmlSlurperの使用法は非常に似ています。 両方で多かれ少なかれ同じ結果を達成することができます。 ただし、それらの間のいくつかの違いにより、スケールがどちらかに傾く可能性があります。

初めに、 XmlParserは、常にドキュメント全体をDOM風の構造に解析します。 そのため、読み取りと書き込みを同時に行うことができます XmlSlurper はパスをより遅延的に評価するため、同じことを行うことはできません。 その結果、XmlParserはより多くのメモリを消費する可能性があります。

一方、 XmlSlurper はより単純な定義を使用しているため、操作が簡単です。 また、 XmlSlurperを使用してXMLに加えられた構造上の変更には再初期化が必要であり、次々に多くの変更を行う場合、許容できないパフォーマンスヒットが発生する可能性があることも覚えておく必要があります。

使用するツールの決定は慎重に行う必要があり、ユースケースに完全に依存します。

6. MarkupBuilder

Groovyは、XMLツリーの読み取りと操作の他に、XMLドキュメントを最初から作成するためのツールも提供します。 groovy.xml.MarkupBuilder を使用して、最初の例の最初の2つの記事で構成されるドキュメントを作成しましょう。

def "Should create XML properly"() {
    given: "Node structures"

    when: "Using MarkupBuilderTest to create xml structure"
    def writer = new StringWriter()
    new MarkupBuilder(writer).articles {
        article {
            title('First steps in Java')
            author(id: '1') {
                firstname('Siena')
                lastname('Kerr')
            }
            'release-date'('2018-12-01')
        }
        article {
            title('Dockerize your SpringBoot application')
            author(id: '2') {
                firstname('Jonas')
                lastname('Lugo')
            }
            'release-date'('2018-12-01')
        }
    }

    then: "Xml is created properly"
    XmlUtil.serialize(writer.toString()) == XmlUtil.serialize(xmlFile.text)
}

上記の例では、 MarkupBuilder が、以前NodeBuilderおよびGPathResultで使用したNode定義とまったく同じアプローチを使用していることがわかります。 。

MarkupBuilder からの出力を予想されるXML構造と比較するために、 groovy.xml.XmlUtil#serializeメソッドを使用しました。

7. 結論

この記事では、Groovyを使用してXML構造を操作する複数の方法について説明しました。

Groovyが提供するXmlParserXmlSlurperの2つのクラスを使用して、ノードの解析、追加、編集、置換、および削除の例を確認しました。 また、それらの違いについても説明し、MarkupBuilderを使用してXMLツリーを最初から作成する方法を示しました。

いつものように、この記事で使用されている完全なコードは、GitHubから入手できます。