1 ;;; -*- show-trailing-whitespace: t; indent-tabs: nil -*-
3 ;;; Copyright (c) 2007,2008 David Lichteblau, Ivan Shvedunov.
4 ;;; All rights reserved.
6 ;;; Redistribution and use in source and binary forms, with or without
7 ;;; modification, are permitted provided that the following conditions
10 ;;; * Redistributions of source code must retain the above copyright
11 ;;; notice, this list of conditions and the following disclaimer.
13 ;;; * Redistributions in binary form must reproduce the above
14 ;;; copyright notice, this list of conditions and the following
15 ;;; disclaimer in the documentation and/or other materials
16 ;;; provided with the distribution.
18 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
19 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
22 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
24 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 (in-package :xuriella
)
32 (declaim (optimize (debug 3) (safety 3) (space 0) (speed 0)))
35 (defmacro define-instruction
(name (args-var env-var
) &body body
)
36 `(setf (get ',name
'xslt-instruction
)
37 (lambda (,args-var
,env-var
)
38 (declare (ignorable ,env-var
))
41 (define-instruction if
(args env
)
42 (destructuring-bind (test then
&optional else
) args
43 (let ((test-thunk (compile-xpath test env
))
44 (then-thunk (compile-instruction then env
))
45 (else-thunk (when else
(compile-instruction else env
))))
48 ((xpath:boolean-value
(funcall test-thunk ctx
))
49 (funcall then-thunk ctx
))
51 (funcall else-thunk ctx
)))))))
53 (define-instruction when
(args env
)
54 (destructuring-bind (test &rest body
) args
55 (compile-instruction `(if ,test
(progn ,@body
)) env
)))
57 (define-instruction unless
(args env
)
58 (destructuring-bind (test &rest body
) args
59 (compile-instruction `(if (:not
,test
) (progn ,@body
)) env
)))
61 (define-instruction cond
(args env
)
63 (destructuring-bind ((test &body body
) &rest clauses
) args
64 (compile-instruction (if (eq test t
)
72 (define-instruction progn
(args env
)
74 (let ((first-thunk (compile-instruction (first args
) env
))
75 (rest-thunk (compile-instruction `(progn ,@(rest args
)) env
)))
77 (funcall first-thunk ctx
)
78 (funcall rest-thunk ctx
)))
81 (defun decode-qname/runtime
(qname namespaces attributep
)
83 (multiple-value-bind (prefix local-name
)
86 (if (or prefix
(not attributep
))
87 (cdr (assoc prefix namespaces
:test
'equal
))
90 (cxml:well-formedness-violation
()
91 (xslt-error "not a qname: ~A" qname
))))
93 (define-instruction xsl
:element
(args env
)
94 (destructuring-bind ((name &key namespace use-attribute-sets
)
97 (declare (ignore use-attribute-sets
)) ;fixme
98 (multiple-value-bind (name-thunk constant-name-p
)
99 (compile-attribute-value-template name env
)
100 (let ((body-thunk (compile-instruction `(progn ,@body
) env
)))
102 (compile-element/constant-name name namespace env body-thunk
)
103 (compile-element/runtime name-thunk namespace body-thunk
))))))
105 (defun compile-element/constant-name
(qname namespace env body-thunk
)
106 ;; the simple case: compile-time decoding of the QName
107 (multiple-value-bind (local-name uri prefix
)
108 (decode-qname qname env nil
)
110 (setf uri namespace
))
112 (with-element (local-name uri
:suggested-prefix prefix
)
113 (funcall body-thunk ctx
)))))
115 (defun compile-element/runtime
(name-thunk namespace body-thunk
)
116 ;; run-time decoding of the QName, but using the same namespaces
117 ;; that would have been known at compilation time.
118 (let ((namespaces *namespaces
*))
120 (let ((qname (funcall name-thunk ctx
)))
121 (multiple-value-bind (local-name uri prefix
)
122 (decode-qname/runtime qname namespaces nil
)
124 (setf uri namespace
))
126 (with-element (local-name uri
:suggested-prefix prefix
)
127 (funcall body-thunk ctx
))))))))
129 (define-instruction xsl
:use-attribute-sets
(args env
)
130 (destructuring-bind (str) args
131 (let ((sets (mapcar (lambda (qname)
132 (multiple-value-list (decode-qname qname env nil
)))
135 (loop for
(local-name uri nil
) in sets do
136 (dolist (thunk (find-attribute-set local-name uri
))
137 (funcall thunk ctx
)))))))
139 (define-instruction xsl
:attribute
(args env
)
140 (destructuring-bind ((name &key namespace
) &body body
) args
141 (multiple-value-bind (name-thunk constant-name-p
)
142 (compile-attribute-value-template name env
)
143 (let ((value-thunk (compile-instruction `(progn ,@body
) env
)))
145 (compile-attribute/constant-name name namespace env value-thunk
)
146 (compile-attribute/runtime name-thunk namespace value-thunk
))))))
148 (defun compile-attribute/constant-name
(qname namespace env value-thunk
)
149 ;; the simple case: compile-time decoding of the QName
150 (multiple-value-bind (local-name uri prefix
)
151 (decode-qname qname env nil
)
153 (setf uri namespace
))
155 (write-attribute local-name
157 (with-text-output-sink (s)
159 (funcall value-thunk ctx
)))
160 :suggested-prefix prefix
))))
162 (defun compile-attribute/runtime
(name-thunk namespace value-thunk
)
163 ;; run-time decoding of the QName, but using the same namespaces
164 ;; that would have been known at compilation time.
165 (let ((namespaces *namespaces
*))
167 (let ((qname (funcall name-thunk ctx
)))
168 (multiple-value-bind (local-name uri prefix
)
169 (decode-qname/runtime qname namespaces nil
)
171 (setf uri namespace
))
173 (write-attribute local-name
175 (with-text-output-sink (s)
177 (funcall value-thunk ctx
)))
178 :suggested-prefix prefix
)))))))
180 (defun remove-excluded-namespaces
181 (namespaces &optional
(excluded-uris *excluded-namespaces
*))
182 (let ((koerbchen '())
185 for cons in namespaces
186 for
(prefix . uri
) = cons
189 ((find prefix kroepfchen
:test
#'equal
))
190 ((find uri excluded-uris
:test
#'equal
)
191 (push prefix kroepfchen
))
193 (push cons koerbchen
))))
196 (define-instruction xsl
:literal-element
(args env
)
198 ((local-name &optional
(uri "") suggested-prefix
) &body body
)
200 (let ((body-thunk (compile-instruction `(progn ,@body
) env
))
201 (namespaces (remove-excluded-namespaces *namespaces
*)))
203 (with-element (local-name uri
204 :suggested-prefix suggested-prefix
205 :extra-namespaces namespaces
)
206 (funcall body-thunk ctx
))))))
208 (define-instruction xsl
:literal-attribute
(args env
)
209 (destructuring-bind ((local-name &optional uri suggested-prefix
) value
) args
210 (let ((value-thunk (compile-attribute-value-template value env
)))
212 (write-attribute local-name
214 (funcall value-thunk ctx
)
215 :suggested-prefix suggested-prefix
)))))
217 (define-instruction xsl
:text
(args env
)
218 (destructuring-bind (str) args
220 (declare (ignore ctx
))
223 (define-instruction xsl
:processing-instruction
(args env
)
224 (destructuring-bind (name &rest body
) args
225 (let ((name-thunk (compile-attribute-value-template name env
))
226 (value-thunk (compile-instruction `(progn ,@body
) env
)))
228 (write-processing-instruction
229 (funcall name-thunk ctx
)
230 (with-text-output-sink (s)
232 (funcall value-thunk ctx
))))))))
234 (define-instruction xsl
:comment
(args env
)
235 (destructuring-bind (str) args
237 (declare (ignore ctx
))
238 (write-comment str
))))
240 (define-instruction xsl
:value-of
(args env
)
241 (destructuring-bind (xpath) args
242 (let ((thunk (compile-xpath xpath env
)))
244 (write-text (xpath:string-value
(funcall thunk ctx
)))))))
246 (define-instruction xsl
:unescaped-value-of
(args env
)
247 (destructuring-bind (xpath) args
248 (let ((thunk (compile-xpath xpath env
)))
250 (write-unescaped (xpath:string-value
(funcall thunk ctx
)))))))
252 (define-instruction xsl
:copy-of
(args env
)
253 (destructuring-bind (xpath) args
254 (let ((thunk (compile-xpath xpath env
))
255 ;; FIXME: what was this for? --david
256 #+(or) (v (intern-variable "varName" "")))
258 (let ((result (funcall thunk ctx
)))
260 (xpath:node-set
;; FIXME: variables can contain node sets w/fragments inside. Maybe just fragments would do?
261 (xpath:map-node-set
#'copy-into-result result
))
262 (result-tree-fragment
263 (copy-into-result result
))
265 (write-text (xpath:string-value result
)))))))))
267 (defun copy-into-result (node)
269 ((result-tree-fragment-p node
)
270 (stp:do-children
(child (result-tree-fragment-node node
))
271 (copy-into-result child
)))
272 ((xpath-protocol:node-type-p node
:element
)
273 (with-element ((xpath-protocol:local-name node
)
274 (xpath-protocol:namespace-uri node
)
275 :suggested-prefix
(xpath-protocol:namespace-prefix node
)
276 ;; FIXME: is remove-excluded-namespaces correct here?
277 :extra-namespaces
(remove-excluded-namespaces
278 (namespaces-as-alist node
)))
279 (map-pipe-eagerly #'copy-into-result
280 (xpath-protocol:attribute-pipe node
))
281 (map-pipe-eagerly #'copy-into-result
282 (xpath-protocol:child-pipe node
))))
283 ((xpath-protocol:node-type-p node
:document
)
284 (map-pipe-eagerly #'copy-into-result
285 (xpath-protocol:child-pipe node
)))
287 (copy-leaf-node node
))))
289 (defun make-sorter (spec env
)
290 (destructuring-bind (&key select lang data-type order case-order
)
292 ;; FIXME: implement case-order
293 (declare (ignore lang case-order
))
294 (let ((select-thunk (compile-xpath (or select
".") env
))
295 (numberp (equal data-type
"number"))
296 (f (if (equal order
"descending") -
1 1)))
298 (let ((i (xpath:string-value
299 (funcall select-thunk
(xpath:make-context a
))))
300 (j (xpath:string-value
301 (funcall select-thunk
(xpath:make-context b
)))))
304 (signum (- (xpath:number-value i
) (xpath:number-value j
)))
310 (defun compose-sorters (sorters)
312 (let ((this (car sorters
))
313 (next (compose-sorters (rest sorters
))))
315 (let ((d (funcall this a b
)))
321 (defun make-sort-predicate (decls env
)
324 (mapcar (lambda (x) (make-sorter x env
)) decls
))))
326 (minusp (funcall sorter a b
)))))
328 (define-instruction xsl
:for-each
(args env
)
329 (destructuring-bind (select &optional decls
&rest body
) args
330 (unless (and (consp decls
)
331 (eq (car decls
) 'declare
))
334 (let ((select-thunk (compile-xpath select env
))
335 (body-thunk (compile-instruction `(progn ,@body
) env
))
338 (make-sort-predicate (cdr decls
) env
))))
340 (let* ((nodes (xpath:all-nodes
(funcall select-thunk ctx
)))
343 (setf nodes
(sort nodes sort-predicate
)))
349 (xpath:make-context node
(lambda () n
) i
))))))))
351 (define-instruction xsl
:with-namespaces
(args env
)
352 (destructuring-bind ((&rest forms
) &rest body
) args
353 (let ((*namespaces
* *namespaces
*))
355 (destructuring-bind (prefix uri
) form
356 (push (cons prefix uri
) *namespaces
*)))
357 (compile-instruction `(progn ,@body
) env
))))
359 (define-instruction xsl
:with-excluded-namespaces
(args env
)
360 (destructuring-bind ((&rest uris
) &rest body
) args
361 (let ((*excluded-namespaces
* (append uris
*excluded-namespaces
*)))
362 (compile-instruction `(progn ,@body
) env
))))
364 ;; XSLT disallows multiple definitions of the same variable within a
365 ;; template. Local variables can shadow global variables though.
366 ;; Since our LET syntax makes it natural to shadow local variables the
367 ;; Lisp way, we check for duplicate variables only where instructed to
368 ;; by the XML syntax parser using WITH-DUPLICATES-CHECK:
369 (defvar *template-variables
* nil
)
371 (define-instruction xsl
:with-duplicates-check
(args env
)
372 (let ((*template-variables
* *template-variables
*))
373 (destructuring-bind ((&rest qnames
) &rest body
) args
374 (dolist (qname qnames
)
375 (multiple-value-bind (local-name uri
)
376 (decode-qname qname env nil
)
377 (let ((key (cons local-name uri
)))
378 (when (find key
*template-variables
* :test
#'equal
)
379 (xslt-error "duplicate variable: ~A, ~A" local-name uri
))
380 (push key
*template-variables
*))))
381 (compile-instruction `(progn ,@body
) env
))))
383 (define-instruction xsl
:with-base-uri
(args env
)
384 (destructuring-bind (uri &rest body
) args
385 (let ((*instruction-base-uri
* uri
))
386 (compile-instruction `(progn ,@body
) env
))))
388 (defstruct (result-tree-fragment
389 (:constructor make-result-tree-fragment
(node)))
392 (defmethod xpath-protocol:node-p
((node result-tree-fragment
))
395 (defmethod xpath-protocol:string-value
((node result-tree-fragment
))
396 (xpath-protocol:string-value
(result-tree-fragment-node node
)))
398 (defun apply-to-result-tree-fragment (ctx thunk
)
400 (with-xml-output (stp:make-builder
)
401 (with-element ("fragment" "")
402 (funcall thunk ctx
)))))
403 (make-result-tree-fragment (stp:document-element document
))))
405 (define-instruction let
(args env
)
406 (destructuring-bind ((&rest forms
) &rest body
) args
407 (let* ((old-top (length *lexical-variable-declarations
*))
408 (vars-and-names (compile-var-bindings/nointern forms env
))
410 (loop for
((local-name . uri
) thunk
) in vars-and-names
412 (list (push-variable local-name
414 *lexical-variable-declarations
*)
416 (let ((thunk (compile-instruction `(progn ,@body
) env
)))
417 (fill *lexical-variable-declarations
* nil
:start old-top
)
419 (loop for
(index var-thunk
) in vars-and-positions
420 do
(setf (lexical-variable-value index
)
421 (funcall var-thunk ctx
)))
422 (funcall thunk ctx
))))))
424 (define-instruction let
* (args env
)
425 (destructuring-bind ((&rest forms
) &rest body
) args
427 (compile-instruction `(let (,(car forms
))
428 (let* (,@(cdr forms
))
431 (compile-instruction `(progn ,@body
) env
))))
433 (define-instruction xsl
:message
(args env
)
434 (compile-message #'warn args env
))
436 (define-instruction xsl
:terminate
(args env
)
437 (compile-message #'error args env
))
439 (defun namespaces-as-alist (element)
440 (let ((namespaces '()))
441 (do-pipe (ns (xpath-protocol:namespace-pipe element
))
442 (push (cons (xpath-protocol:local-name ns
)
443 (xpath-protocol:namespace-uri ns
))
447 (define-instruction xsl
:copy
(args env
)
448 (let ((body (compile-instruction `(progn ,@args
) env
)))
450 (let ((node (xpath:context-node ctx
)))
452 ((xpath-protocol:node-type-p node
:element
)
454 ((xpath-protocol:local-name node
)
455 (xpath-protocol:namespace-uri node
)
456 :suggested-prefix
(xpath-protocol:namespace-prefix node
)
457 :extra-namespaces
(namespaces-as-alist node
))
459 ((xpath-protocol:node-type-p node
:document
)
462 (copy-leaf-node node
)))))))
464 (defun copy-leaf-node (node)
466 ((xpath-protocol:node-type-p node
:text
)
467 (write-text (xpath-protocol:string-value node
)))
468 ((xpath-protocol:node-type-p node
:comment
)
469 (write-comment (xpath-protocol:string-value node
)))
470 ((xpath-protocol:node-type-p node
:processing-instruction
)
471 (write-processing-instruction
472 (xpath-protocol:processing-instruction-target node
)
473 (xpath-protocol:string-value node
)))
474 ((xpath-protocol:node-type-p node
:attribute
)
476 (xpath-protocol:local-name node
)
477 (xpath-protocol:namespace-uri node
)
478 (xpath-protocol:string-value node
)
479 :suggested-prefix
(xpath-protocol:namespace-prefix node
)))
481 (error "don't know how to copy node ~A" node
))))
483 (defun compile-message (fn args env
)
484 (let ((thunk (compile-instruction `(progn ,@args
) env
)))
487 (with-xml-output (cxml:make-string-sink
)
488 (funcall thunk ctx
))))))
490 (define-instruction xsl
:apply-templates
(args env
)
491 (destructuring-bind ((&key select mode
) &rest param-binding-specs
) args
493 (when (and (consp (car param-binding-specs
))
494 (eq (caar param-binding-specs
) 'declare
))
495 (cdr (pop param-binding-specs
))))
497 (compile-xpath (or select
"child::node()") env
))
499 (compile-var-bindings param-binding-specs env
))
502 (make-sort-predicate decls env
))))
503 (multiple-value-bind (mode-local-name mode-uri
)
504 (and mode
(decode-qname mode env nil
))
506 (let ((*mode
* (if mode
507 (or (find-mode *stylesheet
*
512 (apply-templates/list
513 (xpath:all-nodes
(funcall select-thunk ctx
))
514 (loop for
(name nil value-thunk
) in param-bindings
515 collect
(list name
(funcall value-thunk ctx
)))
516 sort-predicate
)))))))
518 (define-instruction xsl
:apply-imports
(args env
)
520 (declare (ignore ctx
))
521 (funcall *apply-imports
*)))
523 (define-instruction xsl
:call-template
(args env
)
524 (destructuring-bind (name &rest param-binding-specs
) args
525 (let ((param-bindings
526 (compile-var-bindings param-binding-specs env
)))
527 (multiple-value-bind (local-name uri
)
528 (decode-qname name env nil
)
529 (setf name
(cons local-name uri
)))
531 (call-template ctx name
532 (loop for
(name nil value-thunk
) in param-bindings
533 collect
(list name
(funcall value-thunk ctx
))))))))
535 (defun compile-instruction (form env
)
536 (funcall (or (get (car form
) 'xslt-instruction
)
537 (error "undefined instruction: ~A" (car form
)))
541 (xpath::deflexer make-attribute-template-lexer
542 ("([^{]+)" (data) (values :data data
))
543 ("{([^}]+)}" (xpath) (values :xpath xpath
)))
545 (defun compile-attribute-value-template (template-string env
)
546 (let* ((lexer (make-attribute-template-lexer template-string
))
551 (multiple-value-bind (kind str
) (funcall lexer
)
557 (xpath:compile-xpath str env
))
561 (values (lambda (ctx)
562 (with-output-to-string (s)
564 (write-string (xpath:string-value
(funcall fn ctx
)) s
))))
568 ;;;; Indentation for slime
570 (defmacro define-indentation
(name (&rest args
))
571 (labels ((collect-variables (list)
577 (collect-variables sub
))
579 (if (eql (mismatch "&" (symbol-name sub
)) 1)
582 `(defmacro ,name
(,@args
)
583 (declare (ignorable ,@(collect-variables args
)))
584 (error "XSL indentation helper ~A used literally in lisp code"
587 (define-indentation xsl
:element
588 ((name &key namespace use-attribute-sets
) &body body
))
589 (define-indentation xsl
:literal-element
((name &optional uri
) &body body
))
590 (define-indentation xsl
:attribute
((name &key namespace
) &body body
))
591 (define-indentation xsl
:literal-attribute
((name &optional uri
) &body body
))
592 (define-indentation xsl
:text
(str))
593 (define-indentation xsl
:processing-instruction
(name &body body
))
594 (define-indentation xsl
:comment
(str))
595 (define-indentation xsl
:value-of
(xpath))
596 (define-indentation xsl
:unescaped-value-of
(xpath))
597 (define-indentation xsl
:for-each
(select &body decls-and-body
))
598 (define-indentation xsl
:message
(&body body
))
599 (define-indentation xsl
:terminate
(&body body
))
600 (define-indentation xsl
:apply-templates
((&key select mode
) &body decls-and-body
))
601 (define-indentation xsl
:call-template
(name &rest parameters
))
602 (define-indentation xsl
:copy-of
(xpath))
606 (defun test-instruction (form document
)
607 (let ((thunk (compile-instruction form
(make-instance 'lexical-environment
)))
608 (root (cxml:parse document
(stp:make-builder
))))
609 (with-xml-output (cxml:make-string-sink
)
610 (funcall thunk
(xpath:make-context root
)))))