Implemented format-number()
[xuriella.git] / instructions.lisp
blobc90899e226ff0d4594eda86b83b499d775ef9ea3
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
7 ;;; are met:
8 ;;;
9 ;;; * Redistributions of source code must retain the above copyright
10 ;;; notice, this list of conditions and the following disclaimer.
11 ;;;
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.
16 ;;;
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)))
32 ;;;; Instructions
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))
38 ,@body)))
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))))
45 (lambda (ctx)
46 (cond
47 ((xpath:boolean-value (funcall test-thunk ctx))
48 (funcall then-thunk ctx))
49 (else-thunk
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)
61 (if args
62 (destructuring-bind ((test &body body) &rest clauses) args
63 (compile-instruction (if (eq test t)
64 `(progn ,@body)
65 `(if ,test
66 (progn ,@body)
67 (cond ,@clauses)))
68 env))
69 (constantly nil)))
71 (define-instruction progn (args env)
72 (if args
73 (let ((first-thunk (compile-instruction (first args) env))
74 (rest-thunk (compile-instruction `(progn ,@(rest args)) env)))
75 (lambda (ctx)
76 (funcall first-thunk ctx)
77 (funcall rest-thunk ctx)))
78 (constantly nil)))
80 (defun decode-qname/runtime (qname namespaces attributep)
81 (handler-case
82 (multiple-value-bind (prefix local-name)
83 (split-qname qname)
84 (values local-name
85 (if (or prefix (not attributep))
86 (cdr (assoc prefix namespaces :test 'equal))
87 "")
88 prefix))
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)
94 &body body)
95 args
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)))
100 (if constant-name-p
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)
108 (when namespace
109 (setf uri namespace))
110 (lambda (ctx)
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*))
118 (lambda (ctx)
119 (let ((qname (funcall name-thunk ctx)))
120 (multiple-value-bind (local-name uri prefix)
121 (decode-qname/runtime qname namespaces nil)
122 (when namespace
123 (setf uri namespace))
124 (lambda (ctx)
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)))
133 (if constant-name-p
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)
141 (when namespace
142 (setf uri namespace))
143 (lambda (ctx)
144 (write-attribute local-name
146 (with-text-output-sink (s)
147 (with-xml-output 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*))
155 (lambda (ctx)
156 (let ((qname (funcall name-thunk ctx)))
157 (multiple-value-bind (local-name uri prefix)
158 (decode-qname/runtime qname namespaces nil)
159 (when namespace
160 (setf uri namespace))
161 (lambda (ctx)
162 (write-attribute local-name
164 (with-text-output-sink (s)
165 (with-xml-output 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 '())
172 (kroepfchen '()))
173 (loop
174 for cons in namespaces
175 for (prefix . uri) = cons
177 (cond
178 ((find prefix kroepfchen :test #'equal))
179 ((find uri excluded-uris :test #'equal)
180 (push prefix kroepfchen))
182 (push cons koerbchen))))
183 koerbchen))
185 (define-instruction xsl:literal-element (args env)
186 (destructuring-bind
187 ((local-name &optional (uri "") suggested-prefix) &body body)
188 args
189 (let ((body-thunk (compile-instruction `(progn ,@body) env))
190 (namespaces (remove-excluded-namespaces *namespaces*)))
191 (lambda (ctx)
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)))
200 (lambda (ctx)
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
208 (lambda (ctx)
209 (declare (ignore ctx))
210 (write-text str))))
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)))
216 (lambda (ctx)
217 (write-processing-instruction
218 (funcall name-thunk ctx)
219 (with-text-output-sink (s)
220 (with-xml-output s
221 (funcall value-thunk ctx))))))))
223 (define-instruction xsl:comment (args env)
224 (destructuring-bind (str) args
225 (lambda (ctx)
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)))
232 (lambda (ctx)
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)))
238 (lambda (ctx)
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" "")))
246 (lambda (ctx)
247 (let ((result (funcall thunk ctx)))
248 (typecase result
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)
257 (cond
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)))
282 (push decls body)
283 (setf decls nil))
284 (let ((select-thunk (compile-xpath select env))
285 (body-thunk (compile-instruction `(progn ,@body) env))
286 (sorter
287 ;; fixme: parse decls here
288 #'identity))
289 (lambda (ctx)
290 (let* ((nodes (xpath:all-nodes (funcall sorter (funcall select-thunk ctx))))
291 (n (length nodes)))
292 (loop
293 for node in nodes
294 for i from 1
296 (funcall body-thunk
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*))
302 (dolist (form forms)
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)))
333 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)
342 (let ((document
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))
352 (vars-and-positions
353 (loop for ((local-name . uri) thunk) in vars-and-names
354 collect
355 (list (push-variable local-name
357 *lexical-variable-declarations*)
358 thunk))))
359 (let ((thunk (compile-instruction `(progn ,@body) env)))
360 (fill *lexical-variable-declarations* nil :start old-top)
361 (lambda (ctx)
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
369 (if forms
370 (compile-instruction `(let (,(car forms))
371 (let* (,@(cdr forms))
372 ,@body))
373 env)
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))
387 namespaces))
388 namespaces))
390 (define-instruction xsl:copy (args env)
391 (destructuring-bind ((&key use-attribute-sets) &rest rest)
392 args
393 (declare (ignore use-attribute-sets))
394 (let ((body (compile-instruction `(progn ,@rest) env)))
395 (lambda (ctx)
396 (let ((node (xpath:context-node ctx)))
397 (cond
398 ((xpath-protocol:node-type-p node :element)
399 (with-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))
404 (funcall body ctx)))
405 ((xpath-protocol:node-type-p node :document)
406 (funcall body ctx))
408 (copy-leaf-node node))))))))
410 (defun copy-leaf-node (node)
411 (cond
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)
421 (write-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)))
431 (lambda (ctx)
432 (funcall fn
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
438 (let ((select-thunk
439 (compile-xpath (or select "child::node()") env))
440 (param-bindings
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))
444 (lambda (ctx)
445 (let ((*mode* (if mode
446 (or (find-mode *stylesheet*
447 mode-local-name
448 mode-uri)
449 *empty-mode*)
450 *mode*)))
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)))
463 (lambda (ctx)
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)))
471 (cdr form)
472 env))
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))
480 (constantp t)
481 (fns
482 (loop
483 collect
484 (multiple-value-bind (kind str) (funcall lexer)
485 (ecase kind
486 (:data
487 (constantly str))
488 (:xpath
489 (setf constantp nil)
490 (xpath:compile-xpath str env))
491 ((nil)
492 (return result))))
493 into result)))
494 (values (lambda (ctx)
495 (with-output-to-string (s)
496 (dolist (fn fns)
497 (write-string (xpath:string-value (funcall fn ctx)) s))))
498 constantp)))
501 ;;;; Indentation for slime
503 (defmacro define-indentation (name (&rest args))
504 (labels ((collect-variables (list)
505 (loop
506 for sub in list
507 append
508 (etypecase sub
509 (list
510 (collect-variables sub))
511 (symbol
512 (if (eql (mismatch "&" (symbol-name sub)) 1)
514 (list sub)))))))
515 `(defmacro ,name (,@args)
516 (declare (ignorable ,@(collect-variables args)))
517 (error "XSL indentation helper ~A used literally in lisp code"
518 ',name))))
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))
537 ;;;;
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)))))