ovidiu 02/01/17 10:06:16
Modified: src/scratchpad/schecoon/scheme sitemap.scm
Log:
Get serious: process the SXML representation of the sitemap and
generate an equivalent sitemap function definition.
Revision Changes Path
1.3 +397 -95 xml-cocoon2/src/scratchpad/schecoon/scheme/sitemap.scm
Index: sitemap.scm
===================================================================
RCS file: /home/cvs/xml-cocoon2/src/scratchpad/schecoon/scheme/sitemap.scm,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- sitemap.scm 8 Jan 2002 23:18:52 -0000 1.2
+++ sitemap.scm 17 Jan 2002 18:06:16 -0000 1.3
@@ -4,103 +4,405 @@
;; Date: December 12, 2001
;;
-;; Pipeline definition.
-;;
-;; A pipeline describes a list of operations to be performed usually
-;; on a resource, usually an XML document. A pipeline starts with a
-;; generator, followed by zero or more transformers, and a
-;; serializer. Such a pipeline is used to process XML documents and
-;; generate some other representation of the original document
-;; (usually HTML, WML, PDF, SVG etc.).
-;;
-;; For resources which just need to be passed through without any
-;; modification, the pipeline could have a single step, composed of a
-;; reader.
-;;
-;; To facilitate reuse, pipelines can have names. Pipeline names have
-;; to be unique, or an error is signaled.
-;;
-;; Pipelines are simply functions that accept arguments. These
-;; arguments are usually passed from the sitemap, where they are
-;; computed usually from the HTTP request. These functions should
-;; accept a variable number of arguments. This is because the actual
-;; values for the parameters are generated as result of the pattern
-;; matching.
-;;
-;; Below is an example of how pipelines definition look in
-;; Scheme. Another Scheme module is responsible to mapping the
-;; external XML representation of the pipelines definition into this
-;; internal one.
-;;
-;;(define pipelines
-;; (define-pipelines
-;; (define-pipeline docbook-xhtml (file . rest)
-;; (generate file)
-;; (transform '((type xslt)
-;; (name "docbook2xhtml.xsl")
-;; (parameter "view-source" (concat "docs/samples/" file))))
-;; (serialize (type xml))))
-;;
-;; (define-pipeline gif-image (file . rest)
-;; (read (concat "src/" file ".gif") "image/gif")))
-;; ))
-;;
-;; In the above example, we have two pipelines, `docbook-xhtml' and
-;; `gif-image'. They both take as arguments a file name component,
-;; which is used to generate the initial resource file. Of course, you
-;; can define as many arguments as you want for a pipeline definition
-;; function.
-;;
-;; The `define-pipelines' definition takes such a structure and
-;; returns an associative list whose key is a pipeline name and the
-;; value is the procedure that defines the pipeline.
-
(load-module "sisc.modules.Regexp")
+(load-module "sisc.modules.J2S")
+(load-module "org.apache.cocoon.scheme.sitemap.SchemeSitemap")
-(define define-pipelines
- (lambda l l))
-
-(define-syntax define-pipeline
- (syntax-rules ()
- ((_ name args body ...)
- (cons (quote name)
- (lambda args (begin body ...))))))
-
-;; Sitemap definition
+;; A Cocoon XML sitemap description is processed by the Scheme code in
+;; this file. The processing happens in several steps:
;;
-;; The sitemap specifies how to map URLs to pipelines, or to Scheme
-;; functions, to control the page flow in an application.
+;; - in the first step, the XML sitemap file is translated into an
+;; SXML representation. SXML is the Scheme representation of an XML
+;; tree. This translation happens in an external Java class which uses
+;; Cocoon's XML parser to do the parsing.
+;;
+;; - in the second step, the SXML representation of the sitemap is
+;; translated into Scheme code, which is evaluated. This evaluation
+;; process defines a Scheme function, which is invoked at runtime to
+;; process the HTTP request.
+;;
+;; Here is a sample example. Suppose we have the following XML sitemap:
+;;
+;; <pipelines>
+;; <pipeline>
+;; <match pattern="documentation/(.*).html">
+;; <generate src="docs/{1}.xml" type="file">
+;; <param name="test" value="123"/>
+;; </generate>
+;; <transform src="stylesheets/document2html.xsl">
+;; <param name="test2" value="456"/>
+;; </transform>
+;; <serialize/>
+;; </match>
+;;
+;; <match pattern="sites/images/(.*).gif">
+;; <read src="{1}" mime-type="image/gif"/>
+;; </match>
+;; </pipeline>
+;; </pipelines>
+;;
+;; The SXML representation of the above XML fragment looks like this:
+;;
+;; (pipelines (@ (*line* 1))
+;; (pipeline (@ (*line* 2))
+;; (match (@ (pattern "documentation/(.*).html") (*line* 3))
+;; (generate (@ (src "docs/{1}.xml") (type "file") (*line* 4))
+;; (param (@ (name "test") (value "123") (*line* 5)))
+;; (transform (@ (src "stylesheets/document2html.xsl") (@ (*line* 6)))
+;; (param (@ (name "test2") (value "456") (@ (*line* 7))))
+;; (serialize (@ (*line* 8)))
+;; )
+;;
+;; (match (@ (pattern "sites/images/(.*).gif") (*line* 10))
+;; (read (@ (src "{1}") (mime-type "image/gif") (@ (*line* 11)))))
+;; )))
+;;
+;; The line numbers where an element starts are added by the XML
+;; parser to the SXML representation as attributes of the element.
+;;
+;; The Scheme code translates the above SXML representation in the
+;; following code.
;;
;;(define the-sitemap
-;; (define-sitemap
-;; (match "sql/(.*)" (file . rest)
-;; (call-pipeline docbook-xhtml)))
-;;
-;; (match "slides/(.*)\.gif" (file . rest)
-;; (call-pipeline gif-image))
-;;
-;; (match "view-source/(*).(*)" (file type . rest)
-;; (generate file))
-;; (transform '((type xslt) (name "xsp"))))
-;; (serialize (type xml)))))
-;;
-;; (match "shopping-cart" (dummy . args)
-;; (shopping-cart))
-;; ))
-
-(define-syntax define-sitemap
- (syntax-rules ()
- ((_) #f)
- ((_ m ...)
- (lambda (url sitemap env) (or (m url sitemap env) ...)))))
-
-(define-syntax match
- (syntax-rules ()
- ((_ pattern args body ...)
- (let ((rx (regexp pattern)))
- (lambda (url sitemap env)
- (let ((result (regexp-match rx url)))
- (if result
- (apply (lambda args (begin body ...)) sitemap env (cdr result))
- #f)))))
- ))
+;; (let ((rx1 (regexp "documentation/(.*).html"))
+;; (rx2 (regexp "sites/images/(.*).gif")))
+;; (define (p1 url sitemap env)
+;; (let ((result (regexp-match rx1 url)))
+;; (if result
+;; (apply
+;; (lambda (arg1 . rest)
+;; (sitemap:process
+;; sitemap env '()
+;; (sitemap:serialize
+;; sitemap env '()
+;; (sitemap:transform
+;; sitemap env
+;; (list (cons 'params (list (cons "test" "123")
+;; (cons "test2" "456")))
+;; (cons 'src "stylesheets/document2html.xsl"))
+;; (sitemap:generate
+;; sitemap env
+;; (list (cons 'params (list (cons "test" "123")
+;; (cons "test2" "456")))
+;; (cons 'src (string-append "docs/" arg1 ".xml"))
+;; (cons 'type "file")))))))
+;; (cdr result))
+;; #f)))
+;;
+;; (define (p2 url sitemap env)
+;; (let ((result (regexp-match rx2 url)))
+;; (if result
+;; (apply (lambda (arg1 . rest)
+;; (sitemap:process
+;; sitemap env '()
+;; (sitemap:read
+;; sitemap env
+;; '(("src" . (string-append "" arg1 ""))
+;; ("mime-type" . "image/gif")))))
+;; (cdr result))
+;; #f)))
+;;
+;; (lambda (url sitemap env)
+;; (or (p1 url sitemap env)
+;; (p2 url sitemap env)))))
+;;
+;;
+;; Notice that all the {1}, {2}, ...{n} get expanded in the body of
+;; the generated function. Thus there is no need to do a runtime
+;; replacement of the {n} arguments in the URL string.
+;;
+;; The effect is that `the-sitemap' will be bound to a Scheme function
+;; which, when executed, will process the HTTP request as described in
+;; the original XML sitemap.
+
+
+;; The main function to process an SXML representation of the sitemap,
+;; and generate a function which is the executable version of the
+;; sitemap.
+;;
+;; process-sitemap:: SXML -> (URL Sitemap Env -> #<void>)
+;;
+;; This returned function should be invoked at runtime to process an
+;; HTTP request.
+;;
+;; The side effect of executing this returned function is the
+;; processing of the input HTTP request as defined by the sitemap.
+;;
+(define (process-sitemap sitemap)
+ (let ((exit #f)
+ (arg-regexp (regexp "/({[0-9]+})/"))
+ (number-arg-regexp (regexp "[{}]"))
+ (match-pattern-regexp (regexp "/(\\([^)]+\\))/"))
+ (pattern-regexps-no 0)
+ (pcount 0))
+
+ ;; Print out an error message, showing the line in the XML document
+ ;; where the error occured, if such information is present in the
+ ;; SXML tree.
+ (define (xml-error node message)
+ (let ((line (sxml:attr node '*line*)))
+ (if line
+ (begin (display "In line ") (display line) (display ": ")))
+ (display message) (newline) (exit 'error)))
+
+ ;; Returns the remaining pipeline after the first element has been
+ ;; removed.
+ (define (rest-of-nodes nodelist)
+ ((take-after (lambda (node) #t)) nodelist))
+
+ ;; Takes a string value and replaces in it all occurrences of
+ ;; '{n}', where 'n' is a number, with argN. If such an occurrence
+ ;; is found, the value returned is an expression of this form:
+ ;;
+ ;; "...{n}..." -> (string-append "..." argN "...")
+ ;;
+ ;; If no such occurrence is found, the value is simply returned.
+ (define (expand-value node value)
+ (let* ((exp (regexp-split arg-regexp value))
+ (length (vector-length exp)))
+ (if (eq? length 1)
+ value
+ `(string-append
+ ,@(vector->list
+ (let loop ((index 1))
+ (if (>= index length)
+ exp
+ (let* ((arg (vector-ref exp index))
+ (n (vector-ref (regexp-split/delimiter
+ number-arg-regexp arg) 1)))
+ ;; Check to see if `n' is greater than the
+ ;; maximum number of paranthesised
+ ;; expressions in the original pattern.
+ (if (> (string->number n) pattern-regexps-no)
+ (xml-error node (format "Reference to inexistent regexp
pattern ~a, maximum allowed is ~s" n pattern-regexps-no)))
+ (if (< (string->number n) 1)
+ (xml-error node (format "Regexp pattern argument should
be greater than 1, got ~a" n)))
+ (vector-set! exp index
+ (string->symbol (string-append "arg" n)))
+ (loop (+ index 2)))))))
+ )))
+
+ ;; Collect embedded <param> elements into a list of name/value
+ ;; pairs and return it.
+ (define (get-params elements)
+ (if (eq? elements '())
+ '()
+ (let* ((nodelist ((node-pos 1) elements))
+ (node (if (null? nodelist) '() (car nodelist)))
+ (name (sxml:attr node 'name))
+ (value (sxml:attr node 'value)))
+ (if (null? name)
+ (xml-error node "Attribute 'name' is required in <param>"))
+ (if (null? value)
+ (xml-error node "Attribute 'value' is required in <param>"))
+ (cons `(cons ,name ,(expand-value node value))
+ (get-params (rest-of-nodes elements)))
+ )))
+
+ ;; Obtain the list of required and optional arguments, as well as
+ ;; the parameters, if they are needed.
+ (define (get-attributes node required optional allows-params)
+ (let* ((elem-name (sxml:element-name node))
+ (args '())
+ (params '())
+ (required-attrs
+ (map
+ (lambda (attr-name)
+ (let ((attr (sxml:attr node attr-name)))
+ (if (not attr)
+ (xml-error node
+ (format "'~s' attribute required in <~s>"
+ attr-name elem-name))
+ `(cons ',attr-name ,(expand-value node attr)))))
+ required))
+ (optional-attrs '()))
+ (for-each
+ (lambda (attr-name)
+ (let ((attr (sxml:attr node attr-name)))
+ (if attr
+ (set! optional-attrs
+ (cons `(cons ',attr-name ,(expand-value node attr))
+ optional-attrs)))))
+ optional)
+
+ (if (not (null? required-attrs))
+ (set! args (append args required-attrs)))
+ (if (not (null? optional-attrs))
+ (set! args (append args optional-attrs)))
+ (if allows-params
+ (begin
+ (set! params (get-params (sxml:content node)))
+ (if (not (null? params))
+ (set! args (cons `(cons 'params (list ,@params)) args)))))
+ (if (null? args) `('()) `((list ,@args)))))
+
+ ;; The following match- functions should probably be transformed
+ ;; into a macro, and described at a much higher level than now. A
+ ;; grammar like approach seems appropriate here.
+
+ ;; Translate a <generate> element.
+ (define (match-generate pipeline)
+ (let* ((nodelist ((node-pos 1) pipeline))
+ (node (if (null? nodelist) '() (car nodelist))))
+ (if (not (eq? (sxml:element-name node) 'generate))
+ #f
+ (begin
+ (let ((args (get-attributes node '(src) '(type) #t)))
+ (match-transform
+ (rest-of-nodes pipeline)
+ `(sitemap:generate sitemap env ,@args))
+ )))))
+
+ ;; Translate zero or more <transform> elements
+ (define (match-transform pipeline compfunc)
+ (let* ((nodelist ((node-pos 1) pipeline))
+ (node (if (null? nodelist) '() (car nodelist))))
+ (cond
+ ((eq? (sxml:element-name node) 'transform)
+ (let ((args (get-attributes node '(src) '(type) #t)))
+ (match-transform
+ (rest-of-nodes pipeline)
+ `(sitemap:transform sitemap env ,@args ,compfunc))
+ ))
+ (else (match-serialize pipeline compfunc))
+ )))
+
+ ;; Transform zero or one <serializer> elements
+ (define (match-serialize pipeline compfunc)
+ (let* ((nodelist ((node-pos 1) pipeline))
+ (node (if (null? nodelist) '() (car nodelist))))
+ (cond
+ ;; A serializer has been explicitly defined
+ ((eq? (sxml:element-name node) 'serialize)
+ (let ((args (get-attributes node '() '(type mime-type) #t)))
+ (match-pipeline-end
+ (rest-of-nodes pipeline)
+ `(sitemap:serialize sitemap env ,@args ,compfunc))))
+
+ ;; End of the pipeline with no serializer specified
+ ((eq? node '())
+ `(sitemap:serialize sitemap env ,compfunc))
+
+ ;; Anything else is an error
+ (else
+ (xml-error node "Only <transformer> or <serialize> allowed here"))
+ )))
+
+ ;; Translate a <read> element
+ (define (match-reader pipeline)
+ (let* ((nodelist ((node-pos 1) pipeline))
+ (node (if (null? nodelist) '() (car nodelist))))
+ (cond
+ ((eq? (sxml:element-name node) 'read)
+ (let ((args (get-attributes node '(src) '(type mime-type) #t)))
+ (match-pipeline-end
+ (rest-of-nodes pipeline)
+ `(sitemap:read sitemap env ,@args))
+ ))
+ (else #f)
+ )))
+
+ ;; Make sure nothing follows the pipeline definition
+ (define (match-pipeline-end pipeline compfunc)
+ (let* ((nodelist ((node-pos 1) pipeline))
+ (node (if (null? nodelist) '() (car nodelist))))
+ (if (null? node)
+ compfunc
+ (xml-error node "No element allowed in this context"))))
+
+ ;; The entry point in matching a pipeline. Transforms a pipeline
+ ;; definition into a Scheme function whose body executes the
+ ;; described pipeline.
+ (define (match-pipeline pipeline)
+ (let ((procname (string->symbol (format "p~a" pcount)))
+ (rxname (string->symbol (format "rx~a" pcount))))
+ `(define (,procname url sitemap env)
+ (let ((result (regexp-match ,rxname url)))
+ (if result
+ (apply
+ (lambda ,(let loop ((index 1))
+ (if (> index pattern-regexps-no)
+ 'rest
+ (cons
+ (string->symbol
+ (format "arg~a" (number->string index)))
+ (loop (+ index 1)))))
+ (sitemap:process
+ sitemap env '()
+ ,(or
+ (match-generate pipeline)
+ (match-reader pipeline)
+ (let* ((nodelist ((node-pos 1) pipeline))
+ (node (if (null? nodelist) '() (car nodelist))))
+ (xml-error node "Invalid pipeline definition")))))
+ (cdr result))
+ #f)))
+ ))
+
+ ;; This is the main processing function for a 'match' node in the
+ ;; SXML representation of the sitemap. This function returns an
+ ;; entry like this:
+ ;;
+ ;; (regexp . matcher-function-representation)
+ ;;
+ ;; The `apply-templates' function which invokes `process-match'
+ ;; will collect all these pair and return them in a list.
+ (define (process-match node)
+ ;; Check for the presence of the 'pattern' attribute and signal
+ ;; an error if not present
+ (let ((pattern (sxml:attr node 'pattern)))
+ (if (not pattern)
+ (xml-error
+ node "required 'pattern' attribute for <match> is not present"))
+ ;; Increment the pipelines count
+ (set! pcount (+ pcount 1))
+ ;; Translate the pipeline definitions into equivalent Scheme
+ ;; functions
+ (let ((pipeline (reverse (sxml:child-elements node)))
+ (exp-pattern (regexp-split match-pattern-regexp pattern))
+ (rxname (string->symbol (format "rx~a" pcount))))
+ (set! pattern-regexps-no (/ (- (vector-length exp-pattern) 1) 2))
+ (list (cons `(,rxname (regexp ,pattern))
+ (match-pipeline pipeline)))
+ )))
+
+ ;; Process the SXML representation of the sitemap. This is done by
+ ;; invoking the apply-templates function on the SXML representation
+ ;; of the sitemap.
+ ;;
+ ;; We first setup the exit function, which will be called in case we
+ ;; encounter semantic errors.
+ (call/cc
+ (lambda (k)
+ (set! exit k)
+ (set! pcount 0)
+ ;; `matchers' will contain a list of (regexp
+ ;; . matcher-function). We iterate on it to construct the top
+ ;; level function that represents the sitemap.
+ (let* ((matchers
+ (apply-templates
+ sitemap
+ `((match . ,(lambda (node) (process-match node))))))
+ (sitemap-code
+ `(let ,(let loop ((ms matchers))
+ (if (null? ms)
+ '()
+ (cons (caar ms)
+ (loop (cdr ms)))))
+ ,@(let loop ((ms matchers))
+ (if (null? ms)
+ '()
+ (cons (cdar ms)
+ (loop (cdr ms)))))
+ (lambda (url sitemap env)
+ (or ,@(let loop ((index 1))
+ (if (> index pcount)
+ '()
+ (cons
+ (list (string->symbol (format "p~a" index))
+ 'url 'sitemap 'env)
+ (loop (+ index 1))))))))))
+; (newline) (write sitemap-code) (newline)
+ (eval sitemap-code (interaction-environment))
+ )))
+ ))
----------------------------------------------------------------------
In case of troubles, e-mail: [EMAIL PROTECTED]
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]