From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Neil Jerram Newsgroups: gmane.lisp.guile.user Subject: Re: scheme -> (X)HTML Date: Tue, 25 Mar 2008 20:38:57 +0000 Message-ID: <87bq521qu6.fsf@ossau.uklinux.net> References: <20080325193702.6B6AE94049@webmail220.herald.ox.ac.uk> <2bc5f8210803251253p6f07911brcceaa1cc2bf949c1@mail.gmail.com> NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: ger.gmane.org 1206477606 6479 80.91.229.12 (25 Mar 2008 20:40:06 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Tue, 25 Mar 2008 20:40:06 +0000 (UTC) Cc: guile-user@gnu.org To: "Paul Emsley" Original-X-From: guile-user-bounces+guile-user=m.gmane.org@gnu.org Tue Mar 25 21:40:33 2008 Return-path: Envelope-to: guile-user@m.gmane.org Original-Received: from lists.gnu.org ([199.232.76.165]) by lo.gmane.org with esmtp (Exim 4.50) id 1JeFwc-0006GR-ME for guile-user@m.gmane.org; Tue, 25 Mar 2008 21:40:31 +0100 Original-Received: from localhost ([127.0.0.1] helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1JeFw1-0003Dd-Bg for guile-user@m.gmane.org; Tue, 25 Mar 2008 16:39:53 -0400 Original-Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1JeFvG-0002Zy-5B for guile-user@gnu.org; Tue, 25 Mar 2008 16:39:06 -0400 Original-Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1JeFvF-0002ZK-7R for guile-user@gnu.org; Tue, 25 Mar 2008 16:39:05 -0400 Original-Received: from [199.232.76.173] (helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1JeFvF-0002Z7-0q for guile-user@gnu.org; Tue, 25 Mar 2008 16:39:05 -0400 Original-Received: from mail3.uklinux.net ([80.84.72.33]) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1JeFvE-00044x-6H for guile-user@gnu.org; Tue, 25 Mar 2008 16:39:04 -0400 Original-Received: from arudy (host86-145-183-175.range86-145.btcentralplus.com [86.145.183.175]) by mail3.uklinux.net (Postfix) with ESMTP id 68AED1F6955; Tue, 25 Mar 2008 20:38:58 +0000 (GMT) Original-Received: from laruns (laruns [192.168.0.10]) by arudy (Postfix) with ESMTP id 5DC003800A; Tue, 25 Mar 2008 20:38:57 +0000 (GMT) In-Reply-To: <2bc5f8210803251253p6f07911brcceaa1cc2bf949c1@mail.gmail.com> (Julian Graham's message of "Tue, 25 Mar 2008 15:53:47 -0400") User-Agent: Gnus/5.110006 (No Gnus v0.6) Emacs/21.4 (gnu/linux) X-detected-kernel: by monty-python.gnu.org: Linux 2.4-2.6 X-BeenThere: guile-user@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: General Guile related discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Original-Sender: guile-user-bounces+guile-user=m.gmane.org@gnu.org Errors-To: guile-user-bounces+guile-user=m.gmane.org@gnu.org Xref: news.gmane.org gmane.lisp.guile.user:6488 Archived-At: --=-=-= "Julian Graham" 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 --=-=-= Content-Type: text/x-scheme Content-Disposition: attachment; filename=template.scm ;;;; (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: *; ;* *; ;* This page was processed by Guile $(display (version))$ *; ;* *; ;* 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)$ *; ;*
  • The square of $(display x)$ is $(display (* x x))$
  • *; ;* $ ) (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))) --=-=-=--