htmlを書く。

htmlはS式と同じ構造をしており、構造上の相互変換は容易に行え、さまざまなスクリプトが公開されている。
しかし、htmlのS式での表現方法はいくつかバリエーションが考えられるので、それらのスクリプトから希望する表現を探すより、自分で書いた方が早い。


今回行いたいのは、lispで表現した、様々な型の混在したデータリストをhtmlのベタテキストへの変換である。

(setq data
 '(body ; car はhtmlタグ
  "hoge" ; cdr は内包するデータリスト
  (p ; 内側にもhtmlデータが入る
   "fuga")
  (/ br) ; 空要素は/で始まる
  ((div (class fizz)) ; 属性を含む場合はhtmlタグと属性をリストにする
   ) ; データが空の場合もある。
  (/ "img" ("src" "foo.jpg") ; タグや属性も文字列で指定できる
           (width 200) ; 属性は複数ありうる。 数値も入る。
     )
  100 ; 平文にも数値を使える。
  (1 2 3) ; ただのリストのようだが、carがhtmlタグとして扱われる
  (t 4 5 6) ; tから始まるリストのcdrをただのリストとして扱う。
  (t (p) (/ br)) ; ただのリストの中身は展開しない。
 ))

次のように変換したい。改行は微妙にスペース扱いになったりして表示に影響したりするが、最終出力では(文字列で明示されないものは)全て省略する。下は見やすいように適当に改行してある。

<body>
hoge
<p>fuga</p>
<br />
<div class="fizz"></div>
<img src="foo.jpg" width="200" />
100
<1>23</1>
456
p/br
</body>

ここで用いるhtmlフォーマットをBNF記法で表すと次のようになる。

htmlフォーマット := 要素 | 空要素 | 文字列 | シンボル | 数値 | リスト提示
要素 := ヘッダ (htmlフォーマット)
空要素 := / タグ [属性リスト]
ヘッダ := タグ [属性リスト]
属性リスト := 属性名 (属性値)
属性名 := 文字列 | シンボル
属性値 := (文字列 | シンボル | 数値)
リスト提示 := t リスト

このようなデータを変換するための関数は以下に書いたとおりである。ただし、一部仕様外のデータを許容する。この関数はすでに定義済みのGraham's #'flattenおよびdeformを用いている。

(deform html (_ let @ labels)
  ((@ #'@prefunc)
   (_< "<")(_> ">")(_s " ")(_br nil)(_v "")
   (_=q "=\"")(_q "\"")(_</ "</")(_/> " />")
   (@prefunc (arg &optional br)
     (setq _br (if br "\n" _v))
     (list t (@main arg)))
   (@attribute+ (alists &optional res)
     (if (car alists)
         (@attribute+ (cdr alists)
                      (append res (@attribute (car alists))))
       (if res res _v)))
   (@attribute (alist)
     (if (atom alist)
         (list _s (@name alist))
       (list _s (@name (car alist))
             _=q (@name (cdr alist)) _q)))
   (@name (x)
     (cond ((null x) _v)
           ((symbolp x) (symbol-name x))
           ((consp x) (list (@name (car x))
                            (if (cadr x) (list _s (@name (cdr x))) _v)))
           (t x)))
   (@main (arg)
     (cond ((atom arg) (list arg))
           ((eq (car arg) '/) (@empty (cdr arg)))
           ((eq (car arg) t) (cdr arg))
           (t (@element arg))))
   (@empty (arg)
     (list _< (@name (car arg)) (@attribute+ (cdr arg)) _/>))
   (@element (arg)
     (list (@head (car arg))
           _br (if (cdr arg) (list (@body (cdr arg)) _br) _v)
           (@tail (car arg))))
   (@body (arg &optional res)
     (if (cdr arg)
         (@body (cdr arg) (append res (@main (car arg)) (list _br)))
       (append res (@main (car arg)))))
   (@head (arg)
     (if (atom arg)
         (list _< (@name arg) _>)
       (list _< (@name (car arg)) (@attribute+ (cdr arg)) _>)))
   (@tail (arg)
     (list _</ (@name (if (consp arg) (car arg) arg)) _>))))

(defun output-html (fp lst &optional br)
  (format fp "~{~A~}" (flatten (cdr (html lst br)))))

; 他種類のscriptを挿入する際に利用。
(defun comment (str)
  (list t "<!-- " str " -->"))
;変換後データが出てくる。
(html (setq x '((table (width 200 px) (height 100 px)) (tr ((td (class u1)) "data")))))
|
(t ((#1="<" #2="table" (#3=" " "width" #4="=\"" (200 (#3# (#5="px" #6=""))) #7="\"" #3# "height" #4# (100 (#3# (#5# #6#))) #7#) #8=">") #6# (((#1# #9="tr" #8#) #6# (((#1# #10="td" (#3# "class" #4# ("u1" #6#) #7#) #8#) #6# (("data") #6#) (#11="</" #10# #8#)) #6#) (#11# #9# #8#)) #6#) (#11# #2# #8#)))

;実際に出力する際は出力用の関数を用いる。
(output-html t x)
|
<table width="200 px" height="100 px"><tr><td class="u1">data</td></tr></table>

;htmlの出力をhtmlフォーマットのデータに組み込める。
(output-html t (html (list (car data) (html x) (cdr data))))
|
<body><table width="200 px" height="100 px"><tr><td class="u1">data</td></tr></table><hoge><p>fuga</p><br /><div class="fizz"></div><img src="foo.jpg" width="200" />100<1>23</1>456p/br</hoge></body>