Clojureでxmlを読みこむ方法
clojureでxmlを処理する方法のメモ。随時更新予定。
基本
下記のxmlをtest.xmlとしてプロジェクト直下に保存。
<parent>
<child>
hello
</child>
</parent>
clojure.xml/parse関数を使うとmapデータに変換できる。
user> (require '[clojure.xml :as xml])
user> (xml/parse (slurp "test.xml"))
{:tag :parent, :attrs nil, :content [{:tag :child, :attrs nil, :content ["\n\t\thello\n\t"]}]}
ここでは、slurp関数を使ってXMLファイルを直接読んで処理したが、 slurp以外の関数で読み込んだxmlを扱いたいときなどは、xmlは既に文字列になっていることが多い。 clojure.xml/parse関数は文字列を直接パースできないので下記のようなラッパー関数を用意する。
(defn xml-parse [s]
(xml/parse (java.io.ByteArrayInputStream. (.getBytes s))))
これで文字列としてのxmlもパースできるようになった。
user> (def xml-doc "<parent> <child> hello </child> </parent>")
#'user/xml-doc
user> (xml-parse xml-doc)
{:tag :parent, :attrs nil, :content [{:tag :child, :attrs nil, :content [" hello "]}]}
xmlのデータが画面に表示されると、場合によってはスクロールが重くなるので、 そんな時はC-c M-o でバッファクリアすると良い感じ。
xml-seq
xmlをmapデータにしただけだと、各要素へのアクセスが大変なので
xml-seq
を使用して、各要素をトラバースした状態のシーケンスを用意することができる。
この関数を使用すると、それぞれのタグが先頭になった状態シーケンスが得られる。
user> (xml-seq (xml-parse xml-doc))
({:tag :parent, ;; 最初のparentタグが先頭
:attrs nil,
:content [{:tag :child, :attrs nil, :content [" hello "]}]}
{:tag :child, ;; 子供のchidleタグが先頭
:attrs nil,
:content [" hello "]}
" hello " ;; 一番最後の要素
)
こうすることで、ツリー構造なXMLデータがフラットなシーケンスデータになる。
つまり for
などの従来のシーケンス関数がそのまま使用できるようになる。
<parent>
<child name="taro">hello</child>
<child name="hanako">wao</child>
</parent>
user> (def xml-doc2 " <parent> <child name=\"taro\">hello</child> <child name=\"hanako\">hi</child> </parent>")
#'user/xml-doc2
user> (for [x (xml-seq (xml-parse xml-doc2)) :when (= :child (:tag x))] (:name (:attrs x)))
("taro" "hanako")
xml-zip
xml-seq
でxmlデータをシーケンスにできるのは良いが、正直使いづらい。
そこでもうちょっと直感的に扱える clojure.zip/xml-zip
関数を使用してみる。
user> (require '[clojure.zip :as zip])
user> (zip/xml-zip (xml-seq (xml-parse xml-doc)))
[({:tag :parent,
:attrs nil,
:content [
{:tag :child,
:attrs nil,
:content [
" hello "]}]}
{:tag :child,
:attrs nil,
:content [
" hello "]}
" hello ") nil]
zipperとは、ツリー構造を扱うためのライブラリっぽい。
XPATH
clj-xpath
というライブラリを使用すれば、xpathを使うことができる。
しかし、XPATHは正規表現のようなミニ言語であり、それなりに学習コストがかかる。
また、要素を指定する部分が文字列になるため、実行時エラーの可能性と、
コンパイルで時間がかかる可能性などがあるため、現時点では使用しない。