Disallow empty attributes in xsl:sort
[xuriella.git] / instructions.lisp
blob970eed8cb20c025cdbf9b2404a65f5a841bc7b79
1 ;;; -*- show-trailing-whitespace: t; indent-tabs-mode: 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
8 ;;; are met:
9 ;;;
10 ;;; * Redistributions of source code must retain the above copyright
11 ;;; notice, this list of conditions and the following disclaimer.
12 ;;;
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.
17 ;;;
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 #+sbcl
33 (declaim (optimize (debug 2)))
36 ;;;; Instructions
38 (defparameter *available-instructions* (make-hash-table :test 'equal))
40 (defmacro define-instruction (name (args-var env-var) &body body)
41 `(setf (get ',name 'xslt-instruction)
42 (lambda (,args-var ,env-var)
43 (declare (ignorable ,env-var))
44 ,@body)))
46 (define-instruction if (args env)
47 (destructuring-bind (test then &optional else) args
48 (let ((test-thunk (compile-xpath test env))
49 (then-thunk (compile-instruction then env))
50 (else-thunk (when else (compile-instruction else env))))
51 (lambda (ctx)
52 (cond
53 ((xpath:boolean-value (funcall test-thunk ctx))
54 (funcall then-thunk ctx))
55 (else-thunk
56 (funcall else-thunk ctx)))))))
58 (define-instruction when (args env)
59 (destructuring-bind (test &rest body) args
60 (compile-instruction `(if ,test (progn ,@body)) env)))
62 (define-instruction unless (args env)
63 (destructuring-bind (test &rest body) args
64 (compile-instruction `(if (:not ,test) (progn ,@body)) env)))
66 (define-instruction cond (args env)
67 (if args
68 (destructuring-bind ((test &body body) &rest clauses) args
69 (compile-instruction (if (eq test t)
70 `(progn ,@body)
71 `(if ,test
72 (progn ,@body)
73 (cond ,@clauses)))
74 env))
75 (constantly nil)))
77 (define-instruction progn (args env)
78 (if args
79 (let ((first-thunk (compile-instruction (first args) env))
80 (rest-thunk (compile-instruction `(progn ,@(rest args)) env)))
81 (lambda (ctx)
82 (funcall first-thunk ctx)
83 (funcall rest-thunk ctx)))
84 (constantly nil)))
86 (defun decode-qname/runtime (qname namespaces attributep)
87 (handler-case
88 (multiple-value-bind (prefix local-name)
89 (split-qname qname)
90 (values local-name
91 (if (or prefix (not attributep))
92 (or (cdr (assoc prefix namespaces :test 'equal))
93 (xslt-error "namespace not found: ~A" prefix))
94 "")
95 prefix))
96 (cxml:well-formedness-violation ()
97 (xslt-error "not a qname: ~A" qname))))
99 (define-instruction xsl:element (args env)
100 (destructuring-bind ((name &key namespace use-attribute-sets)
101 &body body)
102 args
103 (declare (ignore use-attribute-sets)) ;fixme
104 (multiple-value-bind (name-thunk constant-name-p)
105 (compile-avt name env)
106 (multiple-value-bind (ns-thunk constant-ns-p)
107 (if namespace
108 (compile-avt namespace env)
109 (values nil t))
110 (let ((body-thunk (compile-instruction `(progn ,@body) env)))
111 (if (and constant-name-p constant-ns-p)
112 (compile-element/constant-name name namespace env body-thunk)
113 (compile-element/runtime name-thunk ns-thunk body-thunk)))))))
115 (defun compile-element/constant-name (qname namespace env body-thunk)
116 ;; the simple case: compile-time decoding of the QName
117 (multiple-value-bind (local-name uri prefix)
118 (decode-qname qname env nil :allow-unknown-namespace t)
119 (when namespace
120 (setf uri namespace))
121 (lambda (ctx)
122 (cond
123 (uri
124 (with-element (local-name uri :suggested-prefix prefix)
125 (funcall body-thunk ctx)))
127 ;; ERROR rather than CERROR because saxon doesn't do the recovery,
128 ;; and the official output illustrates recovery but is useless as
129 ;; always.
130 (xslt-error "namespace not found: ~A" prefix)
131 #+(or)
132 (let ((*start-tag-written-p* t))
133 (declare (special *start-tag-written-p*))
134 (funcall body-thunk ctx)))))))
136 (defun compile-element/runtime (name-thunk ns-thunk body-thunk)
137 ;; run-time decoding of the QName, but using the same namespaces
138 ;; that would have been known at compilation time.
139 (let ((namespaces *namespaces*))
140 (lambda (ctx)
141 (let ((qname (funcall name-thunk ctx)))
142 (multiple-value-bind (local-name uri prefix)
143 (decode-qname/runtime qname namespaces nil)
144 (when ns-thunk
145 (setf uri (funcall ns-thunk ctx)))
146 (unless uri
147 (setf uri ""))
148 (with-element (local-name uri :suggested-prefix prefix)
149 (funcall body-thunk ctx)))))))
151 (define-instruction xsl:use-attribute-sets (args env)
152 (destructuring-bind (str) args
153 (let ((sets (mapcar (lambda (qname)
154 (multiple-value-list (decode-qname qname env nil)))
155 (words str))))
156 (lambda (ctx)
157 (loop for (local-name uri nil) in sets do
158 (dolist (thunk (find-attribute-set local-name uri))
159 (funcall thunk ctx)))))))
161 (define-instruction xsl:attribute (args env)
162 (destructuring-bind ((name &key namespace) &body body) args
163 (when (null name)
164 (xslt-error "xsl:attribute: name not specified"))
165 (multiple-value-bind (name-thunk constant-name-p)
166 (compile-avt name env)
167 (multiple-value-bind (ns-thunk constant-ns-p)
168 (if namespace
169 (compile-avt namespace env)
170 (values nil t))
171 (let ((value-thunk (compile-instruction `(progn ,@body) env)))
172 (if (and constant-name-p constant-ns-p)
173 (compile-attribute/constant-name name namespace env value-thunk)
174 (compile-attribute/runtime name-thunk ns-thunk value-thunk)))))))
176 (defun compile-attribute/constant-name (qname namespace env value-thunk)
177 ;; the simple case: compile-time decoding of the QName
178 (multiple-value-bind (local-name uri prefix)
179 (decode-qname qname env t :allow-unknown-namespace t)
180 (when namespace
181 (setf uri namespace))
182 (lambda (ctx)
183 (write-attribute local-name
184 (or uri "")
185 (with-toplevel-text-output-sink (s)
186 (with-xml-output s
187 (funcall value-thunk ctx)))
188 :suggested-prefix prefix))))
190 (defun compile-attribute/runtime (name-thunk ns-thunk value-thunk)
191 ;; run-time decoding of the QName, but using the same namespaces
192 ;; that would have been known at compilation time.
193 (let ((namespaces *namespaces*))
194 (lambda (ctx)
195 (let ((qname (funcall name-thunk ctx)))
196 (multiple-value-bind (local-name uri prefix)
197 (decode-qname/runtime qname namespaces t)
198 (when ns-thunk
199 (setf uri (funcall ns-thunk ctx)))
200 (write-attribute local-name
201 (or uri "")
202 (with-toplevel-text-output-sink (s)
203 (with-xml-output s
204 (funcall value-thunk ctx)))
205 :suggested-prefix prefix))))))
207 ;; zzz Also elides (later) namespaces hidden by (earlier) ones.
208 ;; zzz Reverses order.
210 ;; zzz fix the huge kludge that included-after-all-for-weird-reason-uris is
212 (defun remove-excluded-namespaces
213 (namespaces &optional (excluded-uris *excluded-namespaces*)
214 included-after-all-for-weird-reason-uris)
215 (let ((koerbchen '())
216 (kroepfchen '()))
217 (loop
218 for cons in namespaces
219 for (prefix* . uri) = cons
220 for prefix = (or prefix* "")
222 (cond
223 ((find prefix kroepfchen :test #'equal))
224 ((find prefix koerbchen :test #'equal :key #'car))
225 ((and (find uri excluded-uris :test #'equal)
226 (not (find uri included-after-all-for-weird-reason-uris
227 :test #'equal)))
228 (push prefix kroepfchen))
230 (push cons koerbchen))))
231 koerbchen))
233 ;; FIXME!
234 (defun collect-literal-attribute-namespaces-KLUDGE (body)
235 (loop
236 for frob in body
237 when (and (consp frob) (eq (car frob) 'xsl:literal-attribute))
238 collect (second (second frob))))
240 ;; FIXME!
241 (defun not-actually-excluded-namespaces-KLUDGE (element-uri body)
242 (mapcan (lambda (uri)
243 (multiple-value-bind (unaliased-uri matchp)
244 (gethash uri (stylesheet-namespace-aliases *stylesheet*))
245 (if matchp
246 (list unaliased-uri)
247 nil)))
248 (cons element-uri
249 (remove-if
250 (lambda (x) (zerop (length x)))
251 (collect-literal-attribute-namespaces-KLUDGE body)))))
253 (define-instruction xsl:literal-element (args env)
254 (destructuring-bind
255 ((local-name &optional (uri "") suggested-prefix) &body body)
256 args
257 (let ((body-thunk (compile-instruction `(progn ,@body) env))
258 (namespaces (remove-excluded-namespaces
259 *namespaces*
260 *excluded-namespaces*
261 (not-actually-excluded-namespaces-KLUDGE uri body))))
262 (lambda (ctx)
263 (with-element (local-name (or uri "")
264 :suggested-prefix suggested-prefix
265 :extra-namespaces namespaces
266 :process-aliases t)
267 (funcall body-thunk ctx))))))
269 (define-instruction xsl:literal-attribute (args env)
270 (destructuring-bind ((local-name &optional uri suggested-prefix) value) args
271 (let ((value-thunk (compile-avt value env)))
272 (lambda (ctx)
273 (write-attribute local-name
275 (funcall value-thunk ctx)
276 :process-aliases t
277 :suggested-prefix suggested-prefix)))))
279 (define-instruction xsl:text (args env)
280 (destructuring-bind (str) args
281 (lambda (ctx)
282 (declare (ignore ctx))
283 (write-text str))))
285 (define-instruction xsl:unescaped-text (args env)
286 (destructuring-bind (str) args
287 (lambda (ctx)
288 (declare (ignore ctx))
289 (write-unescaped str))))
291 (define-instruction xsl:processing-instruction (args env)
292 (destructuring-bind (name &rest body) args
293 (let ((name-thunk (compile-avt name env))
294 (value-thunk (compile-instruction `(progn ,@body) env)))
295 (lambda (ctx)
296 (write-processing-instruction
297 (funcall name-thunk ctx)
298 (with-toplevel-text-output-sink (s)
299 (with-xml-output s
300 (funcall value-thunk ctx))))))))
302 (define-instruction xsl:comment (args env)
303 (let ((value-thunk (compile-instruction `(progn ,@args) env)))
304 (lambda (ctx)
305 (write-comment (with-toplevel-text-output-sink (s)
306 (with-xml-output s
307 (funcall value-thunk ctx)))))))
309 (define-instruction xsl:value-of (args env)
310 (destructuring-bind (xpath) args
311 (let ((thunk (compile-xpath xpath env)))
312 (xslt-trace-thunk
313 (lambda (ctx)
314 (write-text (xpath:string-value (funcall thunk ctx))))
315 "value-of ~s = ~s" xpath :result))))
317 (define-instruction xsl:unescaped-value-of (args env)
318 (destructuring-bind (xpath) args
319 (let ((thunk (compile-xpath xpath env)))
320 (lambda (ctx)
321 (write-unescaped (xpath:string-value (funcall thunk ctx)))))))
323 (define-instruction xsl:copy-of (args env)
324 (destructuring-bind (xpath) args
325 (let ((thunk (compile-xpath xpath env))
326 ;; FIXME: what was this for? --david
327 #+(or) (v (intern-variable "varName" "")))
328 (xslt-trace-thunk
329 (lambda (ctx)
330 (let ((result (funcall thunk ctx)))
331 (typecase result
332 (xpath:node-set ;; FIXME: variables can contain node sets w/fragments inside. Maybe just fragments would do?
333 (xpath:map-node-set #'copy-into-result (xpath:sort-node-set result)))
334 (result-tree-fragment
335 (copy-into-result result))
337 (write-text (xpath:string-value result))))))
338 "copy-of ~s" xpath))))
340 (defun copy-into-result (node)
341 (cond
342 ((result-tree-fragment-p node)
343 (stp:do-children (child (result-tree-fragment-node node))
344 (copy-into-result child)))
345 ((xpath-protocol:node-type-p node :element)
346 (with-element ((xpath-protocol:local-name node)
347 (xpath-protocol:namespace-uri node)
348 :suggested-prefix (xpath-protocol:namespace-prefix node)
349 :extra-namespaces (namespaces-as-alist node))
350 (map-pipe-eagerly #'copy-into-result
351 (xpath-protocol:attribute-pipe node))
352 (map-pipe-eagerly #'copy-into-result
353 (xpath-protocol:child-pipe node))))
354 ((xpath-protocol:node-type-p node :document)
355 (map-pipe-eagerly #'copy-into-result
356 (xpath-protocol:child-pipe node)))
358 (copy-leaf-node node))))
360 (defparameter *lower-first-order*
361 #(#\ #\! #\" #\# #\$ #\% #\& #\' #\( #\) #\* #\+ #\, #\- #\. #\/ #\0 #\1 #\2
362 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\: #\; #\< #\= #\> #\? #\@ #\H #\J #\L #\N #\P
363 #\R #\T #\V #\X #\Z #\\ #\^ #\` #\b #\d #\f #\h #\j #\l #\n #\p #\r #\t #\v
364 #\x #\z #\A #\B #\C #\D #\E #\F #\G #\I #\K #\M #\O #\Q #\S #\U #\W #\Y #\[
365 #\] #\_ #\a #\c #\e #\g #\i #\k #\m #\o #\q #\s #\u #\w #\y #\{ #\| #\} #\~
366 #\Rubout))
368 (defparameter *upper-first-order*
369 #(#\ #\! #\" #\# #\$ #\% #\& #\' #\( #\) #\* #\+ #\, #\- #\. #\/ #\0 #\1 #\2
370 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\: #\; #\< #\= #\> #\? #\@ #\G #\I #\K #\M #\O
371 #\Q #\S #\U #\W #\Y #\[ #\] #\_ #\a #\c #\e #\g #\i #\k #\m #\o #\q #\s #\u
372 #\w #\y #\A #\B #\C #\D #\E #\F #\H #\J #\L #\N #\P #\R #\T #\V #\X #\Z #\\
373 #\^ #\` #\b #\d #\f #\h #\j #\l #\n #\p #\r #\t #\v #\x #\z #\{ #\| #\} #\~
374 #\Rubout))
376 (defun collation-char (char table)
377 (let ((code (char-code char)))
378 (if (<= 32 code 127)
379 (elt table (- code 32))
380 char)))
382 (defun make-collation-key (str table)
383 (map 'string (lambda (char) (collation-char char table)) str))
385 (defun compare-numbers (n-a n-b)
386 (cond ((and (xpath::nan-p n-a)
387 (not (xpath::nan-p n-b)))
389 ((and (not (xpath::nan-p n-a))
390 (xpath::nan-p n-b))
392 ((xpath::compare-numbers '< n-a n-b) -1)
393 ((xpath::compare-numbers '> n-a n-b) 1)
394 (t 0)))
396 (defun mismatch* (a b)
397 (let ((pos (mismatch a b)))
398 (if (and pos (< pos (min (length a) (length b))))
400 nil)))
402 (defun compare-strings (i j char-table)
403 ;; zzz Unicode support!
404 (let ((pos
405 (or (mismatch* (string-downcase i) (string-downcase j))
406 (mismatch* i j))))
407 (if pos
408 (let ((c (collation-char (elt i pos) char-table))
409 (d (collation-char (elt j pos) char-table)))
410 (cond
411 ((char< c d) -1)
412 ((char= c d) 0)
413 (t 1)))
414 (signum (- (length i) (length j))))))
416 (defun sort/@data-type (str)
417 (cond
418 ((equal str "number")
420 ((or (null str) (equal str "text"))
421 nil)
423 (xslt-error "invalid data-type in sort"))))
425 (defun sort/@case-order (str)
426 (cond
427 ((equal str "lower-first")
428 *lower-first-order*)
429 ((or (null str) (equal str "upper-first"))
430 *upper-first-order*)
432 (xslt-error "invalid case-order in sort"))))
434 (defun sort/@order (str)
435 (cond
436 ((equal str "descending")
438 ((or (null str) (equal str "ascending"))
441 (xslt-error "invalid order in sort"))))
443 (defun compile-optional-avt (template-string env)
444 (if template-string
445 (compile-avt template-string env)
446 (values (constantly nil) t)))
448 (defun make-sorter/lazy (spec env)
449 (destructuring-bind (&key select lang data-type order case-order)
450 (cdr spec)
451 (let ((select-thunk (compile-xpath (or select ".") env))
452 (lang-thunk (compile-optional-avt lang env))
453 (data-type-thunk (compile-optional-avt data-type env))
454 (order-thunk (compile-optional-avt order env))
455 (case-order-thunk (compile-optional-avt case-order env)))
456 (lambda (ctx)
457 (let ((numberp (sort/@data-type (funcall data-type-thunk ctx)))
458 (char-table (sort/@case-order (funcall case-order-thunk ctx)))
459 (f (sort/@order (funcall order-thunk ctx)))
460 (lang (funcall lang-thunk ctx)))
461 (declare (ignore lang))
462 (lambda (a b)
463 (let ((i (xpath:string-value (funcall select-thunk a)))
464 (j (xpath:string-value (funcall select-thunk b))))
465 (* f
466 (if numberp
467 (compare-numbers (xpath:number-value i)
468 (xpath:number-value j))
469 (compare-strings i j char-table))))))))))
471 (defun compose-sorters/lazy (sorters)
472 (if sorters
473 (let ((this-thunk (car sorters))
474 (next-thunk (compose-sorters/lazy (rest sorters))))
475 (lambda (ctx)
476 (let ((this (funcall this-thunk ctx))
477 (next (funcall next-thunk ctx)))
478 (lambda (a b)
479 (let ((d (funcall this a b)))
480 (if (zerop d)
481 (funcall next a b)
482 d))))))
483 (lambda (ctx)
484 (declare (ignore ctx))
485 (constantly 0))))
487 (defun make-sort-predicate/lazy (decls env)
488 (let ((sorter-thunk
489 (compose-sorters/lazy
490 (mapcar (lambda (x) (make-sorter/lazy x env)) decls))))
491 (lambda (ctx)
492 (let ((sorter (funcall sorter-thunk ctx)))
493 (lambda (a b)
494 (minusp (funcall sorter a b)))))))
496 (defun contextify-node-list (nodes)
497 (let ((size (length nodes)))
498 (loop
499 for position from 1
500 for node in nodes
501 collect
502 (xpath:make-context node size position))))
504 (define-instruction xsl:for-each (args env)
505 (destructuring-bind (select &optional decls &rest body) args
506 (unless (and (consp decls)
507 (eq (car decls) 'declare))
508 (push decls body)
509 (setf decls nil))
510 (let ((select-thunk (compile-xpath select env))
511 (body-thunk (compile-instruction `(progn ,@body) env))
512 (sort-predicate-thunk
513 (when (cdr decls)
514 (make-sort-predicate/lazy (cdr decls) env))))
515 (lambda (ctx)
516 (let ((selected (funcall select-thunk ctx))
517 (*apply-imports*
518 (lambda (&optional ignore)
519 (declare (ignore ignore))
520 (xslt-error "apply-imports used in for-each"))))
521 (unless (xpath:node-set-p selected)
522 (xslt-error "for-each select expression should yield a node-set"))
523 (let ((nodes (xpath::force (xpath::sorted-pipe-of selected))))
524 (when sort-predicate-thunk
525 (setf nodes
526 (mapcar #'xpath:context-node
527 (stable-sort (contextify-node-list nodes)
528 (funcall sort-predicate-thunk ctx)))))
529 (dolist (ctx (contextify-node-list nodes))
530 (funcall body-thunk ctx))))))))
532 (define-instruction xsl:with-namespaces (args env)
533 (destructuring-bind ((&rest forms) &rest body) args
534 (let ((*namespaces* *namespaces*))
535 (dolist (form forms)
536 (destructuring-bind (prefix uri) form
537 (push (cons prefix uri) *namespaces*)))
538 (compile-instruction `(progn ,@body) env))))
540 (define-instruction xsl:with-excluded-namespaces (args env)
541 (destructuring-bind ((&rest uris) &rest body) args
542 (let ((*excluded-namespaces* (append uris *excluded-namespaces*)))
543 (compile-instruction `(progn ,@body) env))))
545 (define-instruction xsl:with-extension-namespaces (args env)
546 (destructuring-bind ((&rest uris) &rest body) args
547 (let ((*extension-namespaces* (append uris *extension-namespaces*)))
548 (compile-instruction `(progn ,@body) env))))
550 (define-instruction xsl:with-version (args env)
551 (destructuring-bind (version &rest body) args
552 (let ((*forwards-compatible-p* (not (equal version "1.0"))))
553 (compile-instruction `(progn ,@body) env))))
555 ;; XSLT disallows multiple definitions of the same variable within a
556 ;; template. Local variables can shadow global variables though.
557 ;; Since our LET syntax makes it natural to shadow local variables the
558 ;; Lisp way, we check for duplicate variables only where instructed to
559 ;; by the XML syntax parser using WITH-DUPLICATES-CHECK:
560 (defvar *template-variables* nil)
562 (define-instruction xsl:with-duplicates-check (args env)
563 (let ((*template-variables* *template-variables*))
564 (destructuring-bind ((&rest qnames) &rest body) args
565 (dolist (qname qnames)
566 (multiple-value-bind (local-name uri)
567 (decode-qname qname env nil)
568 (let ((key (cons local-name uri)))
569 (when (find key *template-variables* :test #'equal)
570 (xslt-error "duplicate variable: ~A, ~A" local-name uri))
571 (push key *template-variables*))))
572 (compile-instruction `(progn ,@body) env))))
574 (define-instruction xsl:with-base-uri (args env)
575 (destructuring-bind (uri &rest body) args
576 (let ((*instruction-base-uri* uri))
577 (compile-instruction `(progn ,@body) env))))
579 (defstruct (result-tree-fragment
580 (:constructor make-result-tree-fragment (node)))
581 node)
583 (define-default-method xpath-protocol:node-p
584 ((node result-tree-fragment))
587 (define-default-method xpath-protocol:node-text
588 ((node result-tree-fragment))
589 (xpath-protocol:node-text (result-tree-fragment-node node)))
591 (defun apply-to-result-tree-fragment (ctx thunk)
592 (let ((document
593 (with-xml-output (make-stpx-builder)
594 (with-element ("fragment" "")
595 (funcall thunk ctx)))))
596 (make-result-tree-fragment (stp:document-element document))))
598 (defun compile-var-bindings/nointern (forms env)
599 (loop
600 for (name value) in forms
601 collect (multiple-value-bind (local-name uri)
602 (decode-qname name env nil)
603 (list (cons local-name uri)
604 (xslt-trace-thunk
605 (compile-value-thunk value env)
606 "local variable ~s = ~s" name :result)))))
608 (define-instruction let (args env)
609 (destructuring-bind ((&rest forms) &rest body) args
610 (let* ((old-top (length *lexical-variable-declarations*))
611 (vars-and-names (compile-var-bindings/nointern forms env))
612 (vars-and-positions
613 (loop for ((local-name . uri) thunk) in vars-and-names
614 collect
615 (list (push-variable local-name
617 *lexical-variable-declarations*)
618 thunk))))
619 (let ((thunk (compile-instruction `(progn ,@body) env)))
620 (fill *lexical-variable-declarations* nil :start old-top)
621 (lambda (ctx)
622 (loop for (index var-thunk) in vars-and-positions
623 do (setf (lexical-variable-value index)
624 (funcall var-thunk ctx)))
625 (funcall thunk ctx))))))
627 (define-instruction let* (args env)
628 (destructuring-bind ((&rest forms) &rest body) args
629 (if forms
630 (compile-instruction `(let (,(car forms))
631 (let* (,@(cdr forms))
632 ,@body))
633 env)
634 (compile-instruction `(progn ,@body) env))))
636 (define-instruction xsl:message (args env)
637 (compile-message #'warn args env))
639 (define-instruction xsl:terminate (args env)
640 (compile-message #'xslt-error args env))
642 (defun namespaces-as-alist (element)
643 (let ((namespaces '()))
644 (do-pipe (ns (xpath-protocol:namespace-pipe element))
645 (push (cons (xpath-protocol:local-name ns)
646 (xpath-protocol:node-text ns))
647 namespaces))
648 namespaces))
650 (define-instruction xsl:copy (args env)
651 (let ((body (compile-instruction `(progn ,@args) env)))
652 (lambda (ctx)
653 (let ((node (xpath:context-node ctx)))
654 (cond
655 ((xpath-protocol:node-type-p node :element)
656 (with-element
657 ((xpath-protocol:local-name node)
658 (xpath-protocol:namespace-uri node)
659 :suggested-prefix (xpath-protocol:namespace-prefix node)
660 :extra-namespaces (namespaces-as-alist node))
661 (funcall body ctx)))
662 ((xpath-protocol:node-type-p node :document)
663 (funcall body ctx))
665 (copy-leaf-node node)))))))
667 (defun copy-leaf-node (node)
668 (cond
669 ((xpath-protocol:node-type-p node :text)
670 (etypecase (if (typep node 'stripping-node)
671 (stripping-node-target node)
672 node)
673 (unescaped-text (write-unescaped (xpath-protocol:node-text node)))
674 (stp:text (write-text (xpath-protocol:node-text node)))))
675 ((xpath-protocol:node-type-p node :comment)
676 (write-comment (xpath-protocol:node-text node)))
677 ((xpath-protocol:node-type-p node :processing-instruction)
678 (write-processing-instruction
679 (xpath-protocol:processing-instruction-target node)
680 (xpath-protocol:node-text node)))
681 ((xpath-protocol:node-type-p node :attribute)
682 (write-attribute
683 (xpath-protocol:local-name node)
684 (xpath-protocol:namespace-uri node)
685 (xpath-protocol:node-text node)
686 :suggested-prefix (xpath-protocol:namespace-prefix node)))
687 ((xpath-protocol:node-type-p node :namespace)
688 (write-extra-namespace
689 (xpath-protocol:local-name node)
690 (xpath-protocol:node-text node)
691 nil))
693 (error "don't know how to copy node ~A" node))))
695 (defun compile-message (fn args env)
696 (let ((thunk (compile-instruction `(progn ,@args) env)))
697 (lambda (ctx)
698 (funcall fn
699 (with-xml-output (cxml:make-string-sink)
700 (funcall thunk ctx))))))
702 (define-instruction xsl:apply-templates (args env)
703 (destructuring-bind ((&key select mode) &rest param-binding-specs) args
704 (let* ((decls
705 (when (and (consp (car param-binding-specs))
706 (eq (caar param-binding-specs) 'declare))
707 (cdr (pop param-binding-specs))))
708 (select-thunk
709 (compile-xpath (or select "child::node()") env))
710 (param-bindings
711 (compile-var-bindings param-binding-specs env))
712 (sort-predicate-thunk
713 (when decls
714 (make-sort-predicate/lazy decls env))))
715 (multiple-value-bind (mode-local-name mode-uri)
716 (and mode (decode-qname mode env nil))
717 (lambda (ctx)
718 (apply-templates/list
719 (xpath::force
720 (xpath::sorted-pipe-of (funcall select-thunk ctx)))
721 :param-bindings
722 (loop for (name nil value-thunk) in param-bindings
723 collect (list name (funcall value-thunk ctx)))
724 :sort-predicate (when sort-predicate-thunk
725 (funcall sort-predicate-thunk ctx))
726 :mode (when mode
727 (or (find-mode *stylesheet*
728 mode-local-name
729 mode-uri)
730 *empty-mode*))))))))
732 (define-instruction xsl:apply-imports (args env)
733 (declare (ignore args env))
734 (lambda (ctx)
735 (declare (ignore ctx))
736 (funcall *apply-imports*)))
738 (define-instruction xsl:call-template (args env)
739 (destructuring-bind (name &rest param-binding-specs) args
740 (let ((param-bindings
741 (compile-var-bindings param-binding-specs env)))
742 (multiple-value-bind (local-name uri)
743 (decode-qname name env nil)
744 (setf name (cons local-name uri)))
745 (lambda (ctx)
746 (call-template ctx name
747 (loop for (name nil value-thunk) in param-bindings
748 collect (list name (funcall value-thunk ctx))))))))
750 ;; fixme: incompatible with XSLT 2.0
751 (define-instruction xsl:document (args env)
752 (destructuring-bind ((href &key method indent doctype-public doctype-system)
753 &body body)
754 args
755 (declare (ignore doctype-public doctype-system)) ;fixme
756 (let ((thunk (compile-instruction `(progn ,@body) env))
757 (href-thunk (compile-avt href env)))
758 (lambda (ctx)
759 (let ((pathname
760 (uri-to-pathname
761 (puri:merge-uris (funcall href-thunk ctx)
762 (xpath-protocol:base-uri
763 (xpath:context-node ctx))))))
764 (ensure-directories-exist pathname) ;really?
765 (invoke-with-output-sink
766 (lambda ()
767 (funcall thunk ctx))
768 (make-output-specification :method (or method "XML") :indent indent)
769 pathname))))))
771 (defun compile-instruction (form env)
772 (xslt-trace-thunk
773 (funcall (or (get (car form) 'xslt-instruction)
774 (error "undefined instruction: ~A" (car form)))
775 (cdr form)
776 env)
777 "instruction ~s" (car form)))
779 ;;: WTF: "A right curly brace inside a Literal in an expression is not
780 ;;; recognized as terminating the expression."
782 ;;; Da hilft nur tagbody.
783 (defun parse-attribute-value-template (template-string)
784 (with-input-from-string (input template-string)
785 (let ((ordinary (make-string-output-stream))
786 (xpath (make-string-output-stream))
787 (tokens '())
788 (c (read-char input nil :eof)))
789 (flet ((emit ()
790 (let ((o (get-output-stream-string ordinary)))
791 (when (plusp (length o))
792 (push (list :data o) tokens)))
793 (let ((x (get-output-stream-string xpath)))
794 (when (plusp (length x))
795 (push (list :xpath x) tokens))))
796 (collect-ordinary ()
797 (write-char c ordinary))
798 (collect-xpath ()
799 (write-char c xpath)))
800 (macrolet ((goto (target)
801 `(progn
802 (setf c (read-char input nil :eof))
803 (go ,target))))
804 (tagbody
805 ordinary
806 (case c
807 (#\{
808 (goto seen{))
809 (#\}
810 (goto seen-stray-}))
811 (:eof
812 (go done)))
813 (collect-ordinary)
814 (goto ordinary)
816 seen{
817 (case c
818 (#\{
819 (collect-ordinary)
820 (goto ordinary))
821 (#\'
822 (collect-xpath)
823 (goto in-single-quote))
824 (:eof
825 (xslt-error "unexpected end of avt")))
826 (emit)
827 (collect-xpath)
828 (goto xpath)
830 xpath
831 (case c
832 (#\'
833 (collect-xpath)
834 (goto in-single-quote))
835 (#\"
836 (collect-xpath)
837 (goto in-double-quote))
838 (#\}
839 (goto seen-closing-}))
840 (:eof
841 (xslt-error "unexpected end of avt")))
842 (collect-xpath)
843 (goto xpath)
845 in-single-quote
846 (case c
847 (#\'
848 (collect-xpath)
849 (goto xpath))
850 (:eof
851 (xslt-error "unexpected end of avt")))
852 (collect-xpath)
853 (goto in-single-quote)
855 in-double-quote
856 (case c
857 (#\"
858 (collect-xpath)
859 (goto xpath))
860 (:eof
861 (xslt-error "unexpected end of avt")))
862 (collect-xpath)
863 (goto in-double-quote)
865 seen-closing-}
866 (case c
867 (#\}
868 (emit)
869 (goto seen-stray-}))
870 (#\{
871 (emit)
872 (goto xpath))
873 (:eof
874 (goto done)))
875 (emit)
876 (collect-ordinary)
877 (goto ordinary)
879 seen-stray-}
880 (case c
881 (#\}
882 (collect-ordinary)
883 (goto ordinary)))
884 (xslt-error "unexpected closing brace in avt")
886 done
887 (emit))))
888 (nreverse tokens))))
890 (defun compile-avt (template-string env)
891 (let* ((constantp t)
892 (fns
893 (mapcar (lambda (x)
894 (ecase (car x)
895 (:data
896 (constantly (second x)))
897 (:xpath
898 (setf constantp nil)
899 (compile-xpath (second x) env))))
900 (if template-string
901 (parse-attribute-value-template template-string)
902 (xslt-error "missing avt")))))
903 (values (lambda (ctx)
904 (with-output-to-string (s)
905 (dolist (fn fns)
906 (write-string (xpath:string-value (funcall fn ctx)) s))))
907 constantp)))
910 ;;;; Indentation for slime
912 (defmacro define-indentation (name (&rest args))
913 (labels ((collect-variables (list)
914 (loop
915 for sub in list
916 append
917 (etypecase sub
918 (list
919 (collect-variables sub))
920 (symbol
921 (if (eql (mismatch "&" (symbol-name sub)) 1)
923 (list sub)))))))
924 `(defmacro ,name (,@args)
925 (declare (ignorable ,@(collect-variables args)))
926 (error "XSL indentation helper ~A used literally in lisp code"
927 ',name))))
929 (define-indentation xsl:element
930 ((name &key namespace use-attribute-sets) &body body))
931 (define-indentation xsl:literal-element ((name &optional uri) &body body))
932 (define-indentation xsl:attribute ((name &key namespace) &body body))
933 (define-indentation xsl:literal-attribute ((name &optional uri) &body body))
934 (define-indentation xsl:text (str))
935 (define-indentation xsl:processing-instruction (name &body body))
936 (define-indentation xsl:comment (&body body))
937 (define-indentation xsl:value-of (xpath))
938 (define-indentation xsl:unescaped-value-of (xpath))
939 (define-indentation xsl:for-each (select &body decls-and-body))
940 (define-indentation xsl:message (&body body))
941 (define-indentation xsl:terminate (&body body))
942 (define-indentation xsl:apply-templates ((&key select mode) &body decls-and-body))
943 (define-indentation xsl:call-template (name &rest parameters))
944 (define-indentation xsl:copy-of (xpath))
946 ;;;;
948 (defun test-instruction (form document)
949 (let ((thunk (compile-instruction form (make-instance 'lexical-environment)))
950 (root (cxml:parse document (stp:make-builder))))
951 (with-xml-output (cxml:make-string-sink)
952 (funcall thunk (xpath:make-context root)))))