aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authorVityok <vityok@example.com>2017-12-21 14:29:51 +0200
committerVityok <vityok@example.com>2017-12-21 14:29:51 +0200
commitbfdd686f9e3769a86c45d192d76ddadbcd520f51 (patch)
treef359ae2f9632c64c19f68efa56d246c0331c1761 /docs
parentab4c8ca5244197b02e308406e5a154d2cb938f5a (diff)
downloadtl-who-bfdd686f9e3769a86c45d192d76ddadbcd520f51.tar.gz
tl-who-bfdd686f9e3769a86c45d192d76ddadbcd520f51.tar.bz2
tl-who-bfdd686f9e3769a86c45d192d76ddadbcd520f51.zip
moved doc/ to docs/
Diffstat (limited to 'docs')
-rw-r--r--docs/index.html829
1 files changed, 829 insertions, 0 deletions
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..f64dc10
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,829 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>CL-WHO - Yet another Lisp markup language</title>
+ <style type="text/css">
+ pre { padding:5px; background-color:#e0e0e0 }
+ h3, h4 { text-decoration: underline; }
+ a { text-decoration: none; padding: 1px 2px 1px 2px; }
+ a:visited { text-decoration: none; padding: 1px 2px 1px 2px; }
+ a:hover { text-decoration: none; padding: 1px 1px 1px 1px; border: 1px solid #000000; }
+ a:focus { text-decoration: none; padding: 1px 2px 1px 2px; border: none; }
+ a.none { text-decoration: none; padding: 0; }
+ a.none:visited { text-decoration: none; padding: 0; }
+ a.none:hover { text-decoration: none; border: none; padding: 0; }
+ a.none:focus { text-decoration: none; border: none; padding: 0; }
+ a.noborder { text-decoration: none; padding: 0; }
+ a.noborder:visited { text-decoration: none; padding: 0; }
+ a.noborder:hover { text-decoration: none; border: none; padding: 0; }
+ a.noborder:focus { text-decoration: none; border: none; padding: 0; }
+ pre.none { padding:5px; background-color:#ffffff }
+ </style>
+</head>
+
+<body bgcolor=white>
+
+<h2>CL-WHO - Yet another Lisp markup language</h2>
+
+<blockquote>
+<br>&nbsp;<br><h3><a name=abstract class=none>Abstract</a></h3>
+
+There are plenty of <a
+href="http://www.cliki.net/Lisp%20Markup%20Languages">Lisp Markup
+Languages</a> out there - every Lisp programmer seems to write at
+least one during his career - and CL-WHO (where <em>WHO</em> means
+&quot;with-html-output&quot; for want of a better acronym) is probably
+just as good or bad as the next one. They are all more or less similar
+in that they provide convenient means to convert S-expressions
+intermingled with code into (X)HTML, XML, or whatever but differ with
+respect to syntax, implementation, and API. So, if you haven't made a
+choice yet, check out the alternatives as well before you begin to use
+CL-WHO just because it was the first one you came across. (Was that
+repelling enough?) If you're looking for a slightly different approach
+you might also want to look at <a
+href="http://weitz.de/html-template/">HTML-TEMPLATE</a>.
+<p>
+I wrote this one in 2002 although at least Tim Bradshaw's <a
+href="http://www.cliki.net/htout">htout</a> and <a
+href="http://opensource.franz.com/aserve/aserve-dist/doc/htmlgen.html">AllegroServe's
+HTML generation facilities</a> by John Foderaro of Franz Inc. were
+readily available. Actually, I don't remember why I had to write my
+own library - maybe just because it was fun and didn't take very long. The
+syntax was obviously inspired by htout although it is slightly
+different.
+<p>
+CL-WHO tries to create efficient code in that it makes constant
+strings as long as possible. In other words, the code generated by the
+CL-WHO macros will usually be a sequence of <code>WRITE-STRING</code>
+forms for constant parts of the output interspersed with arbitrary
+code inserted by the user of the macro. CL-WHO will make sure that
+there aren't two adjacent <code>WRITE-STRING</code> forms with
+constant strings. CL-WHO's output is
+either XHTML (default), 'plain' (SGML) HTML or HTML5 (using HTML syntax) &mdash; depending on
+what you've set <a href="#html-mode"><code>HTML-MODE</code></a> to.
+<p>
+CL-WHO is intended to be portable and should work with all
+conforming Common Lisp implementations. <a
+href="#mail">Let us know</a> if you encounter any
+problems.
+<p>
+It comes with a <a
+href="http://www.opensource.org/licenses/bsd-license.php">BSD-style
+license</a> so you can basically do with it whatever you want.
+<p>
+CL-WHO is for example used by <a href="http://clutu.com/">clutu</a> and <a href="http://heikestephan.de/">Heike Stephan</a>.
+
+<p>
+<font color=red>Download shortcut:</font> <a href="http://weitz.de/files/cl-who.tar.gz">http://weitz.de/files/cl-who.tar.gz</a>.
+</blockquote>
+
+<br>&nbsp;<br><h3><a class=none name="contents">Contents</a></h3>
+<ol>
+ <li><a href="#example">Example usage</a>
+ <li><a href="#install">Download and installation</a>
+ <li><a href="#support">Support</a>
+ <li><a href="#syntax">Syntax and Semantics</a>
+ <li><a href="#dictionary">The CL-WHO dictionary</a>
+ <ol>
+ <li><a href="#with-html-output"><code>with-html-output</code></a>
+ <li><a href="#with-html-output-to-string"><code>with-html-output-to-string</code></a>
+ <li><a href="#*attribute-quote-char*"><code>*attribute-quote-char*</code></a>
+ <li><a href="#*downcase-tokens-p*"><code>*downcase-tokens-p*</code></a>
+ <li><a href="#*html-empty-tag-aware-p*"><code>*html-empty-tag-aware-p*</code></a>
+ <li><a href="#*html-empty-tags*"><code>*html-empty-tags*</code></a>
+ <li><a href="#*html-no-indent-tags*"><code>*html-no-indent-tags*</code></a>
+ <li><a href="#*prologue*"><code>*prologue*</code></a>
+ <li><a href="#esc"><code>esc</code></a>
+ <li><a href="#fmt"><code>fmt</code></a>
+ <li><a href="#htm"><code>htm</code></a>
+ <li><a href="#str"><code>str</code></a>
+ <li><a href="#html-mode"><code>html-mode</code></a>
+ <li><a href="#escape-string"><code>escape-string</code></a>
+ <li><a href="#escape-char"><code>escape-char</code></a>
+ <li><a href="#*escape-char-p*"><code>*escape-char-p*</code></a>
+ <li><a href="#escape-string-minimal"><code>escape-string-minimal</code></a>
+ <li><a href="#escape-string-minimal-plus-quotes"><code>escape-string-minimal-plus-quotes</code></a>
+ <li><a href="#escape-string-iso-8859-1"><code>escape-string-iso-8859-1</code></a>
+ <li><a href="#escape-string-all"><code>escape-string-all</code></a>
+ <li><a href="#escape-char-minimal"><code>escape-char-minimal</code></a>
+ <li><a href="#escape-char-minimal-plus-quotes"><code>escape-char-minimal-plus-quotes</code></a>
+ <li><a href="#escape-char-iso-8859-1"><code>escape-char-iso-8859-1</code></a>
+ <li><a href="#escape-char-all"><code>escape-char-all</code></a>
+ <li><a href="#conc"><code>conc</code></a>
+ <li><a href="#convert-tag-to-string-list"><code>convert-tag-to-string-list</code></a>
+ <li><a href="#convert-attributes"><code>convert-attributes</code></a>
+ </ol>
+ <li><a href="#ack">Acknowledgements</a>
+</ol>
+
+<br>&nbsp;<br><h3><a name="example" class=none>Example usage</a></h3>
+
+Let's assume that <code>*HTTP-STREAM*</code> is the stream your web
+application is supposed to write to. Here are some contrived code snippets
+together with the Lisp code generated by CL-WHO and the resulting HTML output.
+
+<table border=0 cellspacing=10 width="100%">
+
+<tr>
+<td bgcolor="#e0e0e0" valign=top><pre>
+(<a class=noborder href="#with-html-output">with-html-output</a> (*http-stream*)
+ (loop for (link . title) in '((&quot;http://zappa.com/&quot; . &quot;Frank Zappa&quot;)
+ (&quot;http://marcusmiller.com/&quot; . &quot;Marcus Miller&quot;)
+ (&quot;http://www.milesdavis.com/&quot; . &quot;Miles Davis&quot;))
+ do (<a class=noborder href="#htm">htm</a> (:a :href link
+ (:b (str title)))
+ :br)))
+</pre></td>
+
+<td valign=top rowspan=2>
+<a href='http://zappa.com/'><b>Frank Zappa</b></a><br /><a href='http://marcusmiller.com/'><b>Marcus Miller</b></a><br /><a href='http://www.milesdavis.com/'><b>Miles Davis</b></a><br />
+</td>
+</tr>
+
+<tr>
+<td bgcolor="#e0e0e0" valign=top><pre>
+<font color="orange">;; code generated by CL-WHO (simplified)</font>
+
+(let ((*http-stream* *http-stream*))
+ (progn
+ nil
+ (loop for (link . title) in '((&quot;http://zappa.com/&quot; . &quot;Frank Zappa&quot;)
+ (&quot;http://marcusmiller.com/&quot; . &quot;Marcus Miller&quot;)
+ (&quot;http://www.milesdavis.com/&quot; . &quot;Miles Davis&quot;))
+ do (progn
+ (write-string &quot;&lt;a href='&quot; *http-stream*)
+ (princ link *http-stream*)
+ (write-string &quot;'&gt;&lt;b&gt;&quot; *http-stream*)
+ (princ title *http-stream*)
+ (write-string &quot;&lt;/b&gt;&lt;/a&gt;&lt;br /&gt;&quot; *http-stream*)))))
+</pre></td>
+</tr>
+
+<tr>
+<td bgcolor="#e0e0e0" valign=top><pre>
+(<a class=noborder href="#with-html-output">with-html-output</a> (*http-stream*)
+ (:table :border 0 :cellpadding 4
+ (loop for i below 25 by 5
+ do (<a class=noborder href="#htm">htm</a>
+ (:tr :align &quot;right&quot;
+ (loop for j from i below (+ i 5)
+ do (<a class=noborder href="#htm">htm</a>
+ (:td :bgcolor (if (oddp j)
+ &quot;pink&quot;
+ &quot;green&quot;)
+ (fmt &quot;~@R&quot; (1+ j))))))))))
+</pre></td>
+
+<td valign=top rowspan=2>
+<table border='0' cellpadding='4'><tr align='right'><td bgcolor='green'>I</td><td bgcolor='pink'>II</td><td bgcolor='green'>III</td><td bgcolor='pink'>IV</td><td bgcolor='green'>V</td></tr><tr align='right'><td bgcolor='pink'>VI</td><td bgcolor='green'>VII</td><td bgcolor='pink'>VIII</td><td bgcolor='green'>IX</td><td bgcolor='pink'>X</td></tr><tr align='right'><td bgcolor='green'>XI</td><td bgcolor='pink'>XII</td><td bgcolor='green'>XIII</td><td bgcolor='pink'>XIV</td><td bgcolor='green'>XV</td></tr><tr align='right'><td bgcolor='pink'>XVI</td><td bgcolor='green'>XVII</td><td bgcolor='pink'>XVIII</td><td bgcolor='green'>XIX</td><td bgcolor='pink'>XX</td></tr><tr align='right'><td bgcolor='green'>XXI</td><td bgcolor='pink'>XXII</td><td bgcolor='green'>XXIII</td><td bgcolor='pink'>XXIV</td><td bgcolor='green'>XXV</td></tr></table>
+</td>
+</tr>
+
+<tr>
+<td bgcolor="#e0e0e0" valign=top><pre>
+<font color="orange">;; code generated by CL-WHO (simplified)</font>
+
+(let ((*http-stream* *http-stream*))
+ (progn
+ nil
+ (write-string &quot;&lt;table border='0' cellpadding='4'&gt;&quot; *http-stream*)
+ (loop for i below 25 by 5
+ do (progn
+ (write-string &quot;&lt;tr align='right'&gt;&quot; *http-stream*)
+ (loop for j from i below (+ i 5)
+ do (progn
+ (write-string &quot;&lt;td bgcolor='&quot; *http-stream*)
+ (princ (if (oddp j) &quot;pink&quot; &quot;green&quot;) *http-stream*)
+ (write-string &quot;'&gt;&quot; *http-stream*)
+ (format *http-stream* &quot;~@r&quot; (1+ j))
+ (write-string &quot;&lt;/td&gt;&quot; *http-stream*)))
+ (write-string &quot;&lt;/tr&gt;&quot; *http-stream*)))
+ (write-string &quot;&lt;/table&gt;&quot; *http-stream*)))
+</pre></td>
+</tr>
+
+<tr>
+<td bgcolor="#e0e0e0" valign=top><pre>
+(<a class=noborder href="#with-html-output">with-html-output</a> (*http-stream*)
+ (:h4 "Look at the character entities generated by this example")
+ (loop for i from 0
+ for string in '("Fête" "Sørensen" "naïve" "Hühner" "Straße")
+ do (<a class=noborder href="#htm">htm</a>
+ (:p :style (<a href="#conc">conc</a> "background-color:" (case (mod i 3)
+ ((0) "red")
+ ((1) "orange")
+ ((2) "blue")))
+ (<a class=noborder href="#htm">htm</a> (<a href="#esc">esc</a> string))))))
+</pre></td>
+<td valign=top rowspan=2>
+<h4>Look at the character entities generated by this example</h4><p style='background-color:red'>F&#xEA;te</p><p style='background-color:orange'>S&#xF8;rensen</p><p style='background-color:blue'>na&#xEF;ve</p><p style='background-color:red'>H&#xFC;hner</p><p style='background-color:orange'>Stra&#xDF;e</p>
+</td>
+</tr>
+
+<tr>
+<td bgcolor="#e0e0e0" valign=top><pre>
+<font color="orange">;; code generated by CL-WHO (simplified)</font>
+
+(let ((*http-stream* *http-stream*))
+ (progn
+ nil
+ (write-string
+ &quot;&lt;h4&gt;Look at the character entities generated by this example&lt;/h4&gt;&quot;
+ *http-stream*)
+ (loop for i from 0 for string in '(&quot;Fête&quot; &quot;Sørensen&quot; &quot;naïve&quot; &quot;Hühner&quot; &quot;Straße&quot;)
+ do (progn
+ (write-string &quot;&lt;p style='&quot; *http-stream*)
+ (princ (<a class=noborder href="#conc">conc</a> &quot;background-color:&quot;
+ (case (mod i 3)
+ ((0) &quot;red&quot;)
+ ((1) &quot;orange&quot;)
+ ((2) &quot;blue&quot;)))
+ *http-stream*)
+ (write-string &quot;'&gt;&quot; *http-stream*)
+ (progn (write-string (<a class=noborder href="#escape-string">escape-string</a> string) *http-stream*))
+ (write-string &quot;&lt;/p&gt;&quot; *http-stream*)))))
+</pre></td>
+</tr>
+
+
+</table>
+
+<br>&nbsp;<br><h3><a name="install" class=none>Download and installation</a></h3>
+
+CL-WHO together with this documentation can be downloaded from <a
+href="http://weitz.de/files/cl-who.tar.gz">http://weitz.de/files/cl-who.tar.gz</a>. The
+current version is 1.1.3.
+<p>
+The preferred method to fetch, compile and load CL-WHO is via <a href="http://beta.quicklisp.org/">Quicklisp</a>. Install
+Quicklisp, then run
+<pre>(ql:quickload :cl-who)</pre>
+<p>
+The current development version of CL-WHO can be found
+at <a href="https://github.com/edicl/cl-who">https://github.com/edicl/cl-who</a>.
+This is the one to send <a href="#mail">patches</a> against. Use at
+your own risk.
+<p>
+Lu&iacute;s Oliveira maintains an
+unofficial <a href="http://darcs.net/">darcs</a> repository of CL-WHO
+at <a href="http://common-lisp.net/~loliveira/ediware/">http://common-lisp.net/~loliveira/ediware/</a>.
+<p>
+You can run a test suite which tests <em>some</em> (but
+not <em>all</em>) aspects of the library with
+<pre>
+(asdf:oos 'asdf:test-op :cl-who)
+</pre>
+
+<br>&nbsp;<br><h3><a name="mail" class=none>Support and mailing lists</a></h3>
+
+The development version of cl-who can be
+found <a href="https://github.com/edicl/cl-who" target="_new">on
+github</a>. Please use the github issue tracking system to submit bug
+reports. Patches are welcome, please
+use <a href="https://github.com/edicl/cl-who/pulls">GitHub pull
+requests</a>. If you want to make a change,
+please <a href="http://weitz.de/patches.html" target="_new">read this
+first</a>.
+
+<br>&nbsp;<br><h3><a name="syntax" class=none>Syntax and Semantics</a></h3>
+
+CL-WHO is essentially just one <a
+href="http://cl-cookbook.sourceforge.net/macros.html">macro</a>, <a
+href="#with-html-output"><code>WITH-HTML-OUTPUT</code></a>, which
+transforms the body of code it encloses into something else obeying the
+following rules (which we'll call <em>transformation rules</em>) for the body's forms:
+
+<ul>
+
+ <li>A string will be printed verbatim. To be
+more precise, it is transformed into a form which'll print this
+string to the stream the user provides.
+
+<table border=0 cellpadding=2 cellspacing=3><tr><td><pre>&quot;foo&quot; <font color="red">=&gt;</font> (write-string &quot;foo&quot; s)</pre></td></tr></table>
+
+ (Here and for the rest of this document the <em>red arrow</em> means '...&nbsp;will be converted to code equivalent to&nbsp;...' where <em>equivalent</em> means that all output is sent to the &quot;right&quot; stream.)
+
+ <li>Each list beginning with a <a
+href="http://www.lispworks.com/reference/HyperSpec/Body/t_kwd.htm"><em>keyword</em></a>
+is transformed into an (X)HTML <b>tag</b> of the same (usually <href="#*downcase-tokens-p*">downcased</a>) name by the following rules:
+
+ <ul>
+
+ <li>If the list contains nothing but the keyword, the resulting tag
+ will be empty.
+
+ <table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(:br) <font color="red">=&gt;</font> (write-string &quot;&lt;br /&gt;&quot; s)</pre></td></tr></table>
+ With <a href="#html-mode"><code>HTML-MODE</code></a> set to <code>:SGML</code> an empty element is written this way:
+ <table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(:br) <font color="red">=&gt;</font> (write-string &quot;&lt;br&gt;&quot; s)</pre></td></tr></table>
+
+ <li>The initial keyword can be followed by another keyword which will be interpreted as the name of an <b>attribute</b>. The next form which will be taken as the attribute's <b>value</b>. (If there's no next form it'll be as if the next form had been <code>NIL</code>.) The form denoting the attribute's value will be treated as follows. (Note that the behaviour with respect to attributes is <em>incompatible</em> with versions earlier than&nbsp;0.3.0!)
+ <ul>
+ <li>If it is a string it will be printed literally.
+
+ <table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(:td :bgcolor &quot;red&quot;) <font color="red">=&gt;</font> (write-string &quot;&lt;td bgcolor='red' /&gt;&quot; s)</pre></td></tr></table>
+
+ <li>If it is <code>T</code> and <a href="#html-mode"><code>HTML-MODE</code></a> is <code>:XML</code> (default) the attribute's value will be the attribute's name (following XHTML convention to denote attributes which don't have a value in HTML).
+
+ <table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(:td :nowrap t) <font color="red">=&gt;</font> (write-string &quot;&lt;td nowrap='nowrap' /&gt;&quot; s)</pre></td></tr></table>
+
+ With <a href="#html-mode"><code>HTML-MODE</code></a> set to <code>:SGML</code> or <code>:HTML5</code>:
+
+ <table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(:td :nowrap t) <font color="red">=&gt;</font> (write-string &quot;&lt;td nowrap&gt;&quot; s)</pre></td></tr></table>
+
+ Attribute minimization is controlled by <a href="#*empty-attribute-syntax*"><code>*EMPTY-ATTRIBUTE-SYNTAX*</code></a><br>
+
+ <li>If it is <code>NIL</code> the attribute will be left out completely.
+
+ <table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(:td :nowrap nil) <font color="red">=&gt;</font> (write-string &quot;&lt;td /&gt;&quot; s)</pre></td></tr></table>
+
+ <li>If it is a <a
+ href="http://www.lispworks.com/reference/HyperSpec/Body/26_glo_c.htm#constant_form"><em>constant form</em></a>, the result of evaluating it will be inserted into the resulting string as if printed with the <a
+ href="http://www.lispworks.com/reference/HyperSpec/Body/26_glo_c.htm#constant_form">format string</a> <code>&quot;~A&quot;</code> at macro expansion time.
+
+ <table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(:table :border 3) <font color="red">=&gt;</font> (write-string &quot;&lt;table border='3' /&gt;&quot; s)</pre></td></tr></table>
+
+ <li>If it is any other form it will be left as is and later evaluated at run time and printed like with <a
+ href="http://www.lispworks.com/reference/HyperSpec/Body/f_wr_pr.htm"><code>PRINC</code></a> <em>unless</em> the value is <code>T</code> or <code>NIL</code> which will be treated as above. (It is the application developer's job to provide the correct <a href="http://www.lispworks.com/reference/HyperSpec/Body/26_glo_p.htm#printer_control_variable">printer control variables</a>.)
+
+ <table border=0 cellpadding=2 cellspacing=3><tr><td><pre><font color="orange">;; simplified example, see function CHECKBOX below
+;; note that this form is not necessarily CONSTANTP in all Lisps</font>
+
+(:table :border (+ 1 2)) <font color="red">=&gt;</font> (write-string &quot;&lt;table border='&quot; s)
+ (princ (+ 1 2) s)
+ (write-string &quot;' />&quot; s)</pre></td></tr></table>
+ </ul>
+
+ <li>Once an attribute/value pair has been worked up another one can follow, i.e. if the form following an attribute's value is again a keyword it will again be treated as an attribute and so on.
+
+ <table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(:table :border 0 :cellpadding 5 :cellspacing 5)
+ <font color="red">=&gt;</font> (write-string &quot;&lt;table border='0' cellpadding='5' cellspacing='5' /&gt;&quot; s)</pre></td></tr></table>
+
+ <li>The first form following either the tag's name itself or an attribute value which is <em>not</em> a keyword determines the beginning of the tag's <b>content</b>. This and all the following forms are subject to the transformation rules we're just describing.
+
+ <table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(:p &quot;Paragraph&quot;) <font color="red">=&gt;</font> (write-string &quot;&lt;p&gt;Paragraph&lt;/p&gt;&quot; s)
+(:p :class "foo" &quot;Paragraph&quot;) <font color="red">=&gt;</font> (write-string &quot;&lt;p class='foo'&gt;Paragraph&lt;/p&gt;&quot; s)
+(:p :class "foo" &quot;One&quot; &quot; &quot; &quot;long&quot; &quot; &quot; &quot;sentence&quot;) <font color="red">=&gt;</font> (write-string &quot;&lt;p class='foo'&gt;One long sentence&lt;/p&gt;&quot; s)
+(:p :class "foo" &quot;Visit &quot; (:a :href &quot;http://www.cliki.net/&quot; &quot;CLiki&quot;))
+ <font color="red">=&gt;</font> (write-string &quot;&lt;p class='foo'&gt;Visit &lt;a href='http://www.cliki.net/'&gt;CLiki&lt;/a&gt;&lt;/p&gt;&quot; s)</pre></td></tr></table>
+
+ <li>Beginning with <a href="#install">version&nbsp;0.4.0</a> you can also use a syntax like that of <a href="http://opensource.franz.com/xmlutils/xmlutils-dist/phtml.htm">LHTML</a> where the tag and all attribute/value pairs are enclosed in an additional list:
+
+ <table border=0 cellpadding=2 cellspacing=3><tr><td><pre>((:p) &quot;Paragraph&quot;) <font color="red">=&gt;</font> (write-string &quot;&lt;p&gt;Paragraph&lt;/p&gt;&quot; s)
+((:p :class "foo") &quot;Paragraph&quot;) <font color="red">=&gt;</font> (write-string &quot;&lt;p class='foo'&gt;Paragraph&lt;/p&gt;&quot; s)
+((:p :class "foo" :name "humpty-dumpty") &quot;One&quot; &quot; &quot; &quot;long&quot; &quot; &quot; &quot;sentence&quot;)
+ <font color="red">=&gt;</font> (write-string &quot;&lt;p class='foo' name='humpty-dumpty'&gt;One long sentence&lt;/p&gt;&quot; s)
+((:p :class "foo") &quot;Visit &quot; ((:a :href &quot;http://www.cliki.net/&quot;) &quot;CLiki&quot;))
+ <font color="red">=&gt;</font> (write-string &quot;&lt;p class='foo'&gt;Visit &lt;a href='http://www.cliki.net/'&gt;CLiki&lt;/a&gt;&lt;/p&gt;&quot; s)</pre></td></tr></table>
+
+ </ul>
+
+ Here's a slightly more elaborate example:
+<pre>
+* (defun checkbox (stream name checked &amp;optional value)
+ (with-html-output (stream)
+ (:input :type &quot;checkbox&quot; :name name :checked checked :value value)))
+
+CHECKBOX
+* (with-output-to-string (s) (checkbox s &quot;foo&quot; t))
+
+&quot;&lt;input type='checkbox' name='foo' checked='checked' /&gt;&quot;
+* (with-output-to-string (s) (checkbox s &quot;foo&quot; nil))
+
+&quot;&lt;input type='checkbox' name='foo' /&gt;&quot;
+* (with-output-to-string (s) (checkbox s &quot;foo&quot; nil &quot;bar&quot;))
+
+&quot;&lt;input type='checkbox' name='foo' value='bar' /&gt;&quot;
+* (with-output-to-string (s) (checkbox s &quot;foo&quot; t &quot;bar&quot;))
+
+&quot;&lt;input type='checkbox' name='foo' checked='checked' value='bar' /&gt;&quot;
+</pre>
+
+ <li>A keyword alone will be treated like a list containing only this keyword.
+
+<table border=0 cellpadding=2 cellspacing=3><tr><td><pre>:hr <font color="red">=&gt;</font> (write-string &quot;&lt;hr /&gt;&quot; s)</pre></td></tr></table>
+
+ <li>A form which is neither a string nor a keyword nor a list beginning with a keyword will be left as is except for the following <a href="http://www.lispworks.com/documentation/HyperSpec/Body/s_flet_.htm#macrolet">local macros</a>:
+ <ul>
+ <li>Forms that look like <code>(<b>str</b> <i>form</i>)</code> will be substituted with
+ <span style="white-space: nowrap"><code>(let ((result <i>form</i>)) (when result (princ result s)))</code></span>.
+
+<table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(loop for i below 10 do (str i)) <font color="red">=&gt;</font>
+(loop for i below 10 do
+ (let ((#:result i))
+ (when #:result (princ #:result *standard-output*))))</pre></td></tr></table>
+
+ <li>Forms that look like <code>(<b>fmt</b> <i>form*</i>)</code> will be substituted with <code>(format s <i>form*</i>)</code>.
+
+<table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(loop for i below 10 do (fmt "~R" i)) <font color="red">=&gt;</font> (loop for i below 10 do (format s "~R" i))</pre></td></tr></table>
+ <li>Forms that look like <code>(<b>esc</b> <i>form</i>)</code> will be substituted with
+ <span style="white-space: nowrap"><code>(let ((result <i>form</i>)) (when result (write-string (<a href="#escape-string">escape-string</a> result s))))</code></span>.
+
+ <li>If a form looks like <code>(<b>htm</b> <i>form*</i>)</code> then each of the <code><i>forms</i></code> will be subject to the transformation rules we're just describing, i.e. this is the body is wrapped with another invocation of <a href="#with-html-output"><code>WITH-HTML-OUTPUT</code></a>.
+
+<table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(loop for i below 100 do (htm (:b &quot;foo&quot;) :br))
+ <font color="red">=&gt;</font> (loop for i below 100 do (progn (write-string &quot;&lt;b&gt;foo&lt;/b&gt;&lt;br /&gt;&quot; s)))</pre></td></tr></table>
+
+
+ </ul>
+
+ <li>That's all. Note in particular that CL-WHO knows <em>nothing</em> about HTML or XHTML, i.e. it doesn't check whether you mis-spelled tag names or use attributes which aren't allowed. CL-WHO doesn't care if you use, say, <code>:foobar</code> instead of <code>:hr</code>.
+</ul>
+
+<br>&nbsp;<br><h3><a class=none name="dictionary">The CL-WHO dictionary</a></h3>
+
+CL-WHO exports the following symbols:
+
+<p><br>[Macro]
+<br><a class=none name="with-html-output"><b>with-html-output</b> <i>(var <tt>&amp;optional</tt> stream <tt>&amp;key</tt> prologue indent) declaration* form*</i> =&gt; <i>result*</i></a>
+
+<blockquote><br> This is the main macro of CL-WHO. It will transform
+its body by the transformation rules described
+in <a href="#syntax"><em>Syntax and Semantics</em></a> such that the
+output generated is sent to the stream denoted
+by <code><i>var</i></code>
+and <code><i>stream</i></code>. <code><i>var</i></code> must be a
+symbol. If <code><i>stream</i></code> is <code>NIL</code> it is
+assumed that <code><i>var</i></code> is already bound to a stream,
+if <code><i>stream</i></code> is
+not <code>NIL</code> <code><i>var</i></code> will be bound to the
+form <code><i>stream</i></code> which will be evaluated at run
+time. <code><i>prologue</i></code> should be a string
+(or <code>NIL</code> for the empty string which is the default) which
+is guaranteed to be the first thing sent to the stream from within the
+body of this macro. If <code><i>prologue</i></code> is <code>T</code>
+the prologue string is the value
+of <a href="#*prologue*"><code>*PROLOGUE*</code></a>.
+<p>
+CL-WHO will usually try not to insert any unnecessary whitespace in
+order to save bandwidth. However, if <code><i>indent</i></code>
+is <em>true</em> line breaks will be inserted and nested tags will be
+indented properly. The value of <code><i>indent</i></code> - if it is
+an integer - will be taken as the initial indentation. If it is not an
+integer it is assumed to mean <code>0</code>. Value
+of <a href="#*html-no-indent-tags*"><code>*HTML-NO-INDENT-TAGS*</code></a>
+controls which tag-contents are excempt from indentation: by default
+contents of <code>PRE</code> and <code>TEXTAREA</code> tags are not
+indented to avoid spurious layout changes. (Note: in certain
+situations additional whitespace may change the layout of tables.)
+<p>
+The <code><i>results</i></code> are the values returned by
+the <code><i>forms</i></code>.
+<p>
+Note that the keyword arguments <code><i>prologue</i></code>
+and <code><i>indent</i></code>, and the associated variables are
+used <em>at macro expansion time</em>.
+
+<pre>
+* (with-html-output (*standard-output* nil :prologue t)
+ (:html (:body &quot;Not much there&quot;))
+ (values))
+&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&gt;&lt;html&gt;&lt;body&gt;Not much there&lt;/body&gt;&lt;/html&gt;
+* (with-html-output (*standard-output*)
+ (:html (:body :bgcolor &quot;white&quot;
+ &quot;Not much there&quot;))
+ (values))
+&lt;html&gt;&lt;body bgcolor='white'&gt;Not much there&lt;/body&gt;&lt;/html&gt;
+* (with-html-output (*standard-output* nil :prologue t :indent t)
+ (:html (:body :bgcolor &quot;white&quot;
+ &quot;Not much there&quot;))
+ (values))
+&lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Strict//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd&quot;&gt;
+&lt;html&gt;
+ &lt;body bgcolor='white'&gt;
+ Not much there
+ &lt;/body&gt;
+&lt;/html&gt;
+</pre>
+</blockquote>
+
+<p><br>[Macro]
+<br><a class=none name="with-html-output-to-string"><b>with-html-output-to-string</b> <i>(var <tt>&amp;optional</tt> string-form <tt>&amp;key</tt> element-type prologue indent) declaration* form*</i> =&gt; <i>result*</i></a>
+
+<blockquote><br>
+This is just a thin wrapper around <a href="#with-html-output"><code>WITH-HTML-OUTPUT</code></a>. Indeed, the wrapper is so thin that the best explanation probably is to show its definition:
+<pre>
+(defmacro with-html-output-to-string ((var &amp;optional string-form
+ &amp;key (element-type ''character)
+ prologue
+ indent)
+ &amp;body body)
+ &quot;Transform the enclosed BODY consisting of HTML as s-expressions
+into Lisp code which creates the corresponding HTML as a string.&quot;
+ `(with-output-to-string (,var ,string-form :element-type ,element-type)
+ (with-html-output (,var nil :prologue ,prologue :indent ,indent)
+ ,@body)))
+</pre>
+Note that the <code><i>results</i></code> of this macro are determined by the behaviour of <a href="http://www.lispworks.com/reference/HyperSpec/Body/m_w_out_.htm"><code>WITH-OUTPUT-TO-STRING</code></a>.
+</blockquote>
+
+<p><br>[Special variable]
+<br><a class=none name="*attribute-quote-char*"><b>*attribute-quote-char*</b></a>
+
+<blockquote><br>
+This character is used as the quote character when building attributes. Defaults to the single quote <code>#\'</code>. Only other reasonable character is the double quote <code>#\&quot;</code>.
+</blockquote>
+
+<p><br>[Special variable]
+<br><a class=none name="*downcase-tokens-p*"><b>*downcase-tokens-p*</b></a>
+
+<blockquote><br>
+If the value of this variable is <code>NIL</code>, keyword symbols representing a tag or attribute name will not be
+automatically converted to lowercase. This is useful when one needs to
+output case sensitive XML. The default is <code>T</code>.
+</blockquote>
+
+<p><br>[Special variable]
+<br><a class=none name="*html-empty-tag-aware-p*"><b>*html-empty-tag-aware-p*</b></a>
+
+<blockquote><br>
+Set this to <code>NIL</code> to if you want to use CL-WHO as a strict XML
+generator. Otherwise, CL-WHO will only write empty tags listed in
+<a href="#*html-empty-tags*"><code>*HTML-EMPTY-TAGS*</code></a> as <code>&lt;tag/&gt;</code> (XHTML mode) or <code>&lt;tag&gt;</code> (SGML mode or HTML mode). For
+all other tags, it will always generate <code>&lt;tag&gt;&lt;/tag&gt;</code>. The initial value of this variable is <code>T</code>.
+</blockquote>
+
+<p><br>[Special variable]
+<br><a class=none name="*html-empty-tags*"><b>*html-empty-tags*</b></a>
+
+<blockquote><br>
+The list of HTML tags that should be output as empty tags. See
+<a href="#*html-empty-tag-aware-p*"><code>*HTML-EMPTY-TAG-AWARE-P*</code></a>.
+The initial value is the list
+<pre>
+(:area :atop :audioscope :base :basefont :br :choose :col :command :embed
+ :frame :hr :img :input :isindex :keygen :left :limittext :link :meta :nextid
+ :of :over :param :range :right :source :spacer :spot :tab :track :wbr)
+</pre>
+</blockquote>
+
+<p><br>[Special variable]
+<br><a class=none name="*html-no-indent-tags*"><b>*html-no-indent-tags*</b></a>
+
+<blockquote><br>
+The list of HTML tags that should disable indentation inside them even
+when indentation is requested. The initial value is a list containing
+only <code>:pre</code> and <code>:texarea</code>.
+</blockquote>
+
+<p><br>[Special variable]
+<br><a class=none name="*prologue*"><b>*prologue*</b></a>
+
+<blockquote><br>
+This is the prologue string which will be printed if the <code><i>prologue</i></code> keyword argument to <a href="#with-html-output"><code>WITH-HTML-OUTPUT</code></a> is <code>T</code>. Gets changed when you set <a href="#html-mode"><code>HTML-MODE</code></a>. Its initial value is
+
+<pre>&quot;&lt;!DOCTYPE html PUBLIC \&quot;-//W3C//DTD XHTML 1.0 Strict//EN\&quot; \&quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\&quot;&gt;&quot;</pre>
+</blockquote>
+
+<p><br>[Special variable]
+ <br><a class=none name="*empty-attribute-syntax*"><b>*empty-attribute-syntax*</b></a>
+
+ <blockquote><br>
+ This controls the attribute minimization. (also called 'boolean attributes', or 'empty attribute syntax' according to the w3 html standard). Set value to <code>T</code> to enable attribute minimization.
+ <p>In XHTML attribute minimization is forbidden, and all attributes must have a value. Thus in XHTML boolean attributes must be defined as
+ <pre> &lt;input disabled='disabled' /&gt;</pre>
+ In HTML5 and SGML HTML boolean attributes can be defined as
+ <pre>&lt;input disabled&gt;</pre>
+ Gets changed when you set <a href="#html-mode"><code>HTML-MODE</code></a>. Its initial value is <code>NIL</code>
+ </p>
+ </blockquote>
+
+<p><br>[Symbol]
+<br><a class=none name="esc"><b>esc</b></a>
+<br>[Symbol]
+<br><a class=none name="fmt"><b>fmt</b></a>
+<br>[Symbol]
+<br><a class=none name="htm"><b>htm</b></a>
+<br>[Symbol]
+<br><a class=none name="str"><b>str</b></a>
+
+<blockquote><br>
+These are just symbols with no bindings associated with them. The only reason they are exported is their special meaning during the transformations described in <a href="#syntax"><em>Syntax and Semantics</em></a>.
+</blockquote>
+
+<p><br>[Accessor]
+<br><a class=none name="html-mode"><b>html-mode</b></a> <i>=> mode</i>
+<br><tt>(setf (</tt><b>html-mode</b>) <i>mode</i><tt>)</tt>
+<blockquote><br>
+The function <code>HTML-MODE</code> returns the current mode for generating HTML. The default is <code>:XML</code> for XHTML. You can change this by setting it with <code>(SETF&nbsp;(HTML-MODE)&nbsp;:SGML)</code> to pre-XML HTML mode or <code>(SETF&nbsp;(HTML-MODE)&nbsp;:HTML5)</code> to HTML5 mode (using HTML syntax).
+<p>
+Setting it to SGML HTML sets the <a href="#*prologue*"><code>*prologue*</code></a> to the doctype string for HTML 4.01 transitional:
+<pre>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot; &quot;http://www.w3.org/TR/html4/loose.dtd&quot;&gt;</pre>
+Code generation in HTML5 and SGML HTML is slightly different from XHTML - there's no need to end empty elements with <code>/&gt;</code> and empty attributes are allowed.
+<p>
+Setting it to HTML5 sets the <a href="#*prologue*"><code>*prologue*</code></a> to the following doctype string:
+<pre>&lt;!DOCTYPE html&gt;</pre>
+</blockquote>
+
+<p><br>[Function]
+<br><a class=none name="escape-string"><b>escape-string</b></a> <i>string <tt>&amp;key</tt> test</i> =&gt; <i>escaped-string</i>
+
+<blockquote><br>
+This function will accept a string <code><i>string</i></code> and will replace every character for which <code><i>test</i></code> returns <em>true</em> with its character entity. The numeric character entities use decimal instead of hexadecimal values when <a href="#html-mode"><code>HTML-MODE</code></a> is set to <code>:SGML</code> because of compatibility reasons with old clients. <code><i>test</i></code> must be a function of one argument which accepts a character and returns a <a href="http://www.lispworks.com/reference/HyperSpec/Body/26_glo_g.htm#generalized_boolean">generalized boolean</a>. The default is the value of <a href="#*escape-char-p*"><code>*ESCAPE-CHAR-P*</code></a>. Note the <a href="#esc"><code>ESC</code></a> shortcut described in <a href="#syntax"><em>Syntax and Semantics</em></a>.
+
+<pre>
+* (escape-string &quot;&lt;Hühner&gt; 'naïve'&quot;)
+&quot;&amp;lt;H&amp;#xFC;hner&amp;gt; &amp;#x27;na&amp;#xEF;ve&amp;#x27;&quot;
+* (with-html-output-to-string (s)
+ (:b (esc &quot;&lt;Hühner&gt; 'naïve'&quot;)))
+&quot;&lt;!DOCTYPE html PUBLIC \&quot;-//W3C//DTD XHTML 1.0 Strict//EN\&quot; \&quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\&quot;&lt;b&gt;&amp;lt;H&amp;#xFC;hner&amp;gt; &amp;#x27;na&amp;#xEF;ve&amp;#x27;&lt;/b&gt;&quot;
+</pre>
+</blockquote>
+
+<p><br>[Function]
+<br><a class=none name="escape-char"><b>escape-char</b></a> <i>character <tt>&amp;key</tt> test</i> =&gt; <i>escaped-string</i>
+
+<blockquote><br>
+This function works identical to <a href="#escape-string"><code>ESCAPE-STRING</code></a>, except that it operates on characters instead of strings.
+</blockquote>
+
+<p><br>[Special variable]
+<br><a class=none name="*escape-char-p*"><b>*escape-char-p*</b></a>
+
+<blockquote><br>
+This is the default for the <code><i>test</i></code> keyword argument to <a href="#escape-string"><code>ESCAPE-STRING</code></a> and <a href="#escape-char"><code>ESCAPE-CHAR</code></a>. Its initial value is
+
+<pre>
+#'(lambda (char)
+ (or (find char &quot;&lt;&gt;&amp;'\&quot;&quot;)
+ (&gt; (char-code char) 127)))
+</pre>
+</blockquote>
+
+<p><br>[Function]
+<br><a class=none name="escape-string-minimal"><b>escape-string-minimal</b> <i>string</i> =&gt; <i>escaped-string</i></a>
+<br>[Function]
+<br><a class=none name="escape-string-minimal-plus-quotes"><b>escape-string-minimal-plus-quotes</b> <i>string</i> =&gt; <i>escaped-string</i></a>
+<br>[Function]
+<br><a class=none name="escape-string-iso-8859-1"><b>escape-string-iso-8859-1</b> <i>string</i> =&gt; <i>escaped-string</i></a>
+<br>[Function]
+<br><a class=none name="escape-string-all"><b>escape-string-all</b> <i>string</i> =&gt; <i>escaped-string</i></a>
+<br>[Function]
+<br><a class=none name="escape-char-minimal"><b>escape-char-minimal</b> <i>character</i> =&gt; <i>escaped-string</i></a>
+<br>[Function]
+<br><a class=none name="escape-char-minimal-plus-quotes"><b>escape-char-minimal-plus-quotes</b> <i>character</i> =&gt; <i>escaped-string</i></a>
+<br>[Function]
+<br><a class=none name="escape-char-iso-8859-1"><b>escape-char-iso-8859-1</b> <i>character</i> =&gt; <i>escaped-string</i></a>
+<br>[Function]
+<br><a class=none name="escape-char-all"><b>escape-char-all</b> <i>character</i> =&gt; <i>escaped-string</i></a>
+
+<blockquote><br> These are convenience function based
+on <a href="#escape-string"><code>ESCAPE-STRING</code></a>
+and <a href="#escape-char"><code>ESCAPE-CHAR</code></a>. The string
+functions are defined in a way similar to this one:
+
+<pre>
+(defun escape-string-minimal (string)
+ &quot;Escape only #\&lt;, #\&gt;, and #\&amp; in STRING.&quot;
+ (escape-string string :test #'(lambda (char) (find char &quot;&lt;&gt;&amp;&quot;))))
+
+(defun escape-string-minimal-plus-quotes (string)
+ &quot;Like ESCAPE-STRING-MINIMAL but also escapes quotes.&quot;
+ (escape-string string :test #'(lambda (char) (find char &quot;&lt;&gt;&amp;'\&quot;&quot;))))
+
+(defun escape-string-iso-8859-1 (string)
+ &quot;Escapes all characters in STRING which aren't defined in ISO-8859-1.&quot;
+ (escape-string string :test #'(lambda (char)
+ (or (find char &quot;&lt;&gt;&amp;'\&quot;&quot;)
+ (&gt; (char-code char) 255)))))
+
+(defun escape-string-all (string)
+ &quot;Escapes all characters in STRING which aren't in the 7-bit ASCII
+character set.&quot;
+ (escape-string string :test #'(lambda (char)
+ (or (find char &quot;&lt;&gt;&amp;'\&quot;&quot;)
+ (&gt; (char-code char) 127)))))
+</pre>
+The character functions are defined in an analogous manner.
+</blockquote>
+
+<p><br>[Function]
+<br><a class=none name="conc"><b>conc</b> <i><tt>&amp;rest</tt> string-list</i> =&gt; <i>string</i></a>
+
+<blockquote><br>
+Utility function to concatenate all arguments (which should be strings) into one string. Meant to be used mainly with attribute values.
+
+<pre>
+* (conc &quot;This&quot; &quot; &quot; &quot;is&quot; &quot; &quot; &quot;a&quot; &quot; &quot; &quot;sentence&quot;)
+&quot;This is a sentence&quot;
+* (with-html-output-to-string (s)
+ (:div :style (conc &quot;padding:&quot;
+ (format nil &quot;~A&quot; (+ 3 2)))
+ &quot;Foobar&quot;))
+&quot;&lt;div style='padding:5'&gt;Foobar&lt;/div&gt;&quot;
+</pre>
+</blockquote>
+
+<p><br>[Generic Function]
+<br><a class=none name="convert-tag-to-string-list"><b>convert-tag-to-string-list</b></a> <i>tag attr-list body body-fn</i> =&gt; <i>strings-or-forms</i>
+
+<blockquote><br>
+
+This function exposes some of CL-WHO's internals so users can
+customize its behaviour. It is called whenever a tag is processed and
+must return a corresponding list of strings or Lisp forms. The idea
+is that you can specialize this generic function in order to process
+certain tags yourself.
+<p>
+<code><i>tag</i></code> is a keyword symbol naming the outer tag,
+<code><i>attr-list</i></code> is an alist of its attributes (the car
+is the attribute's name as a keyword, the cdr is its value),
+<code><i>body</i></code> is the tag's body, and
+<code><i>body-fn</i></code> is a function which should be applied to
+the body to further process it. Of course, if you define your own
+methods you can ignore <code><i>body-fn</i></code> if you want.
+<p>
+Here are some simple examples:
+<pre>
+* (defmethod convert-tag-to-string-list ((tag (eql :red)) attr-list body body-fn)
+ (declare (ignore attr-list))
+ (nconc (cons "&lt;font color='red'&gt;" (funcall body-fn body)) (list "&lt;/font&gt;")))
+; Compiling LAMBDA (PCL::.PV-CELL. PCL::.NEXT-METHOD-CALL. TAG ATTR-LIST BODY BODY-FN):
+; Compiling Top-Level Form:
+
+#&lt;STANDARD-METHOD CONVERT-TAG-TO-STRING-LIST ((EQL :RED) T T T) {582B268D}&gt;
+* (with-html-output (*standard-output*)
+ (:red (:b "Bold and red"))
+ (values))
+&lt;font color='red'&gt;&lt;b&gt;Bold and red&lt;/b&gt;&lt;/font&gt;
+* (show-html-expansion (s)
+ (:red :style "spiffy" (if (foo) (htm "Attributes are ignored"))))
+
+(LET ((S S))
+ (PROGN
+ NIL
+ (WRITE-STRING "&lt;font color='red'&gt;" S)
+ (IF (FOO) (PROGN (WRITE-STRING "Attributes are ignored" S)))
+ (WRITE-STRING "&lt;/font&gt;" S)))
+* (defmethod convert-tag-to-string-list ((tag (eql :table)) attr-list body body-fn)
+ (cond ((cdr (assoc :simple attr-list))
+ (nconc (cons "&lt;table"
+ (<a class=noborder href="#convert-attributes">convert-attributes</a> (remove :simple attr-list :key #'car)))
+ (list "&gt;")
+ (loop for row in body
+ collect "&lt;tr&gt;"
+ nconc (loop for col in row
+ collect "&lt;td&gt;"
+ when (constantp col)
+ collect (format nil "~A" col)
+ else
+ collect col
+ collect "&lt;/td&gt;")
+ collect "&lt;/tr&gt;")
+ (list "&lt;/table&gt;")))
+ (t
+ <font color=orange>;; you could as well invoke CALL-NEXT-METHOD here, of course</font>
+ (nconc (cons "&lt;table "
+ (<a class=noborder href="#convert-attributes">convert-attributes</a> attr-list))
+ (list "&gt;")
+ (funcall body-fn body)
+ (list "&lt;/table&gt;")))))
+; Compiling LAMBDA (PCL::.PV-CELL. PCL::.NEXT-METHOD-CALL. TAG ATTR-LIST BODY BODY-FN):
+; Compiling Top-Level Form:
+
+#&lt;STANDARD-METHOD CONVERT-TAG-TO-STRING-LIST ((EQL :TABLE) T T T) {58AFB7CD}&gt;
+* (with-html-output (*standard-output*)
+ (:table :border 0 (:tr (:td "1") (:td "2")) (:tr (:td "3") (:td "4"))))
+&lt;table border='0'&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
+"&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;"
+* (show-html-expansion (s)
+ (:table :simple t :border 0
+ (1 2) (3 (fmt "Result = ~A" (compute-result)))))
+
+(LET ((S S))
+ (PROGN
+ NIL
+ (WRITE-STRING
+ "&lt;table border='0'&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;"
+ S)
+ (FORMAT S "Result = ~A" (COMPUTE-RESULT))
+ (WRITE-STRING "&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;" S)))
+</pre>
+
+</blockquote>
+
+<p><br>[Function]
+<br><a class=none name="convert-attributes"><b>convert-attributes</b></a> <i>attr-list</i> =&gt; <i>strings-or-forms</i>
+
+<blockquote><br>
+
+This is a helper function which can be called from
+<a href="#convert-tag-to-string-list"><code>CONVERT-TAG-TO-STRING-LIST</code></a> to process the list of attributes.
+
+</blockquote>
+
+<br>&nbsp;<br><h3><a class=none name="ack">Acknowledgements</a></h3>
+
+Thanks to Tim Bradshaw and John Foderaro for the inspiration provided
+by their libraries mentioned <a href="#abstract">above</a>. Thanks to
+J&ouml;rg-Cyril H&ouml;hle for his suggestions with respect to
+attribute values. Thanks to Kevin Rosenberg for the LHTML patch.
+Thanks to Stefan Scholl for the 'old school' patch. Thanks to Mac
+Chan for several useful additions.
+
+<p>
+$Header: /usr/local/cvsrep/cl-who/doc/index.html,v 1.68 2009/03/09 21:54:11 edi Exp $
+<p><a href="http://weitz.de/index.html">BACK TO MY HOMEPAGE</a>
+
+</body>
+</html>