木の変更

上にも書いた、木の更新のしかたが分からなくて驚いたという話。なんか基本的なことのような気がするんだけど……どうやればいいんだろう。

sxml で表現された HTML テンプレートをいじろうと思ったのですが、複雑な木の一部を変更する方法が分からなくて、結局関数にしてしまった。こんな感じ:

(define (template content)
  `(complex
     (@ (SXML TREE)) (DAYO)
     (hoge
       (@ (FUGA foo))
       ,@content)))

これでは一般的じゃないので、例えば外部からテンプレートを読みこんで使おうなんていうときに困る。

(define (eval-template template params)
  ...)

こういう関数があって、

(define template-a
  '(complex
     (@ (SXML TREE)) (DAYO)
     (hoge
       (@ (%attrs))
       (%content))))

(define params
  '((attrs (class "hoge"))
    (content (p "paragraph dayo")
             (div "div dayo"))))

こういうデータを

(srl:sxml->xml (eval-template template-a params))

なんていうふうにできると嬉しいと思う。

tree の更新なんて fold がハマるパターンだよねーと思って、書いた。

(define (templatesymbol? symbol)
  (and (symbol? symbol)
       (eq? (string-ref (symbol->string symbol) 0) #\%)))

(define (eval-template template params)
  (fold-right (lambda (x list)
                (if (not (pair? x))
                  (cons x list)
                  (or (and-let* (((templatesymbol? (car x)))
                                 (content (assoc-ref params (car x))))
                        (append content list))
                      (cons (eval-template x params) list))))
              ()
              template))
(define template
  '(*TOP* (html
           (@ (xmlns "http://www.w3.org/1999/xhtml") (lang "ja"))
           (head (title "html template test"))
           (body
            (h1 "tempalate test")
            (%content)))))

(pretty-print
  (eval-template
    template
    '((%content
        (p (@ (class "content")) "hogefugafoobar")
        (p (@ (class "content")) "hogefugafoobar")))))
;=>
(*TOP* (html (@ (xmlns "http://www.w3.org/1999/xhtml")
                (lang "ja"))
             (head (title "html template test"))
             (body (h1 "tempalate test")
                   (p (@ (class "content")) "hogefugafoobar")
                   (p (@ (class "content")) "hogefugafoobar"))))

実行効率とかはどうなんだろう。コンシングが多いんじゃないか?と思ったけど、テンプレートをメモリ上に保持するならコピーは避けられないわけで、問題無い。