From: Neil Jerram <neil@ossau.uklinux.net>
To: "Paul Emsley" <paul.emsley@bioch.ox.ac.uk>
Cc: guile-user@gnu.org
Subject: Re: scheme -> (X)HTML
Date: Tue, 25 Mar 2008 20:38:57 +0000 [thread overview]
Message-ID: <87bq521qu6.fsf@ossau.uklinux.net> (raw)
In-Reply-To: <2bc5f8210803251253p6f07911brcceaa1cc2bf949c1@mail.gmail.com> (Julian Graham's message of "Tue, 25 Mar 2008 15:53:47 -0400")
[-- Attachment #1: Type: text/plain, Size: 663 bytes --]
"Julian Graham" <joolean@gmail.com> writes:
> Hi Paul,
>
> There are several good tools out there for doing this: Oleg Kiselyov
> has written a Scheme-based port of SAX called SSAX [1] that can read
> and emit S-expressions in a format he calls SXML. It's available for
> Guile as part of Andy Wingo's guile-lib [2]. For permissive HTML
> parsing, Neil Van Dyke has written HtmlPrag [3]. And if you're
> interested in a more DOM-based approach, I've got a module called SDOM
> [4].
As a further option, please see the attached. If you're interested in
this, please let me know, because I may have a more up to date version
somewhere.
Regards,
Neil
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: template.scm --]
[-- Type: text/x-scheme, Size: 13401 bytes --]
;;;; (ossau template) -- template file processor
;;; Copyright (C) 2005 Neil Jerram
;;;
;; This library is free software; you can redistribute it and/or
;; modify it under the terms of the GNU Lesser General Public
;; License as published by the Free Software Foundation; either
;; version 2.1 of the License, or (at your option) any later version.
;;
;; This library is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; Lesser General Public License for more details.
;;
;; You should have received a copy of the GNU Lesser General Public
;; License along with this library; if not, write to the Free Software
;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
(define-module (ossau template)
#:use-module (ice-9 rdelim)
#:use-module (ice-9 regex)
#:export (template->code)
#:export-syntax (process-template))
;*****************************************************************************;
;* A template file is a file of content, such as HTML, that is complete *;
;* except for places where the content needs to be filled in *;
;* programmatically. In the case of the template processor code here, the *;
;* code to fill in the dynamic content is written in Scheme and appears *;
;* inline in the template file. *;
;* *;
;* Areas of Scheme code in the template file are delimited by $. For *;
;* example: *;
;* *;
;* <I>This page was processed by Guile $(display (version))$</I> *;
;* *;
;* here (display (version)) is interpreted and processed as Scheme code; *;
;* the rest is normal (HTML) content. *;
;* *;
;* If either normal content or Scheme code needs to include a $ character, *;
;* it can do so by doubling the $, as in: Price $$10.20. *;
;* *;
;* Fragments of Scheme code do not have to be individually balanced. For *;
;* example: *;
;* *;
;* $(for-each (lambda (x)$ *;
;* <LI>The square of $(display x)$ is $(display (* x x))$</LI> *;
;* $ ) (iota 11))$ *;
;* *;
;* A shorthand is provided for cases where a fragment only wants to display *;
;* a variable. This is $~FORMAT VARNAME$, for example $~A x$. ~FORMAT is a *;
;* format specifier understood by (ice-9 format), and VARNAME is the name of *;
;* the variable to display. *;
;* *;
;* It may sometimes help to know the exact algorithm in order to write a *;
;* piece of template file code correctly. It is as follows. *;
;* *;
;* 1. Convert the template file - even the normal content - into a big *;
;* Scheme code string by: *;
;* *;
;* - converting each fragment of normal content to `(display FRAGMENT)' *;
;* *;
;* - converting each `~FORMAT VARNAME' fragment to *;
;* `(format #t ~FORMAT VARNAME)' *;
;* *;
;* - copying other Scheme code fragments as written. *;
;* *;
;* 2. Read and evaluate this string in an environment as specified by the *;
;* arguments to process-template. *;
;* *;
;*****************************************************************************;
;*****************************************************************************;
;* template->code *;
;* *;
;* Reads a template file and returns the Scheme code that should be read and *;
;* evaluated to generate the implied output. *;
;*****************************************************************************;
(define (template->code template)
;***************************************************************************;
;* Utility procedure: convert any occurrences of "$$" in STRING to just *;
;* "$". *;
;***************************************************************************;
(define (unescape-$$ string)
(cond ((string-match "\\$\\$" string)
=>
(lambda (match-data)
(string-append (substring string 0 (match:start match-data 0))
"$"
(unescape-$$ (substring string
(+ (match:start match-data
0)
1))))))
(else string)))
;***************************************************************************;
;* Utility procedure: given a string read from the template file, after *;
;* splitting between scheme and non-scheme parts, return the Scheme code *;
;* corresponding to the template string. *;
;***************************************************************************;
(define (make-code-string template-string in-scheme)
(if in-scheme
;*********************************************************************;
;* Template string should be interpreted as Scheme code. If it *;
;* begins with "~", it is a shorthand for a format expression; *;
;* otherwise, it is straight Scheme code and doesn't need any *;
;* further tweaking. *;
;*********************************************************************;
(cond ((string-match "^~[^ ]+ " template-string)
=>
(lambda (match-data)
(let ((beg (match:start match-data 0))
(end (match:end match-data 0)))
(format #f
"(format #t ~S ~A)"
(substring template-string beg (- end 1))
(substring template-string end)))))
(else template-string))
;*********************************************************************;
;* Template string is normal file content (i.e. outside Scheme *;
;* code). The corresponding Scheme code should display it. *;
;*********************************************************************;
(format #f "(display ~S)" template-string)))
;***************************************************************************;
;* Main procedure code. *;
;***************************************************************************;
(with-input-from-file template
(lambda ()
;***********************************************************************;
;* Loop reading lines from the template file. *;
;***********************************************************************;
(let loop ((template-line (read-line (current-input-port) 'concat))
(in-scheme #f)
(strings '()))
(if (eof-object? template-line)
;*****************************************************************;
;* EOF: return the concatenated Scheme code string. *;
;*****************************************************************;
; (let ((code
(string-append "(begin "
(apply string-append
(reverse strings))
")")
; ))
; (with-output-to-file "template-debug.scm"
; (lambda ()
; (display code)))
; code)
;*****************************************************************;
;* Not yet EOF: normal processing. First check for single "$"; *;
;* these mark the boundaries between Scheme code and normal *;
;* (non-Scheme) file content. *;
;*****************************************************************;
(cond ((string-match "(^|[^$])(\\$)($|[^$])" template-line)
=>
;**********************************************************;
;* Found a single "$", so process the part of the line *;
;* before the "$", then toggle the in-scheme flag and *;
;* loop to process the rest of the line. *;
;**********************************************************;
(lambda (match-data)
(let (($pos (match:start match-data 2)))
(loop (let ((rest (substring template-line (+ $pos 1))))
(if (<= (string-length rest) 1)
(read-line (current-input-port) 'concat)
rest))
(not in-scheme)
(cons (make-code-string (unescape-$$
(substring template-line
0
$pos))
in-scheme)
strings)))))
;***********************************************************;
;* No "$" in this line, so process whole line and loop to *;
;* read the next line. *;
;***********************************************************;
(else
(loop (read-line (current-input-port) 'concat)
in-scheme
(cons (make-code-string (unescape-$$ template-line)
in-scheme)
strings)))))))))
;*****************************************************************************;
;* process-template *;
;* *;
;* Processes a template file, with the generated output going to the current *;
;* output port. Returns unspecified. *;
;* *;
;* Args are: template - Name of template file. *;
;* vars - Variables to define for the Scheme code in the *;
;* template file, in the same form as a set of let *;
;* bindings, i.e. *;
;* ((variable1 value1) *;
;* (variable2 value2) *;
;* ...) *;
;* modules - List of modules that the Scheme code in the *;
;* template file uses. *;
;* *;
;*****************************************************************************;
(define-macro (process-template template vars . modules)
`(let ((module (make-module 31
(map resolve-interface
',modules))))
,@(map (lambda (vardef)
`(module-define! module
',(if (pair? vardef) (car vardef) vardef)
,(if (pair? vardef) (cadr vardef) vardef)))
vars)
(eval (with-input-from-string (template->code ,template) read)
module)))
next prev parent reply other threads:[~2008-03-25 20:38 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2008-03-25 19:37 scheme -> (X)HTML Paul Emsley
2008-03-25 19:53 ` Julian Graham
2008-03-25 20:38 ` Neil Jerram [this message]
2008-06-13 20:53 ` Sebastian Tennant
2008-06-16 21:15 ` Neil Jerram
2008-06-21 9:11 ` Sebastian Tennant
2008-07-03 22:32 ` Neil Jerram
2008-03-31 16:41 ` Paul Emsley
2008-03-31 20:33 ` Francesco Salvestrini
2008-04-02 11:42 ` Thien-Thi Nguyen
2008-03-25 20:49 ` Francesco Salvestrini
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/guile/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87bq521qu6.fsf@ossau.uklinux.net \
--to=neil@ossau.uklinux.net \
--cc=guile-user@gnu.org \
--cc=paul.emsley@bioch.ox.ac.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).