From: Frank Buss
Subject: Another HTML generation macro
Date: 
Message-ID: <46n0a7ki1wzt$.y7bwb5wd1uk9$.dlg@40tude.net>
I've implemented a first version of my HTML generator. It is a reader macro
and like "#|" and "|#" for comments, it uses "#[" and "]#". My first idea
was to use "#<" for starting a HTML block and ">" for ending it, but then I
can't use HTML fragments, which are not enclosed in tags.

Unlike other generators, you can use text without quotes, which simplifies
translating normal web pages with much text. Some examples:

(defun footer (email)
  #[<hr>
    <p <a :href (format nil "mailto:~A" email)
          <img :src "img/email.gif">>
    <adress 2005, Frank Bu�>>]#)

(defun test-with-footer ()
  #[<html
      <head <title A Test>>
      <body
        <p Just a <bold test><br>
           1 + 2 = #(princ (+ 1 2))<br>
           And a link: <a :href "page.html" The page>.>
        #(footer ···@frank-buss.de")>>]#)

(defun test-recursive ()
  #[<table :border 1
      <tr <th i> <th 2^i>>
      #(loop for i from 1 to 3 do
        #[<tr
            <td #(princ i)>
            <td #(princ (expt 2 i))>>]#)>]#)

The output (formatted for better reading) :

(test-with-footer) ->

<html>
<head>
<title>A Test</title>
</head>
<body>
<p>Just a <bold>test</bold><br>
  1 + 2 = 3<br>
  And a link: <a href="page.html">The page</a>.
</p>
<hr>
<p><a ···············@frank-buss.de"><img src="img/email.gif"></a>
  <adress>2005, Frank Bu�</adress>
</p>
</body>
</html>

(test-recursive) ->

<table border="1">
  <tr>
    <th>i</th>
    <th>2^i</th>
  </tr>
  <tr>
    <td>1</td>
    <td>2</td>
  </tr>
  <tr>
    <td>2</td>
    <td>4</td>
  </tr>
  <tr>
    <td>3</td>
    <td>8</td>
  </tr>
</table>

TODOs:

- special character handling, for example if I want to include the text
"]#". I think this should be written like "\]#"

- escaping HTML characters, like "&" -> "&amp;"

- the implementation generates too much "progn"s and "princ"s

- the read functions are not very fast and perhaps could be simplified

- it should be possible to generate pretty-printed output

- it should be possible to generate compact output (without any redundant
whitespace)

- a reverse-function for converting normal HTML syntax to my syntax


For reading the text with preserving upper/lower case I tweaked the
readtable, but I don't know if I have done it the right way.


(defparameter *normal-readtable* *readtable*)
(defparameter *html-readtable* (copy-readtable *readtable*))
(setf (readtable-case *html-readtable*) :preserve)

(defun read-attributes (s) 
  (let ((result nil))
    (loop
     (let ((c (peek-char t s nil nil t)))
       (if (char= #\: c)
           (let ((name (read s nil nil)))
             (let ((*readtable* *normal-readtable*))
               (let ((value (read s nil nil t)))
                 (push (cons name value) result))))
         (return (nreverse result)))))))

(defun read-html (s char arg)
  (declare (ignore char arg))
  (let ((*readtable* *html-readtable*))
    (let ((result nil)
          (token nil))
      (labels ((push-token ()
                 (when (> (length token) 0)
                   (push (list 'princ
                               (nreverse (coerce token 'string))) result)
                   (setf token nil)))
               (get-result ()
                 (push-token)
                 (when (> (length result) 0)
                   `(progn ,@(nreverse result)))))
        (loop
         (let ((c (read-char s nil nil t)))
           (cond ((or (eql nil c) (char= #\> c))
                  (return (get-result)))
                 ((char= #\] c)
                  (let ((c2 (peek-char nil s nil nil t)))
                    (if (char= c2 #\#)
                        (progn
                          (read-char s nil nil t)
                          (return (get-result)))
                      (push c result))))
                 ((char= #\< c)
                  (push-token)
                  (push (read-tag s) result))
                 ((char= #\# c)
                  (let ((c2 (peek-char nil s nil nil t)))
                    (if (char= c2 #\()
                        (let ((*readtable* *normal-readtable*))
                          (push-token)
                          (push (read s nil nil t) result))
                      (push c result))))
                 (t (push c token)))))))))

(defun read-tag-name (s)
  (let ((result nil))
    (loop
     (let ((c (peek-char nil s nil nil t)))
       (if (and c (alpha-char-p c))
           (push (read-char s nil nil t) result)
         (return (nreverse (coerce result 'string))))))))

(defun read-tag (s)
  (let ((tag-name (read-tag-name s))
        (attributes (read-attributes s))
        (html (read-html s nil nil)))
    `(progn
       (princ ,(format nil "<~A" tag-name))
       ,@(loop for (name . value) in attributes collect
               `(format t ,(format nil " ~A=\"~~A\"" name) ,value))
       (princ ">")
       ,@(when (> (length html) 0)
           (list `(progn ,html)
                 `(princ ,(format nil "</~A>" tag-name)))))))

(set-dispatch-macro-character #\# #\[ #'read-html)


-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de

From: André Thieme
Subject: Re: Another HTML generation macro
Date: 
Message-ID: <d75oa5$o7m$3@ulric.tng.de>
How do you want to call your macro?
PHlisP? ;-)


Andr�
-- 
From: Frank Buss
Subject: Re: Another HTML generation macro
Date: 
Message-ID: <yf10q7rvk6an$.8snymonqzlby$.dlg@40tude.net>
Andr� Thieme wrote:

> How do you want to call your macro?
> PHlisP? ;-)

you are right, it is very good for fast hacking web pages with some Lisp
embedded in it, but it is not limited to this, but can be used for all
other applications which needs to generate HTML from Lisp, too. Perhaps
LSP, Lisp Server Pages, is a good name :-)

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Frank Buss
Subject: Re: Another HTML generation macro
Date: 
Message-ID: <1nmmcyw4qcsok.1gnldu4lfzrx8$.dlg@40tude.net>
Frank Buss wrote:

> Perhaps LSP, Lisp Server Pages, is a good name :-)

looks like there is already Lisp Server pages and it looks as awkward as
JSP and PHP, because of interleaving "(" and ")" with the escape char
sequence:

<% (dotimes (i 10) %>
  <img src="mr-yuck.jpg">
<% ) %>

which is in my language:

(dotimes (i 10)
  #[<img :src "mr-yuck.jpg">]#)

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: ·······@gmail.com
Subject: Re: Another HTML generation macro
Date: 
Message-ID: <1117171026.840427.81410@o13g2000cwo.googlegroups.com>
There's gotta be a better way, that syntax is still kinda messy, but I
applaud the effort. I definitely like the idea of generating the entire
page in the code instead of little scripting bits in the HTML.
From: Frank Buss
Subject: Re: Another HTML generation macro
Date: 
Message-ID: <1busio3720l3t.10ukp1y1kdpfi$.dlg@40tude.net>
·······@gmail.com wrote:

> There's gotta be a better way, that syntax is still kinda messy, but I
> applaud the effort. 

Thanks. How do you think the syntax could be improved? I think it depends
on the requirements, see the thread "HTML reader macro" where I developed
the syntax with the help of others. My requirements are:

- some HTML fragments embeddable in larger Lisp programs
- some Lisp fragments embeddable in larger HTML pages
- no quotes for text
- as compact as possible, without being cryptic
- no interleaving escaping and language parentheses, like in PHP and JSP

But I don't think that my solution is the best possible for my
requirements, so if you have some ideas how to make it less messy, please
post it.

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Daniel Lowe
Subject: Re: Another HTML generation macro
Date: 
Message-ID: <1117199887.547539.54750@f14g2000cwb.googlegroups.com>
You might want to look at BTL, the common lisp analogue to BRL:

http://www.sanctuary.org/~azimuth/code/btl.html

I use it for most of my HTML templating needs.
From: Frank Buss
Subject: Re: Another HTML generation macro
Date: 
Message-ID: <130dammsdao1u$.szu4gvbey2ql$.dlg@40tude.net>
Daniel Lowe wrote:

> You might want to look at BTL, the common lisp analogue to BRL:
> 
> http://www.sanctuary.org/~azimuth/code/btl.html
> 
> I use it for most of my HTML templating needs.

this looks like plain HTML, mixed with Lisp. Why using the redundant
"<tag>...</tag>" syntax? I don't like it.

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: ·················@gmail.com
Subject: Re: Another HTML generation macro
Date: 
Message-ID: <1117202583.153197.304670@g14g2000cwa.googlegroups.com>
Have you checked SXML? (http://okmij.org/ftp/Scheme/SXML.html)
I use it to generate HTML from Scheme. For instance, to generate a
table from a list of lists:

(define (list-table->sxml-table ls)
   `(table ,@(map (lambda (row)
       `(tr ,@(map (lambda (col)
         `(td ,col)) row))) ls)))

> (list-table->sxml-table '((France Paris) (Italy Rome) (Spain Madrid)))
(table
       (tr (td France) (td Paris))
       (tr (td Italy) (td Rome))
       (tr (td Spain) (td Madrid)))

which renders to the obvious:

<table>
<tr><td>France</td>
<td>Paris</td>
</tr>
<tr><td>Italy</td>
<td>Rome</td>
</tr>
<tr><td>Spain</td>
<td>Madrid</td>
</tr>
</table>

                       Michele Simionato
From: Frank Buss
Subject: Re: Another HTML generation macro
Date: 
Message-ID: <1xu1iifbr9061$.1spkxaahe2dyj.dlg@40tude.net>
·················@gmail.com wrote:

> Have you checked SXML? (http://okmij.org/ftp/Scheme/SXML.html)

looks interesting. I like my syntax for writing HTML, but perhaps my reader
macro should create an abstract syntax tree, too, instead of generating
print calls. For example this line:

#[<table :border "1" <tr <td foo> <td bar>>>]#

could be translated to this line:

(html-table :border "1" (html-tr "foo") (html-tr "bar"))

Then I can translate your list-table->sxml-table to a Lisp macro:

(defmacro list-table (table)
  `(html-table :border "1" ,@(mapcar #'(lambda (row) 
     `(html-tr ,@(mapcar #'(lambda (col)
         `(html-td ,col)) row))) table)))

(macroexpand '(list-table (("France" "Paris") ("Italy" "Rome") ("Spain"
"Madrid"))))
->
(TABLE :BORDER "1"
       (HTML-TR (HTML-TD "France")
                (HTML-TD "Paris"))
       (HTML-TR (HTML-TD "Italy")
                (HTML-TD "Rome"))
       (HTML-TR (HTML-TD "Spain")
                (HTML-TD "Madrid")))

assuming that all html tags are defined as html-[tag-name]. With this
concept I can even add my own special tags as functions.

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: ·················@gmail.com
Subject: Re: Another HTML generation macro
Date: 
Message-ID: <1117261200.158892.14650@g43g2000cwa.googlegroups.com>
What disturbs me of your approach is that you are using a macro where I
would just use a function operating on lists.
         
                            Michele Simionato
From: Frank Buss
Subject: Re: Another HTML generation macro
Date: 
Message-ID: <3vopkm6yjdti$.12ypm0usqezlj.dlg@40tude.net>
·················@gmail.com wrote:

> What disturbs me of your approach is that you are using a macro where I
> would just use a function operating on lists.

my representation of the web page is always an active program, otherwise it
would be difficult to integrate loops:

  #[<table :border 1
      <tr <th i> <th 2^i>>
      #(loop for i from 1 to 3 do
        #[<tr
            <td #(princ i)>
            <td #(princ (expt 2 i))>>]#)>]#

I don't know how SXML handles this, but even if you could embed Scheme
expressions in it, which are evaluated and returns SXML, this could contain
another Scheme expression, so you need a multi pass evaluation, while in my
concept it is automaticly compiled to print statements (I think for my
extension with the intermediate syntax tree, the html-* functions needs to
be macros, too).

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Pascal Bourguignon
Subject: Re: Another HTML generation macro
Date: 
Message-ID: <877jhjfv1u.fsf@thalassa.informatimago.com>
Frank Buss <··@frank-buss.de> writes:

> ·················@gmail.com wrote:
>
>> What disturbs me of your approach is that you are using a macro where I
>> would just use a function operating on lists.
>
> my representation of the web page is always an active program, otherwise it
> would be difficult to integrate loops:
>
>   #[<table :border 1
>       <tr <th i> <th 2^i>>
>       #(loop for i from 1 to 3 do
>         #[<tr
>             <td #(princ i)>
>             <td #(princ (expt 2 i))>>]#)>]#
>
> I don't know how SXML handles this, but even if you could embed Scheme
> expressions in it, which are evaluated and returns SXML, this could contain
> another Scheme expression, so you need a multi pass evaluation, while in my
> concept it is automaticly compiled to print statements (I think for my
> extension with the intermediate syntax tree, the html-* functions needs to
> be macros, too).

Well, if one insists, the macros could generate function calls with
the bodies embedded into closures.

(defmacro table (attributes &body body)
   `(gen-table (list ,@attributes) (lambda () ,@body)))

(defmacro tr (attributes &body body)
   `(gen-tr (list ,@attributes) (lambda () ,@body)))

Of course, you'd write a macro (or a function) to generate them...

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
The rule for today:
Touch my tail, I shred your hand.
New rule tomorrow.