HaXml の Combinators を使って XML をいじる
>ちょっと遊び中。以下はちょっとしたメモ。まず HaXml を使うために、 Text.XML.HaXml をインポートする(もしくは対話環境で :m する)。ついでにドキュメントの読み込みとして、
>
>fGetElem fname = do cont <- readFile fname
let Document _ _ elems _ = xmlParse fname cont
return (CElem elems) > >とかいったモノを作ってみた。また、 > >elems <- fGetElem "memo.rss" > >などとして適当なファイルを取り込む(今回は自分の MM/memo のお気に入りのRSSを読み込んだ)。コイツをもとにいろいろ遊んでみる。 > >elems は Content型の値だ。 Content 型はいろいろ種類があるが、 fGetElem で指定した CElem というのはタグつきの、ようするに普通の XML 要素になっている。ほかにも CString というただの文字データとかいろいろある(問題にはならないだろうが Foreign.C と名前のかぶってる型があるので要注意だ)。さて、実際に読み込んだデータがどうなっているか、ということを見てみたい。 > >let CElem (Elem t a c) = elems in print t
"rdf:RDF" > >一番大元のタグの名前が rdf:RDF になっていることがわかる。 a が属性リスト、 c は子要素のリスト。 a を見てみようとすると、属性は Show でないので怒られる。 c もリストなので再帰的に見ていくことはできる。 > >であるから、 c をさらにパターンマッチして子供を手繰って行き……といった七面倒くさい操作をすれば XML の構文木をいじるのもなんとか可能だが、どう考えても面倒くさすぎる。そこで、 HaXml には Combinators というモジュールが用意されていてこれを使えば万全だ。 > >たとえば、 rdf:RDF の子供の channel の子供の title の中の文字列、というのを取り出すなら次のようになる。 > >(tag "rdf:RDF" /> tag "channel" /> tag "title" /> txt) elems > >tag という関数は、文字列を受け取り「その文字列のタグがあれば返す、さもなくば何もしない」というフィルタ関数(Content -> > >(deep (tag "title") /> txt) elems > >こうすると、 RSS の場合には RSS 全体の title のほかに item ごとの title もあるから、その title がぜんぶ出てくることになる。 > >ほかにもXMLを新しく作るという関数もあり、「特定の子供を書換える」とかいった処理も書けるが、詳しくは略。 > >さて、サンプル例題として、 RSS で列挙される URL のリストを取得してみよう。まず問題のタグまで到達しないといけない。とすればこうだ。 > >(tag "rdf:RDF" /> tag "channel" /> tag "items" /> tag "rdf:Seq" /> tag "rdf:li") elems > >もちろんほかにもいろいろ書き方はあるが、ともかくここまでは簡単だ。結果を確認したいが、 Content 型はそのままでは Show でない。そこで次のようにする。ちょっと長いので deep で楽をしておく。 > >mapM_ (print . content) $ (deep $ tag "rdf:li") elems > >content は HaXml 内の Pretty モジュールに定義されている関数で、 Content 型を Doc 型に変換する。その結果を print するわけで、実行すると、 > ><rdf:li rdf:resource="...." /> > >などというのがずらずらと表示されているはずだ。 > >でもまだXML要素のリストでしかない。本当は rdf:resource で指定されたURL が欲しいのだった。そのために LabelFilter を使う。 LabelFilter は通常のフィルタとちがってほかのデータも格納できる型で(Content -> > >(attributed "rdf:resource" (deep $ tag "rdf:li")) elems > >のように「フィルタの実行結果から rdf:resource を取り出す」と指定することになる。返り値はこの場合 > >map fst $ (attributed "rdf:resource" (deep $ tag "rdf:li")) elems > >これで「URLのリスト」が取得できた。 > >以上、 HaXml の Combinators の使い方を簡単に説明した。非常に柔軟でいろんな関数があって、アドホックに使うのにはなかなか便利という印象。ちょっとゴチャゴチャしすぎているように思えるかもしれないが、ふつうはよく使う Combinators の組合せをあらかじめ記述しておいて組み合わせるという手なのだろう。 > >ちなみにもちろん、 DTD を読み込んで、その DTD の仕様どおりの XML 操作言語を生成するという
>DtdHaskell というモジュール
>もあるので、それを使うという手もある。(訂正: DtdToHaskell だった)
>
let Document _ _ elems _ = xmlParse fname cont
return (CElem elems) > >とかいったモノを作ってみた。また、 > >elems <- fGetElem "memo.rss" > >などとして適当なファイルを取り込む(今回は自分の MM/memo のお気に入りのRSSを読み込んだ)。コイツをもとにいろいろ遊んでみる。 > >elems は Content型の値だ。 Content 型はいろいろ種類があるが、 fGetElem で指定した CElem というのはタグつきの、ようするに普通の XML 要素になっている。ほかにも CString というただの文字データとかいろいろある(問題にはならないだろうが Foreign.C と名前のかぶってる型があるので要注意だ)。さて、実際に読み込んだデータがどうなっているか、ということを見てみたい。 > >let CElem (Elem t a c) = elems in print t
"rdf:RDF" > >一番大元のタグの名前が rdf:RDF になっていることがわかる。 a が属性リスト、 c は子要素のリスト。 a を見てみようとすると、属性は Show でないので怒られる。 c もリストなので再帰的に見ていくことはできる。 > >であるから、 c をさらにパターンマッチして子供を手繰って行き……といった七面倒くさい操作をすれば XML の構文木をいじるのもなんとか可能だが、どう考えても面倒くさすぎる。そこで、 HaXml には Combinators というモジュールが用意されていてこれを使えば万全だ。 > >たとえば、 rdf:RDF の子供の channel の子供の title の中の文字列、というのを取り出すなら次のようになる。 > >(tag "rdf:RDF" /> tag "channel" /> tag "title" /> txt) elems > >tag という関数は、文字列を受け取り「その文字列のタグがあれば返す、さもなくば何もしない」というフィルタ関数(Content -> > >(deep (tag "title") /> txt) elems > >こうすると、 RSS の場合には RSS 全体の title のほかに item ごとの title もあるから、その title がぜんぶ出てくることになる。 > >ほかにもXMLを新しく作るという関数もあり、「特定の子供を書換える」とかいった処理も書けるが、詳しくは略。 > >さて、サンプル例題として、 RSS で列挙される URL のリストを取得してみよう。まず問題のタグまで到達しないといけない。とすればこうだ。 > >(tag "rdf:RDF" /> tag "channel" /> tag "items" /> tag "rdf:Seq" /> tag "rdf:li") elems > >もちろんほかにもいろいろ書き方はあるが、ともかくここまでは簡単だ。結果を確認したいが、 Content 型はそのままでは Show でない。そこで次のようにする。ちょっと長いので deep で楽をしておく。 > >mapM_ (print . content) $ (deep $ tag "rdf:li") elems > >content は HaXml 内の Pretty モジュールに定義されている関数で、 Content 型を Doc 型に変換する。その結果を print するわけで、実行すると、 > ><rdf:li rdf:resource="...." /> > >などというのがずらずらと表示されているはずだ。 > >でもまだXML要素のリストでしかない。本当は rdf:resource で指定されたURL が欲しいのだった。そのために LabelFilter を使う。 LabelFilter は通常のフィルタとちがってほかのデータも格納できる型で(Content -> > >(attributed "rdf:resource" (deep $ tag "rdf:li")) elems > >のように「フィルタの実行結果から rdf:resource を取り出す」と指定することになる。返り値はこの場合 > >map fst $ (attributed "rdf:resource" (deep $ tag "rdf:li")) elems > >これで「URLのリスト」が取得できた。 > >以上、 HaXml の Combinators の使い方を簡単に説明した。非常に柔軟でいろんな関数があって、アドホックに使うのにはなかなか便利という印象。ちょっとゴチャゴチャしすぎているように思えるかもしれないが、ふつうはよく使う Combinators の組合せをあらかじめ記述しておいて組み合わせるという手なのだろう。 > >ちなみにもちろん、 DTD を読み込んで、その DTD の仕様どおりの XML 操作言語を生成するという