2009年2月3日火曜日

XMLを扱う (その16) [Common Lisp]


XMLは難しい。。。それ以上に文字コードは難しい。文字コードそのもの、
というよりも、EmacsなどのUIで文字を取り扱っているときは、すでにた
くさんのプログラムがそれぞれに文字コードを取り扱った集積としてのこ
となので、本当に理解するにはそれぞれがどのように文字コードを扱って
いるのかをちゃんと知っていなければならない。今の私は、Emacsがどの
ように文字コードを扱っているのか、そしてTerminalがどうなのかがわか
らない。なのでとてもちゃんと理解できるとは思えない。

まあ、それは将来の課題としよう。

cxmlを使い「始める」のに十分な程度には理解できたと思う。(使いこな
す、ではない)

さて、自分なりの現時点でのcxmlのまとめを。はじめのターゲットは、
SAX、Klacks、DOM、XMLS。

XML形式と別の形式を相互変換するには、SAXまたはKlacksを用いる。

XML形式から別の何かへ。

XMLファイルを準備する。

--- example-100.xml ---
<!DOCTYPE test [
<!ELEMENT test (foo,bar*)>
<!ATTLIST test a CDATA #IMPLIED>
<!ELEMENT foo #PCDATA>
<!ELEMENT bar (foo?)>
<!ATTLIST bar xml:space (default|preserve) "default">
]>
<test a='b'>
<foo> </foo>
<bar> </bar>
<bar xml:space="preserve"> </bar>
</test>
---

まず、SAX。

SAXの中心的な概念は、eventsとhandlersだ。SAX parserはXML文書を読み
進めるにしたがってeventsを上げる。それを処理するhandlersがいろいろ
用意されているし、自分でも書ける。

---
;; parsing a file
(defparameter *example*
(cxml:parse-file "example-100.xml" (cxml-dom:make-dom-builder)))
---

この例ではcxml-dom:make-dom-builderがhanlerを生成している。その
handlerはDOMを生成するようなものだ。

xmlsを生成するには次のようにする。

---
;; parsing a file to xmls
(cxml:parse-file "example-100.xml" (cxml-xmls:make-xmls-builder))
---

さて、この結果は、

---
("test" (("a" "b")) "
" ("child" NIL) "
")
---

となる。多くの場合、ここでの改行はXMLファイルの可読性のためであり、
データとしての意味はないだろう。こういう場合は、

---
(cxml:parse-file "example-100.xml"
(cxml:make-whitespace-normalizer (cxml-xmls:make-xmls-builder))
:validate t)
---

というようにhandlerを段積みにして回避できたりする。

ここで大事なのはhandlerの基本形は、eventを受け取るものでもあり、
eventを投げるものでもあるということ。なので、うまく設計すれば、こ
こでのnormalizerのような汎用的なhandler部品ができる。

さて、eventを受け取るだけで、決してeventを投げないhandlerをsinkと
呼ぶ。(以前の私の理解は間違いであった)

なので、ここでのdom-builderやxmls-builderはsinkである。
sinkは大抵の場合、XMLとは別の表現を出力する。

続いてKlacks。

Klacksの中心的な概念は、pull-based (peek/consume)だ。
XML形式を読み込んで何か処理をする場合は、push-basedなSAXよりも
Klacksの方が書きやすい。peek、peek-next、find-element, skipなど、
bufferをもっているKlacksならではの制御機能が用意されている。

---
(klacks:with-open-source ; on REPL
(s (cxml:make-source #p"example-100.xml"))
(loop
for key = (klacks:peek s)
while key
do
(case key
(:start-element
(format t "~A {" (klacks:current-qname s)))
(:end-element
(format t "}")))
(klacks:consume s)))
---

なので、XMLの文書の一部の情報のみ利用して出力を構成するときなど、
Klacksがいいかもしれない。まるまる変換する場合は、SAXまたはいった
んDOMまたはXMLSにしてそれらをCLで変換というのがよいかもしれない。


続いて別の何かからXMLへ。すなわちSerialization。

これもSAXでもKlacksでも実現できる。SAXで実現できるというのは奇異で
ある。本来のJavaのSAXはSerializationには使えない。cxmlのSAXは、DOM
やxmlsをeventの源泉にすることができるのでSerializationに使える。

---
(with-open-file (out "example-100.out" :direction :output :element-type '(unsigned-byte 8) :if-exists :supersede)
(dom:map-document (cxml:make-octet-stream-sink out) *example*))
---

---
(with-open-file (out "example-100-xmls.out" :direction :output :element-type '(unsigned-byte 8) :if-exists :supersede)
(cxml-xmls:map-node (cxml:make-octet-stream-sink out)
'("test" (("a" "b")) ("child" nil))
:include-namespace-uri nil))
---

ここで、map-documentとmap-nodeがSAX eventを生成している。
make-octet-stream-sinkがそのeventを受け取るhandlerである。
sinkの処理としてstreamにXML形式を出力する。


さて、続いてin memoryな表現とその取り扱い。

まず、DOM。DOM自体は先程SAXとdom-builderを使って作成した。
DOMのインターフェイスはCORBA IDLで定義されており、cxmlでは、それを
独自なマッピングでCLにもってきている。ドキュメントはなさそう。
ただ、DOMのインターフェイスの名称はdescriptiveなので、ソースを見れ
ばすぐにわかる。

---
(dom:parent-node *example*)
(dom:child-nodes *example*)
(dom:first-child *example*)
(dom:node-name (dom:first-child *example*))
---

などなど。

次にXMLS。XMLS自体は先程SAXとxmls-builderで生成方法を確認した。
リスト、なので操作についての説明は割愛する。いくつかユーティリティ
がある。例えば、XMLSノードを作る便利関数などがある、が別にいらんよ
うな、、、リストだし。

---
(cxml-xmls:make-node :name "hoge" :ns "http://piyo" :attrs '(("att1" . "value1") ("att2" . "value2")))
---

とりあえず、今はこれだけ。

こつこつ。

0 件のコメント: