Groovy, Scala, Ruby, PHP による XML の加工 - 要素や属性の追加・更新・削除
今回は、各種スクリプト言語による XML の加工方法をまとめてみました。
Groovy, Scala, Ruby, PHP を使って以下のような XML を
<root> <data id="1">sample1</data> <data id="2" ext="none"> <details /> <details /> </data> </root>
次のような加工を施して
- (1) id="3" の data 要素を追加
- (2) id="2" の data 要素に type 属性を追加
- (3) id="1" の data 要素を削除
- (4) 一番目の details 要素を text 要素に置換
- (5) 二番目の details 要素の内容を変更
- (6) id="2" の data 要素の ext 属性の内容を変更
以下のように変更してみます。
<root> <!-- (3) data 要素削除 --> <!-- (2) type 属性追加、(6) ext 属性の内容変更 --> <data id="2" ext="updated" type="node"> <!-- (4) text 要素に置換 --> <text>update test</text> <!-- (5) 要素の内容を変更 --> <details>after</details> </data> <!-- (1) 要素の追加 --> <data id="3"> <details>added</details> </data> </root>
Groovy の場合
XmlSlurper を使いました。find や配列によるアクセスは parse 時の構成に基づいて実施する必要があり、変更を加えた箇所には適用できないようなので注意。
import groovy.xml.StreamingMarkupBuilder def doc = new XmlSlurper().parse(new File(args[0])) // (1) id="3" の data 要素を追加(details 子要素含む) doc.appendNode { data(id: "3") { details("added") } } // (2) type 属性を追加 doc.data.find {it.@id == "2"}.@type = "node" //以下でも可 //doc.data[1].@type = "node" //以下は不可。appendNode した要素は find や配列でアクセスできない模様 //doc.data.find {it.@id == "3"}.@type = "node1" //doc.data[2].@type = "node2" // (3) id="1" の data 要素を削除 doc.data.find {it.@id == "1"}.replaceNode {} // (4) details 要素を text 要素に置換 doc.data.find {it.@id == "2"}.children()[0].replaceNode { text("update test") } //以下でも可。id="1" を削除する前の構成でアクセスする必要あり /* doc.data[1].details[0].replaceNode { text("update test") } */ // (5) details 要素の内容を変更 doc.data.find {it.@id == "2"}.details[1] = "after" // (6) ext 属性の内容を変更 doc.data.find {it.@id == "2"}.@ext = "updated" //文字列化して出力 println new StreamingMarkupBuilder().bind{ mkp.yield doc }
Scala の場合
RuleTransformer と RewriteRule を使いました。
Scala の場合、他のものに比べると処理内容がかなり変わっています。
要素や属性の値を変更するというよりは、変更点を反映した新しい要素を作っていくようなイメージです。
ただし、普段 XML の更新に Scala を使っていない事もあり、知らないだけでもっと良い方法があるかもしれません。特に details 要素の処理の部分はイマイチかもしれません。
ちなみに、スクリプトは Scala 2.8.0 用です。
import scala.xml._ import scala.xml.transform.{RewriteRule, RuleTransformer} val rule = new RewriteRule { override def transform(n: Node): NodeSeq = n match { // (1) 要素を追加 case <root>{ ch @ _* }</root> => <root>{ ch }<data id="3"><details>added</details></data></root> // (3) 要素を削除 case e: Elem if (e \ "@id").text == "1" => NodeSeq.Empty case e: Elem if (e \ "@id").text == "2" => // (2) 属性の追加と (6) 変更 val ne = e % Attribute("", "type", "node", Null) % Attribute("", "ext", "updated", Null) //以下でも可 // val ne = e % new UnprefixedAttribute("type", "node", Null) % new UnprefixedAttribute("ext", "updated", Null) //子要素(details)を処理 val ch: NodeSeq = ne.child.zipWithIndex.map {l => l._2 match { // (4) 要素を置換 case 0 => <text>update test</text> // (5) 要素の値を変更 case 1 => Elem(l._1.prefix, l._1.label, l._1.attributes, l._1.scope, Text("after")) case _ => l._1 } } //子要素だけを変更した data 要素のコピーを作成 ne.copy(child = ch) case n => n } } val doc = XML.loadFile(args(0)) val newDoc = new RuleTransformer(rule).transform(Utility.trim(doc)) println(newDoc.mkString)
Ruby の場合
REXML を使いました。
elements[index] を使う場合、index は 1 からになるようなので注意。
require "rexml/document" xml = REXML::Document.new File.new(ARGV[0]) doc = xml.root # (1) data 要素を追加 doc.add_element("data", {"id" => "3"}).add_element("details").add_text("added") n = doc.get_elements("//data[@id='2']")[0] # (2) type 属性を追加 n.add_attribute("type", "node") # (3) 要素を削除 doc.elements["//data[@id='1']"].remove #以下でも可 #doc.get_elements("//data[@id='1']")[0].remove #doc.elements["data"].remove #doc.delete_element("data") # (4) 要素を置換、elements[index] を使用する場合 index は 1 からになる n.replace_child(n.elements[1], REXML::Element.new("text").add_text("update test")) # (5) 要素の値を変更 n.elements[2].text = "after" # (6) ext 属性の値を変更 n.attributes["ext"] = "updated" puts doc
PHP の場合
SimpleXML を使いました。
SimpleXML で要素を置換する方法が無さそうだったので、DOMDocument の API を使って、要素を置換しています。dom_import_simplexml や simplexml_import_dom で DOM と SimpleXML との相互変換を行っています。
<?php $doc = simplexml_load_file($argv[1]); // (1) 要素を追加 $d = $doc->addChild("data"); $d["id"] = "3"; $d->addChild("details", "added"); // (2) type 属性を追加 $doc->data[1]["type"] = "node"; // (3) 要素を削除 unset($doc->data[0]); //SimpleXML を DOM に変換 $dom = dom_import_simplexml($doc); //1番目の details 要素取得 $target = $dom->getElementsByTagName("details")->item(0); // (4) 要素を置換(DOMDocument の API を使用) $target->parentNode->replaceChild(new DOMElement("text", "update test"), $target); //DOM を SimpleXML に変換 $doc = simplexml_import_dom($dom); // (5) 要素の値を変更 $doc->data[0]->details[0] = "after"; // (6) ext 属性の値を変更 $doc->data[0]["ext"] = "updated"; echo $doc->asXML();