1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
6 ;;;; Purpose: Lisp Markup Language functions
7 ;;;; Programmer: Kevin M. Rosenberg
8 ;;;; Date Started: Aug 2002
10 ;;;; $Id: lml.cl,v 1.11 2002/09/16 10:02:14 kevin Exp $
12 ;;;; This file, part of LML, is Copyright (c) 2002 by Kevin M. Rosenberg
14 ;;;; LML users are granted the rights to distribute and use this software
15 ;;;; as governed by the terms of the GNU General Public License v2
16 ;;;; (http://www.gnu.org/licenses/gpl.html)
17 ;;;; *************************************************************************
19 (declaim (optimize (debug 3) (speed 3) (safety 1) (compilation-speed 0)))
22 (defun html4-prologue-string ()
23 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">")
25 (defun xml-prologue-string ()
26 "version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"yes\"?>")
28 (defun xhtml-prologue-string ()
29 "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">")
31 (defvar *print-spaces* nil)
33 (defun reset-indent ()
36 (defun lml-print (str &rest args)
37 (when (streamp *html-output*)
38 (when *print-spaces* (indent-spaces *indent* *html-output*))
40 (apply #'format *html-output* str args)
41 (princ str *html-output*))
42 (when *print-spaces* (format *html-output* "~%"))
45 (defmacro lml-line (str &rest args)
46 `(lml-print ,str ,@args))
48 (defun lml-print-date (date)
49 (lml-print (date-string date)))
51 (defmacro lml-exec-body (&body forms)
59 `(lml-print "~D" ,form))
61 `(lml-print (string-downcase (symbol-name ,form))))
68 (defmacro with-attr-string (tag attr-string &body body)
69 (let ((attr (gensym)))
70 `(let ((,attr ,attr-string))
71 (lml-print "<~(~A~)~A>" ',tag
72 (if (and (stringp ,attr) (plusp (length ,attr)))
73 (format nil "~A" ,attr)
76 (lml-exec-body ,@body)
78 (lml-print "</~(~A~)>" ',tag))))
80 (defun one-keyarg-string (key value)
81 "Return attribute string for keys"
82 (format nil "~(~A~)=\"~A\"" key
85 (string-downcase (symbol-name value)))
91 (defmacro with-keyargs (tag keyargs &body body)
96 (dolist (,kv ',keyargs)
98 (push (one-keyarg-string (car ,kv) it) ,attr)))
99 (with-attr-string ,tag (list-to-spaced-string (nreverse ,attr)) ,@body)))))
101 (defmacro with (tag &rest args)
107 (let ((arg (nth i args))
108 (value (when (< (1+ i) n)
110 (if (keyword-symbol? arg)
112 (push (list arg value) keyargs)
115 `(with-keyargs ,tag ,keyargs ,@(nreverse body))))
118 (defmacro keyargs-string (&rest args)
119 "Returns a string of attributes and values. Result contains a leading space."
120 (let ((keyarg-list '()))
121 (loop for ( name val ) on args by #'cddr
124 (push (one-keyarg-string name val) keyarg-list)))
125 (list-to-spaced-string (nreverse keyarg-list))))
128 (defmacro xhtml-prologue ()
130 (lml-print "~A~%" (xml-prologue-string))
131 (lml-print "~A~%" (xhtml-prologue-string))))
133 (defmacro link (dest &body body)
134 `(with a :href ,dest ,@body))
136 (defmacro link-c (class dest &body body)
137 `(with a :href ,dest :class ,class ,@body))
139 (defmacro img (dest &key class id alt style width height align)
141 (eval `(keyargs-string :class ,class :id ,id :alt ,alt :style ,style
142 :width ,width :height ,height :align ,align))))
143 `(lml-print ,(format nil "<img src=\"~A\"~A />" dest attr))))
145 (defmacro input (&key name class id type style size maxlength value)
147 (eval `(keyargs-string :name ,name :class ,class :id ,id :style ,style
148 :size ,size :maxlength ,maxlength :value ,value
150 `(lml-print ,(format nil "<input~A />" attr))))
152 (defmacro meta (name content)
153 `(with meta :name ,name :content ,content))
155 (defmacro meta-key (&key name content http-equiv)
156 `(with meta :name ,name :content ,content :http-equiv ,http-equiv))
159 `(lml-print "<br />"))
162 `(lml-print "<hr />"))
164 (defmacro lml-tag-macro (tag)
166 (defmacro ,tag (&body body)
167 `(with ,',tag ,@body))
170 (defmacro lml-tag-class-macro (tag)
171 (let ((name (intern (format nil "~A-~A" tag :c))))
173 (defmacro ,name (&body body)
174 `(with ,',tag :class ,(car body) ,@(cdr body)))
177 (eval-when (:compile-toplevel :load-toplevel :execute)
178 (defparameter *macro-list*
179 '(a div span h1 h2 h3 h4 h5 h6 i b p li ul ol table tbody td tr body head
180 html title pre tt u dl dt dd kbd code form))
181 (export '(link link-c br hr img input meta meta-key))
182 (export *macro-list*))
184 (loop for i in *macro-list*
186 (eval `(lml-tag-macro ,i))
187 (eval `(lml-tag-class-macro ,i)))
189 (defmacro print-page (title &body body)
195 (defmacro page (out-file &body body)
196 `(with-open-file (*html-output*
197 (lml-file-name ,out-file :output)
199 :if-exists :supersede)
201 (html :xmlns "http://www.w3.org/1999/xhtml"
204 (set-macro-character #\[
205 #'(lambda (stream char)
206 (declare (ignore char))
208 (curr-string (make-array 1024 :fill-pointer 0 :adjustable t :element-type 'character))
210 (do ((ch (read-char stream t nil t) (read-char stream t nil t)))
214 ;; Starting top-level ,(
216 (push `(lml-print ,curr-string) forms)
217 (setf (fill-pointer curr-string) 0)
219 (vector-push #\( curr-string)
220 (do ((ch (read-char stream t nil t) (Read-char stream t nil t)))
223 (format *trace-output* "Syntax error reading #\]")
225 (vector-push-extend ch curr-string))
226 (vector-push-extend #\) curr-string)
227 (let ((eval-string (read-from-string curr-string))
230 `(let ((,res ,eval-string))
234 (setf (fill-pointer curr-string) 0))
235 ;; read comma, then non #\( char
238 (setq got-comma nil))
239 (vector-push-extend #\, curr-string) ;; push previous command
240 (vector-push-extend ch curr-string)))
241 ;; previous character is not a comma
246 (vector-push-extend ch curr-string)))))
247 (push `(lml-print ,curr-string) forms)
248 `(progn ,@(nreverse forms)))))