1 ;;; bb.lisp - BBCode parser
3 ;;;; Copyright (c) 2008, Maciej Pasternacki <maciej@pasternacki.net>
4 ;;;; All rights reserved. This file is available on the terms
5 ;;;; detailed in COPYING file included with it.
8 (:use
#:common-lisp
#:meta-sexp
)
10 (in-package #:trane-bb
)
12 (defun html-quote-char (char stream
)
13 "Output CHAR safely escaped for HTML to STREAM."
15 (#\
< (write-string "<" stream
))
16 (#\
> (write-string ">" stream
))
17 (#\
& (write-string "&" stream
))
18 (#\" (write-string """ stream
))
19 (t (write-char char stream
))))
21 (defun html-quote-string (string)
22 "Escape STRING for HTML."
23 (with-output-to-string (s)
24 (map nil
#'(lambda (c) (html-quote-char c s
)) string
)))
26 (declaim (ftype function bb-inline
))
28 ;;; inline, <tag>interior</tag>
29 (defrenderer bb-simple
! (tag arg interior
&aux
(lctag (string-downcase (string tag
))))
31 (format attachment
"<~A>~A</~A>" lctag
(bb-inline interior
) lctag
))
34 (defrenderer bb-url
! (tag arg interior
) (attachment)
35 (format attachment
"<a href=\"~A\">~A</a>"
39 (bb-inline interior
)))
41 ;;; inline, for [color]
42 (defrenderer bb-color
! (tag arg interior
) (attachment)
43 (format attachment
"<span style=\"color:~A;\">~A</span>"
44 (html-quote-string arg
) (bb-inline interior
)))
46 ;;; inline, sets style=TAG
47 (defrenderer bb-style
! (tag arg interior
) (attachment)
48 (format attachment
"<span style=\"~A\">~A</span>"
49 tag
(bb-inline interior
)))
52 (defrenderer bb-size
! (tag arg interior
) (attachment)
53 (format attachment
"<span style=\"font-weight:~[xx-small~;x-small~;small~;medium~;large~;x-large~;xx-large~];\">~A</span>"
54 (+ 3 (parse-integer arg
)) (bb-inline interior
)))
57 (defrenderer bb-quote
! (tag arg interior
) (attachment)
58 (format attachment
"<blockquote>~:[<q>~A</q>~;~A~]~A</blockquote>"
60 (html-quote-string arg
)
61 (bb-inline interior
)))
64 (defrule whitespace
*?
() ()
65 (:* (:or
(:type white-space?
)
68 ;;; list item, inline, meaningful only within [list] block
69 (defrule bb-list-item?
(&aux
(interior (make-char-accum))) ()
71 "[*]" (:* (:not
(:checkpoint
"[*]"))
72 (:char-push interior
))
73 (:render bb-simple
! "li" nil interior
))
75 ;;; list interior, many list items
76 (defrule bb-list-interior?
() ()
77 (:+ (:rule bb-list-item?
)))
80 (defrenderer bb-list
! (tag arg interior
) (attachment)
81 (setf interior
(with-output-to-string (s)
82 (bb-list-interior?
(create-parser-context interior
:attachment s
))))
84 (format attachment
"<ul>~A</ul>" interior
)
85 (format attachment
"<ol style=\"list-style-type:~A;\">~A</ol>"
86 (cond ((string= arg
"1") "decimal")
87 ((string= arg
"01") "decimal-leading-zero")
88 ((string= arg
"i") "lower-roman")
89 ((string= arg
"I") "upper-roman")
90 ((string= arg
"a") "lower-latin")
91 ((string= arg
"A") "upper-latin")
92 ((string= arg
"alpha") "lower-greek")
93 ((string= arg
"α") "lower-greek")
94 (t (error "Unsupported enumeration style.")))
98 (defmacro defbbrule
(bbtag renderer
&optional
(tag `(quote ,bbtag
)))
99 (let ((rule-name (intern (concatenate 'string
103 `(defrule ,rule-name
(&aux
(arg (make-char-accum)) (interior (make-char-accum))) ()
104 #\
[ (:icase
,(string bbtag
))
105 (:?
"=" (:+ (:not
"]")
108 (:* (:not
(:checkpoint
#\
[ #\
/ (:icase
,(string bbtag
)) #\
]))
109 (:char-push interior
))
110 (:?
#\
[ #\
/ (:icase
,(string bbtag
)) #\
])
112 `(:render
,renderer
,tag arg interior
)
113 (cons :render renderer
)))))
116 (defbbrule b bb-simple
!)
117 (defbbrule i bb-simple
!)
118 (defbbrule u bb-style
! "font-decoration: underline;")
119 (defbbrule s bb-style
! "font-decoration: line-through;")
120 (defbbrule url bb-url
!)
121 (defbbrule color bb-color
!)
122 (defbbrule size bb-size
!)
125 (defbbrule quote bb-quote
!)
126 (defbbrule list bb-list
!)
128 (defrule bb-inline?
(&aux c
) (attachment)
129 (:* (:or
(:rule bb-tag-b?
)
133 (:rule bb-tag-color?
)
136 (:and
(:assign c
(:read-atom
))
137 (html-quote-char c attachment
)))))
139 (defun bb-inline (text)
140 (with-output-to-string (s)
141 (bb-inline?
(create-parser-context text
:attachment s
))))
143 (defrenderer bb-paragraph
! (text) (attachment)
144 (format attachment
"<p>~A</p>" (bb-inline text
)))
146 (defrule bb-paragraph?
(&aux
(text (make-char-accum))) ()
147 (:+ (:not
(:checkpoint
(:type newline?
)
152 (:render bb-paragraph
! text
))
154 (defrule bb-document?
() ()
155 (:* (:rule whitespace
*?
)
157 (:or
(:rule bb-tag-quote?
)
159 (:rule bb-paragraph?
))))
161 (defun bb (input &optional stream
)
162 "Parse INPUT (either string or input stream) as BBCode, format
163 resulting HTML to STREAM or return it as string if STREAM is not
166 (bb-document?
(create-parser-context input
:attachment stream
))
167 (with-output-to-string (s)
168 (bb-document?
(create-parser-context input
:attachment s
)))))