1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
|
<!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> <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
"with-html-output" 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) — 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> <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="#mail">Support and mailing lists</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="#*prologue*"><code>*prologue*</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="#*downcase-tokens-p*"><code>*downcase-tokens-p*</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> <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 '(("http://zappa.com/" . "Frank Zappa")
("http://marcusmiller.com/" . "Marcus Miller")
("http://www.milesdavis.com/" . "Miles Davis"))
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 '(("http://zappa.com/" . "Frank Zappa")
("http://marcusmiller.com/" . "Marcus Miller")
("http://www.milesdavis.com/" . "Miles Davis"))
do (progn
(write-string "<a href='" *http-stream*)
(princ link *http-stream*)
(write-string "'><b>" *http-stream*)
(princ title *http-stream*)
(write-string "</b></a><br />" *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 "right"
(loop for j from i below (+ i 5)
do (<a class=noborder href="#htm">htm</a>
(:td :bgcolor (if (oddp j)
"pink"
"green")
(fmt "~@R" (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 "<table border='0' cellpadding='4'>" *http-stream*)
(loop for i below 25 by 5
do (progn
(write-string "<tr align='right'>" *http-stream*)
(loop for j from i below (+ i 5)
do (progn
(write-string "<td bgcolor='" *http-stream*)
(princ (if (oddp j) "pink" "green") *http-stream*)
(write-string "'>" *http-stream*)
(format *http-stream* "~@r" (1+ j))
(write-string "</td>" *http-stream*)))
(write-string "</tr>" *http-stream*)))
(write-string "</table>" *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ête</p><p style='background-color:orange'>Sørensen</p><p style='background-color:blue'>naïve</p><p style='background-color:red'>Hühner</p><p style='background-color:orange'>Straß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
"<h4>Look at the character entities generated by this example</h4>"
*http-stream*)
(loop for i from 0 for string in '("Fête" "Sørensen" "naïve" "Hühner" "Straße")
do (progn
(write-string "<p style='" *http-stream*)
(princ (<a class=noborder href="#conc">conc</a> "background-color:"
(case (mod i 3)
((0) "red")
((1) "orange")
((2) "blue")))
*http-stream*)
(write-string "'>" *http-stream*)
(progn (write-string (<a class=noborder href="#escape-string">escape-string</a> string) *http-stream*))
(write-string "</p>" *http-stream*)))))
</pre></td>
</tr>
</table>
<br> <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 0.2.0.
<p>
The preferred method to compile and load CL-WHO is via <a href="http://www.cliki.net/asdf">ASDF</a>.
<p>
If you're on <a href="http://www.debian.org/">Debian</a> you can
probably use
the <a
href="http://packages.debian.org/cgi-bin/search_packages.pl?keywords=cl-who&searchon=names&version=all&release=all">cl-who
Debian package</a> which is available thanks to Kevin
Rosenberg. There's also a port
for <a
href="http://www.gentoo.org/proj/en/common-lisp/index.xml">Gentoo
Linux</a> thanks to Matthew Kennedy. In both cases, check if they have the newest version available.
<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í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> <br><h3><a name="mail" class=none>Support and mailing lists</a></h3>
For questions, bug reports, feature requests, improvements, or patches
please use the <a
href="http://common-lisp.net/mailman/listinfo/cl-who-devel">cl-who-devel
mailing list</a>. If you want to be notified about future releases
subscribe to the <a
href="http://common-lisp.net/mailman/listinfo/cl-who-announce">cl-who-announce
mailing list</a>. These mailing lists were made available thanks to
the services of <a href="http://common-lisp.net/">common-lisp.net</a>.
<p>
If you want to send patches, please <a href="http://weitz.de/patches.html">read this first</a>.
<br> <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>"foo" <font color="red">=></font> (write-string "foo" s)</pre></td></tr></table>
(Here and for the rest of this document the <em>red arrow</em> means '... will be converted to code equivalent to ...' where <em>equivalent</em> means that all output is sent to the "right" 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">=></font> (write-string "<br />" 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">=></font> (write-string "<br>" 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 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 "red") <font color="red">=></font> (write-string "<td bgcolor='red' />" 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">=></font> (write-string "<td nowrap='nowrap' />" s)</pre></td></tr></table>
With <a href="#html-mode"><code>HTML-MODE</code></a> set to <code>:SGML</code>:
<table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(:td :nowrap t) <font color="red">=></font> (write-string "<td nowrap>" s)</pre></td></tr></table>
<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">=></font> (write-string "<td />" 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>"~A"</code> at macro expansion time.
<table border=0 cellpadding=2 cellspacing=3><tr><td><pre>(:table :border 3) <font color="red">=></font> (write-string "<table border='3' />" 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">=></font> (write-string "<table border='" s)
(princ (+ 1 2) s)
(write-string "' />" 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">=></font> (write-string "<table border='0' cellpadding='5' cellspacing='5' />" 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 "Paragraph") <font color="red">=></font> (write-string "<p>Paragraph</p>" s)
(:p :class "foo" "Paragraph") <font color="red">=></font> (write-string "<p class='foo'>Paragraph</p>" s)
(:p :class "foo" "One" " " "long" " " "sentence") <font color="red">=></font> (write-string "<p class='foo'>One long sentence</p>" s)
(:p :class "foo" "Visit " (:a :href "http://www.cliki.net/" "CLiki"))
<font color="red">=></font> (write-string "<p class='foo'>Visit <a href='http://www.cliki.net/'>CLiki</a></p>" s)</pre></td></tr></table>
<li>Beginning with <a href="#install">version 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) "Paragraph") <font color="red">=></font> (write-string "<p>Paragraph</p>" s)
((:p :class "foo") "Paragraph") <font color="red">=></font> (write-string "<p class='foo'>Paragraph</p>" s)
((:p :class "foo" :name "humpty-dumpty") "One" " " "long" " " "sentence")
<font color="red">=></font> (write-string "<p class='foo' name='humpty-dumpty'>One long sentence</p>" s)
((:p :class "foo") "Visit " ((:a :href "http://www.cliki.net/") "CLiki"))
<font color="red">=></font> (write-string "<p class='foo'>Visit <a href='http://www.cliki.net/'>CLiki</a></p>" s)</pre></td></tr></table>
</ul>
Here's a slightly more elaborate example:
<pre>
* (defun checkbox (stream name checked &optional value)
(with-html-output (stream)
(:input :type "checkbox" :name name :checked checked :value value)))
CHECKBOX
* (with-output-to-string (s) (checkbox s "foo" t))
"<input type='checkbox' name='foo' checked='checked' />"
* (with-output-to-string (s) (checkbox s "foo" nil))
"<input type='checkbox' name='foo' />"
* (with-output-to-string (s) (checkbox s "foo" nil "bar"))
"<input type='checkbox' name='foo' value='bar' />"
* (with-output-to-string (s) (checkbox s "foo" t "bar"))
"<input type='checkbox' name='foo' checked='checked' value='bar' />"
</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">=></font> (write-string "<hr />" 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">=></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">=></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 "foo") :br))
<font color="red">=></font> (loop for i below 100 do (progn (write-string "<b>foo</b><br />" 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> <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>&optional</tt> stream <tt>&key</tt> prologue indent) declaration* form*</i> => <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>. 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>. (But note that
indentation might change the semantics of the generated HTML. This is
for example the case for the <code>PRE</code>
and <code>TEXTAREA</code> tags, and in certain situations additional
whitespace might also change the layout of tables.)
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> are used at macro expansion time.
<pre>
* (with-html-output (*standard-output* nil :prologue t)
(:html (:body "Not much there"))
(values))
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html><body>Not much there</body></html>
* (with-html-output (*standard-output*)
(:html (:body :bgcolor "white"
"Not much there"))
(values))
<html><body bgcolor='white'>Not much there</body></html>
* (with-html-output (*standard-output* nil :prologue t :indent t)
(:html (:body :bgcolor "white"
"Not much there"))
(values))
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<body bgcolor='white'>
Not much there
</body>
</html>
</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>&optional</tt> string-form <tt>&key</tt> element-type prologue indent) declaration* form*</i> => <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 &optional string-form
&key (element-type ''character)
prologue
indent)
&body body)
"Transform the enclosed BODY consisting of HTML as s-expressions
into Lisp code which creates the corresponding HTML as a string."
`(with-output-to-string (,var ,string-form :elementy-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>#\"</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>"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"</pre>
</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><tag/></code> (XHTML mode) or <code><tag></code> (SGML mode or HTML mode). For
all other tags, it will always generate <code><tag></tag></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="*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>[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 (HTML-MODE) :SGML)</code> to pre-XML HTML mode or <code>(SETF (HTML-MODE) :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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"></pre>
Code generation in SGML HTML is slightly different from XHTML - there's no need to end empty elements with <code>/></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><!DOCTYPE html></pre>
</blockquote>
<p><br>[Function]
<br><a class=none name="escape-string"><b>escape-string</b></a> <i>string <tt>&key</tt> test</i> => <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 "<Hühner> 'naïve'")
"&lt;H&#xFC;hner&gt; &#x27;na&#xEF;ve&#x27;"
* (with-html-output-to-string (s)
(:b (esc "<Hühner> 'naïve'")))
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"<b>&lt;H&#xFC;hner&gt; &#x27;na&#xEF;ve&#x27;</b>"
</pre>
</blockquote>
<p><br>[Function]
<br><a class=none name="escape-char"><b>escape-char</b></a> <i>character <tt>&key</tt> test</i> => <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 "<>&'\"")
(> (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> => <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> => <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> => <i>escaped-string</i></a>
<br>[Function]
<br><a class=none name="escape-string-all"><b>escape-string-all</b> <i>string</i> => <i>escaped-string</i></a>
<br>[Function]
<br><a class=none name="escape-char-minimal"><b>escape-char-minimal</b> <i>character</i> => <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> => <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> => <i>escaped-string</i></a>
<br>[Function]
<br><a class=none name="escape-char-all"><b>escape-char-all</b> <i>character</i> => <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)
"Escape only #\<, #\>, and #\& in STRING."
(escape-string string :test #'(lambda (char) (find char "<>&"))))
(defun escape-string-minimal-plus-quotes (string)
"Like ESCAPE-STRING-MINIMAL but also escapes quotes."
(escape-string string :test #'(lambda (char) (find char "<>&'\""))))
(defun escape-string-iso-8859-1 (string)
"Escapes all characters in STRING which aren't defined in ISO-8859-1."
(escape-string string :test #'(lambda (char)
(or (find char "<>&'\"")
(> (char-code char) 255)))))
(defun escape-string-all (string)
"Escapes all characters in STRING which aren't in the 7-bit ASCII
character set."
(escape-string string :test #'(lambda (char)
(or (find char "<>&'\"")
(> (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>&rest</tt> string-list</i> => <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 "This" " " "is" " " "a" " " "sentence")
"This is a sentence"
* (with-html-output-to-string (s)
(:div :style (conc "padding:"
(format nil "~A" (+ 3 2)))
"Foobar"))
"<div style='padding:5'>Foobar</div>"
</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> => <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 "<font color='red'>" (funcall body-fn body)) (list "</font>")))
; Compiling LAMBDA (PCL::.PV-CELL. PCL::.NEXT-METHOD-CALL. TAG ATTR-LIST BODY BODY-FN):
; Compiling Top-Level Form:
#<STANDARD-METHOD CONVERT-TAG-TO-STRING-LIST ((EQL :RED) T T T) {582B268D}>
* (with-html-output (*standard-output*)
(:red (:b "Bold and red"))
(values))
<font color='red'><b>Bold and red</b></font>
* (show-html-expansion (s)
(:red :style "spiffy" (if (foo) (htm "Attributes are ignored"))))
(LET ((S S))
(PROGN
NIL
(WRITE-STRING "<font color='red'>" S)
(IF (FOO) (PROGN (WRITE-STRING "Attributes are ignored" S)))
(WRITE-STRING "</font>" S)))
* (defmethod convert-tag-to-string-list ((tag (eql :table)) attr-list body body-fn)
(cond ((cdr (assoc :simple attr-list))
(nconc (cons "<table"
(<a class=noborder href="#convert-attributes">convert-attributes</a> (remove :simple attr-list :key #'car)))
(list ">")
(loop for row in body
collect "<tr>"
nconc (loop for col in row
collect "<td>"
when (constantp col)
collect (format nil "~A" col)
else
collect col
collect "</td>")
collect "</tr>")
(list "</table>")))
(t
<font color=orange>;; you could as well invoke CALL-NEXT-METHOD here, of course</font>
(nconc (cons "<table "
(<a class=noborder href="#convert-attributes">convert-attributes</a> attr-list))
(list ">")
(funcall body-fn body)
(list "</table>")))))
; Compiling LAMBDA (PCL::.PV-CELL. PCL::.NEXT-METHOD-CALL. TAG ATTR-LIST BODY BODY-FN):
; Compiling Top-Level Form:
#<STANDARD-METHOD CONVERT-TAG-TO-STRING-LIST ((EQL :TABLE) T T T) {58AFB7CD}>
* (with-html-output (*standard-output*)
(:table :border 0 (:tr (:td "1") (:td "2")) (:tr (:td "3") (:td "4"))))
<table border='0'><tr><td>1</td><td>2</td></tr><tr><td>3</td><td>4</td></tr></table>
"</td></tr></table>"
* (show-html-expansion (s)
(:table :simple t :border 0
(1 2) (3 (fmt "Result = ~A" (compute-result)))))
(LET ((S S))
(PROGN
NIL
(WRITE-STRING
"<table border='0'><tr><td>1</td><td>2</td></tr><tr><td>3</td><td>"
S)
(FORMAT S "Result = ~A" (COMPUTE-RESULT))
(WRITE-STRING "</td></tr></table>" S)))
</pre>
</blockquote>
<p><br>[Function]
<br><a class=none name="convert-attributes"><b>convert-attributes</b></a> <i>attr-list</i> => <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> <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örg-Cyril Hö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>
|