GroovyでXMLを操作する

1. 前書き

link:/groovy-language[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構造の属性と値にアクセスできます。 *
link:/groovy-spock[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 __operationの結果の内部構造に少し焦点を当てましょう。
_articles_オブジェクトは、__groovy.util.Nodeのインスタンスです。 __Every _Node_は、名前、属性マップ、値、および親(_null_または別の_Node)で構成されます。
この場合、_articles_の値は__groovy.util.NodeList __instanceであり、これは__Node__sのコレクションのラッパークラスです。 _NodeList_は、_java.util.ArrayList_クラスを拡張し、インデックスによる要素の抽出を提供します。 __Nodeの文字列値を取得するには、___ groovy.util.Node#text()._を使用します
上記の例では、いくつかのGPath式を導入しました。
  • articles.article [0] .author.firstname —著者の名を取得します
    最初の記事– _articles.article [n] _はnthの記事に直接アクセスします

  • _ ’*’ _ — article ’の子のリストを取得します–これは以下と同等です
    groovy.util.Node#children()

  • author.’@id’ — author_author_要素の_id_属性を取得します–
    _author.’@attributeName’_は名前で属性値にアクセスします(同等のものは_author [‘@ id’] _および_link:/ cdn-cgi / l / email-protection [[email protected]] _)。

* 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()
}
上記の例では、_NodeList_を走査するためにlink:/groovy-collections-find-elements[Groovy Collections API]も使用しました。

* 3.4。 ノードの交換*

次に、値の1つを変更するだけでなく、ノード全体を置き換える方法を見てみましょう。
新しい要素を追加するのと同様に、_Node_定義に_NodeBuilder_を使用し、_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_メソッドを呼び出す必要があります
著者が_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"
}
ご覧のとおり、remove操作の結果として、記事が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_のインターフェイスと同じです。 *ただし、出力構造は_groovy.util.slurpersupport.GPathResult_ *を使用します。これは_Node_のラッパークラスです。 _GPathResult_は、次のようなメソッドの簡略化された定義を提供します。_equals()_および_toString()_をラップすることにより

* 4.2。 *ノードの追加

_Node_の追加も、_XmlParser_の使用と非常に似ています。 ただし、この場合、_groovy.util.slurpersupport.GPathResult#appendNode_は、__ java.lang.Object ___のインスタンスを引数として取るメソッドを提供します。 その結果、_NodeBuilder_によって導入されたのと同じ規則に従って、新しい_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_ methods __.__ *の組み合わせを使用してこれを実現できます*

* 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 ___comesが助けになります。 _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. XmlParser vs XmlSlurper *

例で示したように、__ XmlParser __と_XmlSlurper_の使用法はかなり似ています。 両方でほぼ同じ結果を達成できます。 ただし、それらの間のいくつかの違いは、スケールをどちらか一方に傾けることができます。
まず、* _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が提供する2つのクラス_XmlParser_と_XmlSlurper_を使用して、ノードの解析、追加、編集、置換、削除の例を見てきました。 また、それらの違いについても説明し、_MarkupBuilder_を使用してXMLツリーをゼロから構築する方法を示しました。
いつものように、この記事で使用される完全なコードはhttps://github.com/eugenp/tutorials/tree/master/core-groovy-2[GitHubで]から入手できます。