--- title: SXML から 任意の要素とかタグの値を取得する関数 author: kazu634 date: 2010-04-26 url: /2010/04/26/_1519/ wordtwit_post_info: - 'O:8:"stdClass":13:{s:6:"manual";b:0;s:11:"tweet_times";i:1;s:5:"delay";i:0;s:7:"enabled";i:1;s:10:"separation";s:2:"60";s:7:"version";s:3:"3.7";s:14:"tweet_template";b:0;s:6:"status";i:2;s:6:"result";a:0:{}s:13:"tweet_counter";i:2;s:13:"tweet_log_ids";a:1:{i:0;i:5241;}s:9:"hash_tags";a:0:{}s:8:"accounts";a:1:{i:0;s:7:"kazu634";}}' categories: - gauche - Lisp ---

Tumblr の Web サービスから XML を取得して、ごにょごにょする準備のため、作成してみました。

読み込む SXML ファイル

読み込むSXMLはこんな感じです:

(post
(|@|
(url-with-slug "http://kazu634.tumblr.com/post/547214365")
(url "http://kazu634.tumblr.com/post/547214365")
(unix-timestamp "1272170551")
(type "quote")
(slug "")
(reblog-key "Yp9UyNJ1")
(id "547214365")
(format "html")
(date-gmt "2010-04-25 04:42:31 GMT")
(date "Sun, 25 Apr 2010 13:42:31"))
(quote-text "情報が便利になればなるほどに、お金はそこから逃げていく。裏を返せば、情報という物に、何かの不自由を付加したものがメディアであって、不自由を付加されて、初めてその情報は、お金に紐付けられるのだと思う。")
(quote-source "<a href=\"http://medt00lz.s59.xrea.com/wp/archives/787\" target=\"_blank\">不便が強みになる - レジデント初期研修用資料</a>"))

作成した関数

作成したのはこんな関数:

(define (get-value-from-sxml key lis)
(cond
[(null? lis) #f]
[(atom? (car lis))
(if (equal? (car lis) key)
(cadr lis)
(get-value-from-sxml key (cdr lis)))]
[else
(or
(get-value-from-sxml key (car lis))
(get-value-from-sxml key (cdr lis)))]))

2010/04/27 追記

Shiroさんからコメント頂きました:

ドキュメントが少なくてアレなんですが、SXMLから要素を抜き出すのはsxpathが便利ですよ。

上の(post (@ …)) なるsxmlが変数*sxml*に入っているとして、

type要素の値の抜きだし:

*1 *sxml*) => (“quote”)

quote-text要素だと:

((sxpath ‘(// quote-text *text*) *sxml*) => (“情報が便利になればなるほどに、 …

戻り値がリストなのは複数マッチする可能性があるからですが、マッチした最初の

値が欲しければif-car-sxpathを使います (マッチしなければ#fが返る)

*2 *sxml*) => “1272170551”

実は Tumblr から取得した XML を SXML に変換する過程ですでに sxpath を使っていました。こんな感じです。

(define (test-sxpath sxpath)
(let*
((xml
(open-input-file
"/Users/kazu634/Documents/working/tmp_lisp/tumblr/tumblr.xml"))
(sxml (ssax:xml->sxml xml '())))
((sxpath sxpath sxml))) ;; この部分に冒頭の関数を使うことを考えてた

でもこれだと sxpath の部分に「'(// unix-timestamp)」などと指定しなければならずあんまり嬉しくなくて、自分でリストをごりごりやった方がいいのではないかと思って冒頭の関数を書きました。でも、 Shiro さんのコメントを見ていて、こんな風に書き換えればいいのではないかと思いつきました:

(define (test-sxpath element-name)
(let*
((xml
(open-input-file
"/Users/kazu634/Documents/working/tmp_lisp/tumblr/tumblr.xml"))
(sxml (ssax:xml->sxml xml '())))
((sxpath `(// ,element-name)) sxml)))

これなら任意の要素の値とか属性の値を直感的にピンポイントで取得できます!

実行例はこんな感じになりました:

gosh> (car (test-sxpath 'post)) ;; tumblr.xmlには複数の post要素があるのでとりあえず1つめを指定
(post
(|@|
(url-with-slug "http://kazu634.tumblr.com/post/547214365")
(url "http://kazu634.tumblr.com/post/547214365")
(unix-timestamp "1272170551")
(type "quote")
(slug "")
(reblog-key "Yp9UyNJ1")
(id "547214365")
(format "html")
(date-gmt "2010-04-25 04:42:31 GMT")
(date "Sun, 25 Apr 2010 13:42:31"))
(quote-text "情報が便利になればなるほどに、お金はそこから逃げていく。裏を返せば、情報という物に、何かの不自由を付加したものがメディアであって、不自由を付加されて、初めてその情報は、お金に紐付けられるのだと思う。")
(quote-source "<a href=\"http://medt00lz.s59.xrea.com/wp/archives/787\" target=\"_blank\">不便が強みになる - レジデント初期研修用資料</a>"))

sxpathを使って最初にヒットしたものを取得する方法も考えていたのですが、 if-car-sxpath だったんですね。同じように関数をつくってみました:

gosh> (define (get-value-from-sxml2 sxpath sxml)
((if-car-sxpath `(// ,sxpath *text*)) sxml))
gosh> (get-value-from-sxml 'unix-timestamp (car (test-sxpath 'post)))
"1272170551"

だいぶ見通しが良くなりました!ありがとうございます!

*1:sxpath '(// type *text*

*2:if-car-sxpath '(// unix-timestamp *text*