ovidiu 02/01/25 14:59:29 Modified: src/scratchpad/schecoon/scheme sitemap.scm Log: Added support for defining and referencing resources, and invoking Scheme functions directly. Revision Changes Path 1.7 +319 -86 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.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- sitemap.scm 19 Jan 2002 02:13:19 -0000 1.6 +++ sitemap.scm 25 Jan 2002 22:59:29 -0000 1.7 @@ -1,4 +1,4 @@ -;; Sitemap definitions +;; Cocoon sitemap translator ;; ;; Author: Ovidiu Predescu <[EMAIL PROTECTED]> ;; Date: December 12, 2001 @@ -67,7 +67,7 @@ ;; ;;(define the-sitemap ;; (let ((rx1 (regexp "documentation/(.*).html")) -;; (rx2 (regexp "sites/images/(.*).gif"))) +;; (rx2 (regexp "sites/images/(.*).gif"))) ;; (define (p1 url sitemap env) ;; (let ((result (regexp-match rx1 url))) ;; (if result @@ -117,6 +117,13 @@ ;; which, when executed, will process the HTTP request as described in ;; the original XML sitemap. +;; Converts a name to a resource. `name' is either a string or a +;; symbol. The return value is r_<name> and is of the same type as +;; `name'. +(define (name->resource name) + (cond ((string? name) (string-append "r_" name)) + ((symbol? name) (name->resource (symbol->string name))) + (else #f))) ;; The main function to process an SXML representation of the sitemap, ;; and generate a function which is the executable version of the @@ -132,11 +139,67 @@ ;; (define (process-sitemap sitemap) (let ((exit #f) - (arg-regexp (regexp "/({[0-9]+})/")) - (number-arg-regexp (regexp "[{}]")) + ;; Regular expressions for matching various types of arguments + (res-arg-regexp (regexp "/({[^0-9].*})/")) + (res-arg-regexp-match (regexp "{[^0-9].*}")) + (num-arg-regexp (regexp "/({[0-9]+})/")) + (arg-regexp-split (regexp "[{}]")) (match-pattern-regexp (regexp "/(\\([^)]+\\))/")) + + ;; The number of paranthesised groups in the current pattern + ;; being analyzed by match-match. (pattern-regexps-no 0) - (pcount 0)) + + ;; The number of pipelines analyzed so far + (pcount 0) + + ;; Identifies all the <map:resource name="..."> elements. It + ;; is a list of pairs, with the car being the function + ;; signature, and the cons being the function body definition: + ;; + ;; ((function-name arguments ...) . function-definition-code) + ;; + ;; This is used in the translating the calls to the pipeline + ;; resources defined. + (resources '())) + + ;; Search for a resource whose name is `name'. Returns the cons + ;; entry in the `resources' store, which holds the function + ;; signature and the function body of the resource. #f is returned + ;; if no resource `name' is found. + (define (lookup-resource name) + (let loop ((resources resources)) + (if (null? resources) + #f + (let ((elem (caar resources))) + (if (eq? elem name) + (car resources) + (loop (cdr resources))))))) + + ;; Adds a new resource to the available resources. No check is + ;; done to see if a similarly named resource exists. + (define (add-resource funsig funbody) + (set! resources (cons (cons funsig funbody) resources))) + + ;; Return the signature of the resource named `name' or #f if no + ;; such resource exits. + (define (resource-function-signature name) + (let ((resource (lookup-resource name))) + (if resource + (car resource) + #f))) + + ;; Return the body of the resource named `name' or #f if no such + ;; resource exists. + (define (resource-function-body name) + (let ((resource (lookup-resource name))) + (if resource + (cdr resource) + #f))) + + ;; Return a list of all the function bodies. + (define (resources-get-function-bodies) + (map (lambda (res) (cdr res)) resources)) ;; Print out an error message, showing the line in the XML document ;; where the error occured, if such information is present in the @@ -153,40 +216,64 @@ ((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}', where 'n' is required to be either a number or a + ;; name. If `n' is a number, `{n} will be replace with `argN', + ;; which stands for the function argument with the same name. If + ;; `n' is a name, `{n}' is replaced with `n', which also stands + ;; for the function argument with the same name. If such an + ;; occurrence is found, the value returned is an expression of + ;; this form: ;; ;; "...{n}..." -> (string-append "..." argN "...") ;; + ;; or + ;; + ;; "...{n}..." -> (string-append "..." n "...") + ;; ;; 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))))))) - ))) + (define (expand-value node value args-are-numbers?) + (let* ((rx (if args-are-numbers? num-arg-regexp res-arg-regexp)) + (exp (filter (lambda (x) (if (equal? x "") #f x)) + (vector->list (regexp-split rx value)))) + (exp2 + (reverse + (let loop ((lst exp) (acc '())) + (if (null? lst) + acc + (let* ((arg (car lst)) + (split-arg (regexp-split/delimiter + arg-regexp-split arg)) + (n (if (> (vector-length split-arg) 1) + (vector-ref split-arg 1) + #f))) + (if n + ;; If we are looking for argument numbers, + ;; verify than `n' is greater than the + ;; maximum number of paranthesised + ;; expressions in the original pattern. + (if args-are-numbers? + (begin + (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))) + (set! arg (string->symbol (string-append "arg" n)))) + ;; If the argument is not a number, we + ;; want to convert it to a symbol, so + ;; that in the generated code + ;; expression it will refer to the + ;; function argument with the same + ;; name. + (set! arg (string->symbol n)))) + (loop (cdr lst) (cons arg acc))))) + ))) + (if (= (length exp2) 1) + (car exp2) + `(string-append ,@exp2)))) ;; Collect embedded <param> elements into a list of name/value ;; pairs and return it. - (define (get-params elements) + (define (get-params elements args-are-numbers?) (if (eq? elements '()) '() (let* ((nodelist ((node-pos 1) elements)) @@ -197,13 +284,13 @@ (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))) - ))) + (cons `(cons ,name ,(expand-value node value args-are-numbers?)) + (get-params (rest-of-nodes elements) args-are-numbers?))) + )) ;; 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) + (define (get-attributes node required optional allow-params args-are-numbers?) (let* ((elem-name (sxml:element-name node)) (args '()) (params '()) @@ -215,7 +302,8 @@ (xml-error node (format "'~s' attribute required in <~s>" attr-name elem-name)) - `(cons ',attr-name ,(expand-value node attr))))) + `(cons ',attr-name + ,(expand-value node attr args-are-numbers?))))) required)) (optional-attrs '())) (for-each @@ -223,17 +311,19 @@ (let ((attr (sxml:attr node attr-name))) (if attr (set! optional-attrs - (cons `(cons ',attr-name ,(expand-value node attr)) - optional-attrs))))) + (cons + `(cons ',attr-name + ,(expand-value node attr args-are-numbers?)) + 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 + (if allow-params (begin - (set! params (get-params (sxml:content node))) + (set! params (get-params (sxml:content node) args-are-numbers?)) (if (not (null? params)) (set! args (cons `(cons 'params (list ,@params)) args))))) (if (null? args) `('()) `((list ,@args))))) @@ -243,40 +333,45 @@ ;; grammar like approach seems appropriate here. ;; Translate a <generate> element. - (define (match-generate pipeline) + (define (match-generate pipeline args-are-numbers?) (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))) + (let ((args (get-attributes node '(src) '(type) #t + args-are-numbers?))) (match-transform (rest-of-nodes pipeline) - `(sitemap:generate sitemap env ,@args)) + `(sitemap:generate sitemap env ,@args) + args-are-numbers?) ))))) ;; Translate zero or more <transform> elements - (define (match-transform pipeline compfunc) + (define (match-transform pipeline compfunc args-are-numbers?) (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))) + (let ((args (get-attributes node '(src) '(type) #t + args-are-numbers?))) (match-transform (rest-of-nodes pipeline) - `(sitemap:transform sitemap env ,@args ,compfunc)) + `(sitemap:transform sitemap env ,@args ,compfunc) + args-are-numbers?) )) - (else (match-serialize pipeline compfunc)) + (else (match-serialize pipeline compfunc args-are-numbers?)) ))) ;; Transform zero or one <serializer> elements - (define (match-serialize pipeline compfunc) + (define (match-serialize pipeline compfunc args-are-numbers?) (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))) + (let ((args (get-attributes node '() '(type mime-type) #t + args-are-numbers?))) (match-pipeline-end (rest-of-nodes pipeline) `(sitemap:serialize sitemap env ,@args ,compfunc)))) @@ -291,12 +386,13 @@ ))) ;; Translate a <read> element - (define (match-reader pipeline) + (define (match-reader pipeline args-are-numbers?) (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))) + (let ((args (get-attributes node '(src) '(type mime-type) #t + args-are-numbers?))) (match-pipeline-end (rest-of-nodes pipeline) `(sitemap:read sitemap env ,@args)) @@ -304,6 +400,41 @@ (else #f) ))) + ;; Translate a <call function="..."> element + (define (match-call-function pipeline args-are-numbers?) + (let* ((nodelist ((node-pos 1) pipeline)) + (node (if (null? nodelist) '() (car nodelist)))) + (cond + ((and (eq? (sxml:element-name node) 'call) + (sxml:attr node 'function)) + (let ((args (get-attributes node '(function) '() #t + args-are-numbers?))) + (match-pipeline-end + (rest-of-nodes pipeline) + `(begin (function-call-v sitemap env ,@args) #t)) + )) + (else #f) + ))) + + ;; Translate a <call resource="..."> element + (define (match-call-resource pipeline args-are-numbers?) + (let* ((nodelist ((node-pos 1) pipeline)) + (node (if (null? nodelist) '() (car nodelist)))) + (cond + ((and (eq? (sxml:element-name node) 'call) + (sxml:attr node 'resource)) + (let* ((args (get-attributes node '(resource) '() #t + args-are-numbers?)) + (pipeline + ((sxpath '(resources (resource (@ (equal? (name )))))) + sitemap))) + (match-pipeline-end + (rest-of-nodes pipeline) + `(begin (display (format "calling ~s~%" ,@args)) (newline) (resource-call-v sitemap env ,@args) #t)) + )) + (else #f) + ))) + ;; Make sure nothing follows the pipeline definition (define (match-pipeline-end pipeline compfunc) (let* ((nodelist ((node-pos 1) pipeline)) @@ -315,7 +446,7 @@ ;; 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) + (define (match-match pipeline args-are-numbers?) (let ((procname (string->symbol (format "p~a" pcount))) (rxname (string->symbol (format "rx~a" pcount)))) `(define (,procname url sitemap env) @@ -329,19 +460,28 @@ (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"))))) + ,(generate-function-body pipeline args-are-numbers?)) (cdr result)) #f)) (regexp-match ,rxname url))) )) + ;; Generates the code for the function body to processes a + ;; pipeline + (define (generate-function-body pipeline args-are-numbers?) + (let ((is-call (or (match-call-function pipeline args-are-numbers?) + (match-call-resource pipeline args-are-numbers?)))) + (if is-call + is-call + `(sitemap:process + sitemap env '() + ,(or + (match-generate pipeline args-are-numbers?) + (match-reader pipeline args-are-numbers?) + (let* ((nodelist ((node-pos 1) pipeline)) + (node (if (null? nodelist) '() (car nodelist)))) + (xml-error node "Invalid pipeline definition"))))))) + ;; This is the main processing function for a 'match' node in the ;; SXML representation of the sitemap. This function returns an ;; entry like this: @@ -349,7 +489,7 @@ ;; (regexp . matcher-function-representation) ;; ;; The `apply-templates' function which invokes `process-match' - ;; will collect all these pair and return them in a list. + ;; will collect all these pairs 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 @@ -366,9 +506,81 @@ (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))) + (match-match pipeline #t))) ))) + ;; This function is called by the `apply-templates' function below + ;; to process SXML nodes corresponding to <map:resource>. + ;; + ;; For each named resource we create a function whose name is + ;; r_<resource-name>, which contains the definition of the + ;; resource obtained by calling `match-match'. + ;; + ;; This function returns an association whose car is a list, whose + ;; first element is a symbol for the function name, and the rest + ;; are symbols for the arguments of the function. The cdr of the + ;; topmost list is the definition of the function: + ;; + ;; ((r_<resource-name> arguments ...) . <function-definition>) + ;; + ;; Each resource might make use of named parameters, like below: + ;; + ;; <map:resource name="document2html"> + ;; <map:generate src="{source}" type="file"/> + ;; ... + ;; </map:resource> + ;; + ;; This function will identify all the parameters used in the + ;; definition of the resource, and will make them arguments in the + ;; function definition. The created function will use the + ;; `function' macro, instead of the normal Scheme `define'. This + ;; allows for named parameters to be passed to the function at + ;; runtime. + (define (process-resource node) + (let ((resname (sxml:attr node 'name))) + (if (not resname) + (xml-error node "Resource must be named, use a 'name' attribute") + (let* ((funname (name->resource resname)) + (funsymbol (string->symbol funname)) + ;; Check to see if a similar resource has already + ;; been defined. + (_ (if (lookup-resource funsymbol) + (xml-error node "A resource named ~s already defined" + funsymbol))) + (attributes (map (lambda (x) (cadr x)) + ((sxpath '(// @ (*))) node))) + (source-arguments + (filter (lambda (x) + (if (and (string? x) + (regexp-match res-arg-regexp-match x)) + x + #f)) + attributes)) + ;; `source-arguments' contains all the attributes + ;; that contain named parameters. We need to + ;; extract the names from within curly braces + (arguments + (flatten + (map + (lambda (x) + (let* ((v (regexp-split/delimiter arg-regexp-split x)) + (len (vector-length v))) + (let loop ((i 1) (acc '())) + (if (>= i len) + acc + (loop (+ i 2) (cons (vector-ref v i) acc)))) + )) + source-arguments))) + (argument-symbols + (map (lambda (x) (string->symbol x)) arguments)) + (funsig `(,funsymbol ,@argument-symbols)) + (funbody + `(function (,funsymbol sitemap env ,@argument-symbols) + ,(generate-function-body + (reverse (sxml:child-elements node)) #f)))) + (add-resource funsig funbody) + )))) + ;; Process the SXML representation of the sitemap. This is done by ;; invoking the apply-templates function on the SXML representation ;; of the sitemap. @@ -379,19 +591,31 @@ (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. + ;; Compute the available resources first. + (apply-templates + sitemap + `((resources resource . ,(lambda (node) (process-resource node))))) + + ;; `matchers' will contain a list of (regexp . matcher-function) + ;; We'll 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 - `((lambda (,@(let loop ((ms matchers)) + `((pipelines pipeline match + . ,(lambda (node) (process-match node)))))) + (sitemap-code + `((lambda (,@(let loop ((ms matchers)) (if (null? ms) '() (cons (caaar ms) (loop (cdr ms)))))) + ;; Generate the code for the resource function + ;; definitions + ,@(resources-get-function-bodies) + + ;; Generate the code corresponding to the + ;; <map:match> definitions ,@(let loop ((ms matchers)) (if (null? ms) '() @@ -411,13 +635,16 @@ (cons (cadaar ms) (loop (cdr ms)))))) )) -; (newline) (pretty-print sitemap-code) (newline) + (newline) (pretty-print sitemap-code) (newline) (eval sitemap-code (interaction-environment)) ))) )) ;; `the-sitemap' will contain the compiled version of the sitemap. -(define the-sitemap #f) +(define the-sitemap + (lambda (url sitemap env) + (display "Sitemap was not compiled because of errors!") + (newline))) ;; Invoked from the Java side to parse the XML representation of the ;; sitemap and update the `sxml-sitemap' variable. The processing of @@ -442,7 +669,8 @@ (define (sitemap-parse! manager source) (let* ((sxml (sitemap:parse manager source)) (xsxml (process-sitemap sxml))) - (set! the-sitemap xsxml))) + (if (not (eq? xsxml 'error)) + (set! the-sitemap xsxml)))) ;; This is the main entry point in the Scheme Cocoon sitemap. This ;; function is invoked from the SchemeSitemap#process method. @@ -450,17 +678,22 @@ (the-sitemap url sitemap environment)) (define test-sitemap - '(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))))) - ))) - + '(sitemap (@ (*line* 3)) + (resources (@ (*line* 5)) + (resource (@ (name "document2html") (*line* 7)) + (generate (@ (src "{source}") (type "file") (*line* 8))) + (transform (@ (src "stylesheets/document2html.xsl") (*line* 9))) + (serialize (@ (*line* 10))) + )) + + (pipelines (@ (*line* 15)) + (pipeline (@ (*line* 16)) + (match (@ (pattern "a") (*line* 18)) + (generate (@ (src "docs/{1}.xml") (type "file") (*line* 19))) + (transform (@ (src "stylesheets/document2html.xsl") (*line* 20))) + (serialize (@ (*line* 21))) + ) + + (match (@ (pattern "sites/images/(.*).gif") (*line* 23)) + (read (@ (src "{1}") (mime-type "image/gif") (*line* 24)))) + ))))
---------------------------------------------------------------------- In case of troubles, e-mail: [EMAIL PROTECTED] To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]