アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

macroexpandでHTMLを書く

こんにちは、chibaです。
今回もネタがないのでEmacsネタです。
Dylanのマクロについての文献:
http://people.csail.mit.edu/jrb/Projects/dexprs.pdf を読んでいて、XMLや、LISPは、Skeleton Syntax Tree Representationsに分類されるというのを読んで、そうか、HTMLも簡単にマクロ書けたりするのかもと思ったのでEmacs Lispでちょっと試してみました。

(defun split-id-or-class (string)
  (let ((elts (split-string string "[\\.#]")))
    (if (cdr elts)
        elts
        string)))

(defmacro defhtmltag (tag)
  (let* ((tag-str (symbol-name tag))
         (tags (split-string tag-str "[\\.#]"))
         (tag-name (car tags))
         (attr (cadr tags)))
    `(defmacro ,tag (&rest body)
       `(list ,(concat
                "<"
                ,tag-name
                (apply #'concat
                       (cond ((find ?. ,tag-str)
                              (list " class=\"" ,attr "\""))
                             ((find ?# ,tag-str)
                              (list " id=\"" ,attr "\""))
                             ('T (list ""))))
                ">")
              ,@body
              ,(concat "</" ,tag-name ">")))))

(defun flatten (lis)
  (cond ((atom lis) lis)
        ((listp (car lis))
         (append (flatten (car lis)) (flatten (cdr lis))))
        (t (append (list (car lis)) (flatten (cdr lis))))))

(defmacro with-html-output-to-string (&rest body)
  `(reduce (lambda (x y) (format "%s%s" x y))
           (flatten (list ,@body))))

使い方としては、

(defhtmltag li.foo)

のようにすると、

(list "<li>" "こんにちは" "</li>")

のようなものにマクロが展開されるので、リストの入れ子を平坦にして一つの文字列に繋げれば完成です。
ちょっと込み入ったところだと

(defhtmltag table)
(defhtmltag tbody)
(defhtmltag td)
(defhtmltag tr)

(defun list-to-table (list)
  (table
   (tbody
    (mapcar (lambda (x)
              (tr
               (mapcar (lambda (y) (td y))
                       x)))
            list))))

(insert
 (with-html-output-to-string
  (list-to-table '((foo bar baz quux)
                   (1 2 3 4)
                   (z z z z)))))
;=> <li class="foo"><li class="foo"><li class="foo"><li class="foo">foo</li><li class="foo">bar</li><li class="foo">baz</li><li class="foo">quux</li></li><li class="foo"><li class="foo">1</li><li class="foo">2</li><li class="foo">3</li><li class="foo">4</li></li><li class="foo"><li class="foo">z</li><li class="foo">z</li><li class="foo">z</li><li class="foo">z</li></li></li></li>

ということもできます。
ちなみに、マクロ展開でHTML書くって新しいかも!と思いましたが、別にマクロにしなくても関数でも書けることに今気付きました…
とりあえず、もし、HTMLにマクロがあったとしたらLISPと似た感じになるのかなあという印象は持ちました。