XPath を使う


Tags: R6RS, SXML, SXPath, htmlprag

SXML ライブラリの sxpath 手続きに XPath 文字列を渡すと、 SXML ツリーから与えられた XPath 文字列を満足するノードのリストを返す手続きを得ることができる。

(import (rnrs) (sxml sxpath))

(define tbl '(*TOP*
              (table (tr (td "1") (td "2") (td "3"))
                     (tr (td "4") (td "5") (td "6"))
                     (tr (td "7") (td "8") (td "9")))))

(sxpath "*") ; => #<procedure>
((sxpath "//td") tbl)
; => ((td "1") (td "2") (td "3") (td "4") (td "5") (td "6") (td "7") (td "8") (td "9"))
((sxpath "/table/tr/td[1]") tbl)
; => ((td "1"))
((sxpath "//tr[td = '9']") tbl)
; => ((tr (td "7") (td "8") (td "9")))

応用

たとえば、 http://www.r6rs.org/final/html/r6rs-lib/r6rs-lib-Z-H-21.html をもとに見出し文字列から URL への対応表を作りたいとする。 HTML ソースを大雑把に見渡すと

ことがわかる。

これらの知識をもとに見出しと URL の対応関係を表す連想リストをつくる手続きを考える。

(import (rnrs)
        (only (srfi :1) concatenate filter-map)
        (srfi :2)
        (only (srfi :13) string-every string-trim-both)
        (htmlprag)
        (sxml sxpath)
        (sxml tools))

(define (r6rs-lib-index->alist sxml)
  (fold-left
   (lambda (alis x)
     (cond ((string? x)
            (cons `(,(string-trim-both x)) alis))
           ((eq? (sxml:node-name x) 'tt)
            (cons `(,(car (sxml:content x))) alis))
           ((eq? (sxml:node-name x) 'a)
            (cons `(,(caar alis)
                    ,(sxml:attr x 'href)
                    ,@(cdar alis))
                  (cdr alis)))
           (else (assert #f))))         ; should not be reached
   '()
   (concatenate
    (filter-map
     (lambda (p)
       (and-let* ((c (remp (lambda (k)
                             (and (string? k)
                                  (string-every (lambda (c)
                                                  (or (char=? c #\,)
                                                      (char-whitespace? c)))
                                                k)))
                           (sxml:content p)))
                  ((not (null? c)))
                  (t (car c))
                  ((or (string? t)
                       (eq? (sxml:node-name t) 'tt))))
         c))
     ((sxpath "//p") sxml)))))

(r6rs-lib-index->alist
 (call-with-input-file "r6rs-lib-Z-H-21.html" html->sxml))
; => (("write-char"
;      "r6rs-lib-Z-H-9.html#node_idx_822"
;      "r6rs-lib-Z-H-9.html#node_idx_820")
;     ...)