list2html

HTMLパーサのところで、パース結果のlistをcl-whoに渡せば、HTML文字列が生成できるということを書いたが、整形されていなくても良いなら、変換処理自体はpretty-printを使って*1簡単に実装できる。


準備
参照: nlet

;; 型定義: car部がkeywordならHTMLリスト
(defun html-element-p (obj) (and (consp obj) (keywordp (car obj))))
(deftype html-element () '(satisfies html-element-p))

;; HTMLリスト操作関数群
(defun element-contents-position (elem)
  (nlet self ((rest (cdr elem)) (i 1))
    (if (not (keywordp (car rest)))
        i
      (self (cddr rest) (+ i 2)))))

(defun element-attributes (elem)
  (subseq elem 1 (element-contents-position elem)))

(defun element-contents (elem)
  (subseq elem (element-contents-position elem)))


list -> HTML変換

(defvar *pprint-html* (copy-pprint-dispatch))

;; 変換用テーブル登録
(set-pprint-dispatch
 'html-element
 (lambda (stream elem &aux (name (car elem)))
    (if (eq name :!)
	;; コメントと仮定(不正確)
        (format stream "<!-- ~A -->" (third elem))
      ;; 通常の要素
      (format stream "<~A~@[ ~{~A=~S~}~]~:[ /~;>~:*~{~A~}</~0@*~A~]>"
	      name (element-attributes elem) (element-contents elem))))
  0
  *pprint-html*)

(defun list2html (html-lst)
  (with-output-to-string (*standard-output*)
    (write html-lst :pprint-dispatch *pprint-html*)))


実行

> (list2html '(:html (:head (:title "タイトル")) (:! :comment "コメント") (:body (:div :id 30 "テキスト1" (:br) "テキスト2"))))
--> "<HTML><HEAD><TITLE>タイトル</TITLE></HEAD><!-- コメント --><BODY><DIV ID=30>テキスト1<BR />テキスト2</DIV></BODY></HTML>"

要素名等が全部大文字になってしまうが、(setf *print-case* :downcase)としておけば小文字に出来る。

*1:使わなくても、もちろん出来る