1 ;;; -*- show-trailing-whitespace: t; indent-tabs: nil -*-
3 ;;; Copyright (c) 2007 David Lichteblau. All rights reserved.
5 ;;; Redistribution and use in source and binary forms, with or without
6 ;;; modification, are permitted provided that the following conditions
9 ;;; * Redistributions of source code must retain the above copyright
10 ;;; notice, this list of conditions and the following disclaimer.
12 ;;; * Redistributions in binary form must reproduce the above
13 ;;; copyright notice, this list of conditions and the following
14 ;;; disclaimer in the documentation and/or other materials
15 ;;; provided with the distribution.
17 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
18 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
21 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 (in-package :xuriella
)
31 (declaim (optimize (debug 3) (safety 3) (space 0) (speed 0)))
34 (defmacro define-instruction
(name (args-var env-var
) &body body
)
35 `(setf (get ',name
'xslt-instruction
)
36 (lambda (,args-var
,env-var
)
37 (declare (ignorable ,env-var
))
40 (define-instruction if
(args env
)
41 (destructuring-bind (test then
&optional else
) args
42 (let ((test-thunk (compile-xpath test env
))
43 (then-thunk (compile-instruction then env
))
44 (else-thunk (when else
(compile-instruction else env
))))
47 ((xpath:boolean-value
(funcall test-thunk ctx
))
48 (funcall then-thunk ctx
))
50 (funcall else-thunk ctx
)))))))
52 (define-instruction when
(args env
)
53 (destructuring-bind (test &rest body
) args
54 (compile-instruction `(if ,test
(progn ,@body
)) env
)))
56 (define-instruction unless
(args env
)
57 (destructuring-bind (test &rest body
) args
58 (compile-instruction `(if (:not
,test
) (progn ,@body
)) env
)))
60 (define-instruction cond
(args env
)
62 (destructuring-bind ((test &body body
) &rest clauses
) args
63 (compile-instruction (if (eq test t
)
71 (define-instruction progn
(args env
)
73 (let ((first-thunk (compile-instruction (first args
) env
))
74 (rest-thunk (compile-instruction `(progn ,@(rest args
)) env
)))
76 (funcall first-thunk ctx
)
77 (funcall rest-thunk ctx
)))
80 (defun decode-qname/runtime
(qname namespaces attributep
)
82 (multiple-value-bind (prefix local-name
)
85 (if (or prefix
(not attributep
))
86 (cdr (assoc prefix namespaces
:test
'equal
))
89 (cxml:well-formedness-violation
()
90 (xslt-error "not a qname: ~A" qname
))))
92 (define-instruction xsl
:element
(args env
)
93 (destructuring-bind ((name &key namespace use-attribute-sets
)
96 (declare (ignore use-attribute-sets
)) ;fixme
97 (multiple-value-bind (name-thunk constant-name-p
)
98 (compile-attribute-value-template name env
)
99 (let ((body-thunk (compile-instruction `(progn ,@body
) env
)))
101 (compile-element/constant-name name namespace env body-thunk
)
102 (compile-element/runtime name-thunk namespace body-thunk
))))))
104 (defun compile-element/constant-name
(qname namespace env body-thunk
)
105 ;; the simple case: compile-time decoding of the QName
106 (multiple-value-bind (local-name uri prefix
)
107 (decode-qname qname env nil
)
109 (setf uri namespace
))
111 (with-element (local-name uri
:suggested-prefix prefix
)
112 (funcall body-thunk ctx
)))))
114 (defun compile-element/runtime
(name-thunk namespace body-thunk
)
115 ;; run-time decoding of the QName, but using the same namespaces
116 ;; that would have been known at compilation time.
117 (let ((namespaces *namespaces
*))
119 (let ((qname (funcall name-thunk ctx
)))
120 (multiple-value-bind (local-name uri prefix
)
121 (decode-qname/runtime qname namespaces nil
)
123 (setf uri namespace
))
125 (with-element (local-name uri
:suggested-prefix prefix
)
126 (funcall body-thunk ctx
))))))))
128 (define-instruction xsl
:attribute
(args env
)
129 (destructuring-bind ((name &key namespace
) &body body
) args
130 (multiple-value-bind (name-thunk constant-name-p
)
131 (compile-attribute-value-template name env
)
132 (let ((value-thunk (compile-instruction `(progn ,@body
) env
)))
134 (compile-attribute/constant-name name namespace env value-thunk
)
135 (compile-attribute/runtime name-thunk namespace value-thunk
))))))
137 (defun compile-attribute/constant-name
(qname namespace env value-thunk
)
138 ;; the simple case: compile-time decoding of the QName
139 (multiple-value-bind (local-name uri prefix
)
140 (decode-qname qname env nil
)
142 (setf uri namespace
))
144 (write-attribute local-name
146 (with-text-output-sink (s)
148 (funcall value-thunk ctx
)))
149 :suggested-prefix prefix
))))
151 (defun compile-attribute/runtime
(name-thunk namespace value-thunk
)
152 ;; run-time decoding of the QName, but using the same namespaces
153 ;; that would have been known at compilation time.
154 (let ((namespaces *namespaces
*))
156 (let ((qname (funcall name-thunk ctx
)))
157 (multiple-value-bind (local-name uri prefix
)
158 (decode-qname/runtime qname namespaces nil
)
160 (setf uri namespace
))
162 (write-attribute local-name
164 (with-text-output-sink (s)
166 (funcall value-thunk ctx
)))
167 :suggested-prefix prefix
)))))))
169 (defun remove-excluded-namespaces
170 (namespaces &optional
(excluded-uris *excluded-namespaces
*))
171 (let ((koerbchen '())
174 for cons in namespaces
175 for
(prefix . uri
) = cons
178 ((find prefix kroepfchen
:test
#'equal
))
179 ((find uri excluded-uris
:test
#'equal
)
180 (push prefix kroepfchen
))
182 (push cons koerbchen
))))
185 (define-instruction xsl
:literal-element
(args env
)
187 ((local-name &optional
(uri "") suggested-prefix
) &body body
)
189 (let ((body-thunk (compile-instruction `(progn ,@body
) env
))
190 (namespaces (remove-excluded-namespaces *namespaces
*)))
192 (with-element (local-name uri
193 :suggested-prefix suggested-prefix
194 :extra-namespaces namespaces
)
195 (funcall body-thunk ctx
))))))
197 (define-instruction xsl
:literal-attribute
(args env
)
198 (destructuring-bind ((local-name &optional uri suggested-prefix
) value
) args
199 (let ((value-thunk (compile-attribute-value-template value env
)))
201 (write-attribute local-name
203 (funcall value-thunk ctx
)
204 :suggested-prefix suggested-prefix
)))))
206 (define-instruction xsl
:text
(args env
)
207 (destructuring-bind (str) args
209 (declare (ignore ctx
))
212 (define-instruction xsl
:processing-instruction
(args env
)
213 (destructuring-bind (name &rest body
) args
214 (let ((name-thunk (compile-attribute-value-template name env
))
215 (value-thunk (compile-instruction `(progn ,@body
) env
)))
217 (write-processing-instruction
218 (funcall name-thunk ctx
)
219 (with-text-output-sink (s)
221 (funcall value-thunk ctx
))))))))
223 (define-instruction xsl
:comment
(args env
)
224 (destructuring-bind (str) args
226 (declare (ignore ctx
))
227 (write-comment str
))))
229 (define-instruction xsl
:value-of
(args env
)
230 (destructuring-bind (xpath) args
231 (let ((thunk (compile-xpath xpath env
)))
233 (write-text (xpath:string-value
(funcall thunk ctx
)))))))
235 (define-instruction xsl
:unescaped-value-of
(args env
)
236 (destructuring-bind (xpath) args
237 (let ((thunk (compile-xpath xpath env
)))
239 (write-unescaped (xpath:string-value
(funcall thunk ctx
)))))))
241 (define-instruction xsl
:copy-of
(args env
)
242 (destructuring-bind (xpath) args
243 (let ((thunk (compile-xpath xpath env
))
244 ;; FIXME: what was this for? --david
245 #+(or) (v (intern-variable "varName" "")))
247 (let ((result (funcall thunk ctx
)))
249 (xpath:node-set
;; FIXME: variables can contain node sets w/fragments inside. Maybe just fragments would do?
250 (xpath:map-node-set
#'copy-into-result result
))
251 (result-tree-fragment
252 (copy-into-result result
))
254 (write-text (xpath:string-value result
)))))))))
256 (defun copy-into-result (node)
258 ((result-tree-fragment-p node
)
259 (stp:do-children
(child (result-tree-fragment-node node
))
260 (copy-into-result child
)))
261 ((xpath-protocol:node-type-p node
:element
)
262 (with-element ((xpath-protocol:local-name node
)
263 (xpath-protocol:namespace-uri node
)
264 :suggested-prefix
(xpath-protocol:namespace-prefix node
)
265 ;; FIXME: is remove-excluded-namespaces correct here?
266 :extra-namespaces
(remove-excluded-namespaces
267 (namespaces-as-alist node
)))
268 (map-pipe-eagerly #'copy-into-result
269 (xpath-protocol:attribute-pipe node
))
270 (map-pipe-eagerly #'copy-into-result
271 (xpath-protocol:child-pipe node
))))
272 ((xpath-protocol:node-type-p node
:document
)
273 (map-pipe-eagerly #'copy-into-result
274 (xpath-protocol:child-pipe node
)))
276 (copy-leaf-node node
))))
278 (define-instruction xsl
:for-each
(args env
)
279 (destructuring-bind (select &optional decls
&rest body
) args
280 (when (and (consp decls
)
281 (not (eq (car decls
) 'declare
)))
284 (let ((select-thunk (compile-xpath select env
))
285 (body-thunk (compile-instruction `(progn ,@body
) env
))
287 ;; fixme: parse decls here
290 (let* ((nodes (xpath:all-nodes
(funcall sorter
(funcall select-thunk ctx
))))
297 (xpath:make-context node
(lambda () n
) i
))))))))
299 (define-instruction xsl
:with-namespaces
(args env
)
300 (destructuring-bind ((&rest forms
) &rest body
) args
301 (let ((*namespaces
* *namespaces
*))
303 (destructuring-bind (prefix uri
) form
304 (push (cons prefix uri
) *namespaces
*)))
305 (compile-instruction `(progn ,@body
) env
))))
307 (define-instruction xsl
:with-excluded-namespaces
(args env
)
308 (destructuring-bind ((&rest uris
) &rest body
) args
309 (let ((*excluded-namespaces
* (append uris
*excluded-namespaces
*)))
310 (compile-instruction `(progn ,@body
) env
))))
312 ;; XSLT disallows multiple definitions of the same variable within a
313 ;; template. Local variables can shadow global variables though.
314 ;; Since our LET syntax makes it natural to shadow local variables the
315 ;; Lisp way, we check for duplicate variables only where instructed to
316 ;; by the XML syntax parser using WITH-DUPLICATES-CHECK:
317 (defvar *template-variables
* nil
)
319 (define-instruction xsl
:with-duplicates-check
(args env
)
320 (let ((*template-variables
* *template-variables
*))
321 (destructuring-bind ((&rest qnames
) &rest body
) args
322 (dolist (qname qnames
)
323 (multiple-value-bind (local-name uri
)
324 (decode-qname qname env nil
)
325 (let ((key (cons local-name uri
)))
326 (when (find key
*template-variables
* :test
#'equal
)
327 (xslt-error "duplicate variable: ~A, ~A" local-name uri
))
328 (push key
*template-variables
*))))
329 (compile-instruction `(progn ,@body
) env
))))
331 (defstruct (result-tree-fragment
332 (:constructor make-result-tree-fragment
(node)))
335 (defmethod xpath-protocol:node-p
((node result-tree-fragment
))
338 (defmethod xpath-protocol:string-value
((node result-tree-fragment
))
339 (xpath-protocol:string-value
(result-tree-fragment-node node
)))
341 (defun apply-to-result-tree-fragment (ctx thunk
)
343 (with-xml-output (stp:make-builder
)
344 (with-element ("fragment" "")
345 (funcall thunk ctx
)))))
346 (make-result-tree-fragment (stp:document-element document
))))
348 (define-instruction let
(args env
)
349 (destructuring-bind ((&rest forms
) &rest body
) args
350 (let* ((old-top (length *lexical-variable-declarations
*))
351 (vars-and-names (compile-var-bindings/nointern forms env
))
353 (loop for
((local-name . uri
) thunk
) in vars-and-names
355 (list (push-variable local-name
357 *lexical-variable-declarations
*)
359 (let ((thunk (compile-instruction `(progn ,@body
) env
)))
360 (fill *lexical-variable-declarations
* nil
:start old-top
)
362 (loop for
(index var-thunk
) in vars-and-positions
363 do
(setf (lexical-variable-value index
)
364 (funcall var-thunk ctx
)))
365 (funcall thunk ctx
))))))
367 (define-instruction let
* (args env
)
368 (destructuring-bind ((&rest forms
) &rest body
) args
370 (compile-instruction `(let (,(car forms
))
371 (let* (,@(cdr forms
))
374 (compile-instruction `(progn ,@body
) env
))))
376 (define-instruction xsl
:message
(args env
)
377 (compile-message #'warn args env
))
379 (define-instruction xsl
:terminate
(args env
)
380 (compile-message #'error args env
))
382 (defun namespaces-as-alist (element)
383 (let ((namespaces '()))
384 (do-pipe (ns (xpath-protocol:namespace-pipe element
))
385 (push (cons (xpath-protocol:local-name ns
)
386 (xpath-protocol:namespace-uri ns
))
390 (define-instruction xsl
:copy
(args env
)
391 (destructuring-bind ((&key use-attribute-sets
) &rest rest
)
393 (declare (ignore use-attribute-sets
))
394 (let ((body (compile-instruction `(progn ,@rest
) env
)))
396 (let ((node (xpath:context-node ctx
)))
398 ((xpath-protocol:node-type-p node
:element
)
400 ((xpath-protocol:local-name node
)
401 (xpath-protocol:namespace-uri node
)
402 :suggested-prefix
(xpath-protocol:namespace-prefix node
)
403 :extra-namespaces
(namespaces-as-alist node
))
405 ((xpath-protocol:node-type-p node
:document
)
408 (copy-leaf-node node
))))))))
410 (defun copy-leaf-node (node)
412 ((xpath-protocol:node-type-p node
:text
)
413 (write-text (xpath-protocol:string-value node
)))
414 ((xpath-protocol:node-type-p node
:comment
)
415 (write-comment (xpath-protocol:string-value node
)))
416 ((xpath-protocol:node-type-p node
:processing-instruction
)
417 (write-processing-instruction
418 (xpath-protocol:processing-instruction-target node
)
419 (xpath-protocol:string-value node
)))
420 ((xpath-protocol:node-type-p node
:attribute
)
422 (xpath-protocol:local-name node
)
423 (xpath-protocol:namespace-uri node
)
424 (xpath-protocol:string-value node
)
425 :suggested-prefix
(xpath-protocol:namespace-prefix node
)))
427 (error "don't know how to copy node ~A" node
))))
429 (defun compile-message (fn args env
)
430 (let ((thunk (compile-instruction `(progn ,@args
) env
)))
433 (with-xml-output (cxml:make-string-sink
)
434 (funcall thunk ctx
))))))
436 (define-instruction xsl
:apply-templates
(args env
)
437 (destructuring-bind ((&key select mode
) &rest param-binding-specs
) args
439 (compile-xpath (or select
"child::node()") env
))
441 (compile-var-bindings param-binding-specs env
)))
442 (multiple-value-bind (mode-local-name mode-uri
)
443 (and mode
(decode-qname mode env nil
))
445 (let ((*mode
* (if mode
446 (or (find-mode *stylesheet
*
451 (apply-templates/list
452 (xpath:all-nodes
(funcall select-thunk ctx
))
453 (loop for
(name nil value-thunk
) in param-bindings
454 collect
(list name
(funcall value-thunk ctx
))))))))))
456 (define-instruction xsl
:call-template
(args env
)
457 (destructuring-bind (name &rest param-binding-specs
) args
458 (let ((param-bindings
459 (compile-var-bindings param-binding-specs env
)))
460 (multiple-value-bind (local-name uri
)
461 (decode-qname name env nil
)
462 (setf name
(cons local-name uri
)))
464 (call-template ctx name
465 (loop for
(name nil value-thunk
) in param-bindings
466 collect
(list name
(funcall value-thunk ctx
))))))))
468 (defun compile-instruction (form env
)
469 (funcall (or (get (car form
) 'xslt-instruction
)
470 (error "undefined instruction: ~A" (car form
)))
474 (xpath::deflexer make-attribute-template-lexer
475 ("([^{]+)" (data) (values :data data
))
476 ("{([^}]+)}" (xpath) (values :xpath xpath
)))
478 (defun compile-attribute-value-template (template-string env
)
479 (let* ((lexer (make-attribute-template-lexer template-string
))
484 (multiple-value-bind (kind str
) (funcall lexer
)
490 (xpath:compile-xpath str env
))
494 (values (lambda (ctx)
495 (with-output-to-string (s)
497 (write-string (xpath:string-value
(funcall fn ctx
)) s
))))
501 ;;;; Indentation for slime
503 (defmacro define-indentation
(name (&rest args
))
504 (labels ((collect-variables (list)
510 (collect-variables sub
))
512 (if (eql (mismatch "&" (symbol-name sub
)) 1)
515 `(defmacro ,name
(,@args
)
516 (declare (ignorable ,@(collect-variables args
)))
517 (error "XSL indentation helper ~A used literally in lisp code"
520 (define-indentation xsl
:element
521 ((name &key namespace use-attribute-sets
) &body body
))
522 (define-indentation xsl
:literal-element
((name &optional uri
) &body body
))
523 (define-indentation xsl
:attribute
((name &key namespace
) &body body
))
524 (define-indentation xsl
:literal-attribute
((name &optional uri
) &body body
))
525 (define-indentation xsl
:text
(str))
526 (define-indentation xsl
:processing-instruction
(name &body body
))
527 (define-indentation xsl
:comment
(str))
528 (define-indentation xsl
:value-of
(xpath))
529 (define-indentation xsl
:unescaped-value-of
(xpath))
530 (define-indentation xsl
:for-each
(select &body decls-and-body
))
531 (define-indentation xsl
:message
(&body body
))
532 (define-indentation xsl
:terminate
(&body body
))
533 (define-indentation xsl
:apply-templates
((&key select mode
) &body decls-and-body
))
534 (define-indentation xsl
:call-template
(name &rest parameters
))
535 (define-indentation xsl
:copy-of
(xpath))
539 (defun test-instruction (form document
)
540 (let ((thunk (compile-instruction form
(make-instance 'lexical-environment
)))
541 (root (cxml:parse document
(stp:make-builder
))))
542 (with-xml-output (cxml:make-string-sink
)
543 (funcall thunk
(xpath:make-context root
)))))