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>