Check for various disallowed attributes
[xuriella.git] / xslt.lisp
blob619f93ace75f68a8944fac795030348161517f63
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 (eval-when (:compile-toplevel :load-toplevel :execute)
37 (defvar *xsl* "http://www.w3.org/1999/XSL/Transform")
38 (defvar *xml* "http://www.w3.org/XML/1998/namespace")
39 (defvar *html* "http://www.w3.org/1999/xhtml"))
42 ;;;; XSLT-ERROR
44 (define-condition xslt-error (simple-error)
46 (:documentation "The class of all XSLT errors."))
48 (define-condition recoverable-xslt-error (xslt-error)
50 (:documentation "The class of recoverable XSLT errors."))
52 (defun xslt-error (fmt &rest args)
53 (error 'xslt-error :format-control fmt :format-arguments args))
55 (defun xslt-cerror (fmt &rest args)
56 (with-simple-restart (recover "recover")
57 (error 'recoverable-xslt-error
58 :format-control fmt
59 :format-arguments args)))
61 (defvar *debug* nil)
63 (defmacro handler-case* (form &rest clauses)
64 ;; like HANDLER-CASE if *DEBUG* is off. If it's on, don't establish
65 ;; a handler at all so that we see the real stack traces. (We could use
66 ;; HANDLER-BIND here and check at signalling time, but doesn't seem
67 ;; important.)
68 (let ((doit (gensym)))
69 `(flet ((,doit () ,form))
70 (if *debug*
71 (,doit)
72 (handler-case
73 (,doit)
74 ,@clauses)))))
76 (defun compile-xpath (xpath &optional env)
77 (handler-case*
78 (xpath:compile-xpath xpath env)
79 (xpath:xpath-error (c)
80 (xslt-error "~A" c))))
82 (defmacro with-stack-limit ((&optional) &body body)
83 `(invoke-with-stack-limit (lambda () ,@body)))
86 ;;;; Helper functions and macros
88 (defun check-for-invalid-attributes (valid-names node)
89 (labels ((check-attribute (a)
90 (unless
91 (let ((uri (stp:namespace-uri a)))
92 (or (and (plusp (length uri)) (not (equal uri *xsl*)))
93 (find (cons (stp:local-name a) uri)
94 valid-names
95 :test #'equal)))
96 (xslt-error "attribute ~A not allowed on ~A"
97 (stp:local-name a)
98 (stp:local-name node)))))
99 (stp:map-attributes nil #'check-attribute node)))
101 (defmacro only-with-attributes ((&rest specs) node &body body)
102 (let ((valid-names
103 (mapcar (lambda (entry)
104 (if (and (listp entry) (cdr entry))
105 (destructuring-bind (name &optional (uri ""))
106 (cdr entry)
107 (cons name uri))
108 (cons (string-downcase
109 (princ-to-string
110 (symbol-name entry)))
111 "")))
112 specs))
113 (%node (gensym)))
114 `(let ((,%NODE ,node))
115 (check-for-invalid-attributes ',valid-names ,%NODE)
116 (stp:with-attributes ,specs ,%NODE
117 ,@body))))
119 (defun map-pipe-eagerly (fn pipe)
120 (xpath::enumerate pipe :key fn :result nil))
122 (defmacro do-pipe ((var pipe &optional result) &body body)
123 `(block nil
124 (map-pipe-eagerly #'(lambda (,var) ,@body) ,pipe)
125 ,result))
128 ;;;; XSLT-ENVIRONMENT and XSLT-CONTEXT
130 (defparameter *namespaces*
131 '((nil . "")
132 ("xmlns" . #"http://www.w3.org/2000/xmlns/")
133 ("xml" . #"http://www.w3.org/XML/1998/namespace")))
135 (defvar *global-variable-declarations*)
136 (defvar *lexical-variable-declarations*)
138 (defvar *global-variable-values*)
139 (defvar *lexical-variable-values*)
141 (defclass xslt-environment () ())
143 (defun split-qname (str)
144 (handler-case
145 (multiple-value-bind (prefix local-name)
146 (cxml::split-qname str)
147 (unless
148 ;; FIXME: cxml should really offer a function that does
149 ;; checks for NCName and QName in a sensible way for user code.
150 ;; cxml::split-qname is tailored to the needs of the parser.
152 ;; For now, let's just check the syntax explicitly.
153 (and (or (null prefix) (xpath::nc-name-p prefix))
154 (xpath::nc-name-p local-name))
155 (xslt-error "not a qname: ~A" str))
156 (values prefix local-name))
157 (cxml:well-formedness-violation ()
158 (xslt-error "not a qname: ~A" str))))
160 (defun decode-qname (qname env attributep)
161 (multiple-value-bind (prefix local-name)
162 (split-qname qname)
163 (values local-name
164 (if (or prefix (not attributep))
165 (xpath-sys:environment-find-namespace env (or prefix ""))
167 prefix)))
169 (defmethod xpath-sys:environment-find-namespace ((env xslt-environment) prefix)
170 (or (cdr (assoc prefix *namespaces* :test 'equal))
171 ;; zzz gross hack.
172 ;; Change the entire code base to represent "no prefix" as the
173 ;; empty string consistently. unparse.lisp has already been changed.
174 (and (equal prefix "")
175 (cdr (assoc nil *namespaces* :test 'equal)))
176 (and (eql prefix nil)
177 (cdr (assoc "" *namespaces* :test 'equal)))))
179 (defun find-variable-index (local-name uri table)
180 (position (cons local-name uri) table :test 'equal))
182 (defun intern-global-variable (local-name uri)
183 (or (find-variable-index local-name uri *global-variable-declarations*)
184 (push-variable local-name uri *global-variable-declarations*)))
186 (defun push-variable (local-name uri table)
187 (prog1
188 (length table)
189 (vector-push-extend (cons local-name uri) table)))
191 (defun lexical-variable-value (index &optional (errorp t))
192 (let ((result (svref *lexical-variable-values* index)))
193 (when errorp
194 (assert (not (eq result 'unbound))))
195 result))
197 (defun (setf lexical-variable-value) (newval index)
198 (assert (not (eq newval 'unbound)))
199 (setf (svref *lexical-variable-values* index) newval))
201 (defun global-variable-value (index &optional (errorp t))
202 (let ((result (svref *global-variable-values* index)))
203 (when errorp
204 (assert (not (eq result 'unbound))))
205 result))
207 (defun (setf global-variable-value) (newval index)
208 (assert (not (eq newval 'unbound)))
209 (setf (svref *global-variable-values* index) newval))
211 (defmethod xpath-sys:environment-find-function
212 ((env xslt-environment) lname uri)
213 (if (string= uri "")
214 (or (xpath-sys:find-xpath-function lname *xsl*)
215 (xpath-sys:find-xpath-function lname uri))
216 (xpath-sys:find-xpath-function lname uri)))
218 (defmethod xpath-sys:environment-find-variable
219 ((env xslt-environment) lname uri)
220 (let ((index
221 (find-variable-index lname uri *lexical-variable-declarations*)))
222 (when index
223 (lambda (ctx)
224 (declare (ignore ctx))
225 (svref *lexical-variable-values* index)))))
227 (defclass lexical-xslt-environment (xslt-environment) ())
229 (defmethod xpath-sys:environment-find-variable
230 ((env lexical-xslt-environment) lname uri)
231 (or (call-next-method)
232 (let ((index
233 (find-variable-index lname uri *global-variable-declarations*)))
234 (when index
235 (xslt-trace-thunk
236 (lambda (ctx)
237 (declare (ignore ctx))
238 (svref *global-variable-values* index))
239 "global ~s (uri ~s) = ~s" lname uri :result)))))
241 (defclass global-variable-environment (xslt-environment)
242 ((initial-global-variable-thunks
243 :initarg :initial-global-variable-thunks
244 :accessor initial-global-variable-thunks)))
246 (defmethod xpath-sys:environment-find-variable
247 ((env global-variable-environment) lname uri)
248 (or (call-next-method)
249 (gethash (cons lname uri) (initial-global-variable-thunks env))))
252 ;;;; TOPLEVEL-TEXT-OUTPUT-SINK
253 ;;;;
254 ;;;; A sink that serializes only text not contained in any element.
256 (defmacro with-toplevel-text-output-sink ((var) &body body)
257 `(invoke-with-toplevel-text-output-sink (lambda (,var) ,@body)))
259 (defclass toplevel-text-output-sink (sax:default-handler)
260 ((target :initarg :target :accessor text-output-sink-target)
261 (depth :initform 0 :accessor textoutput-sink-depth)))
263 (defmethod sax:start-element ((sink toplevel-text-output-sink)
264 namespace-uri local-name qname attributes)
265 (declare (ignore namespace-uri local-name qname attributes))
266 (incf (textoutput-sink-depth sink)))
268 (defmethod sax:characters ((sink toplevel-text-output-sink) data)
269 (when (zerop (textoutput-sink-depth sink))
270 (write-string data (text-output-sink-target sink))))
272 (defmethod sax:unescaped ((sink toplevel-text-output-sink) data)
273 (sax:characters sink data))
275 (defmethod sax:end-element ((sink toplevel-text-output-sink)
276 namespace-uri local-name qname)
277 (declare (ignore namespace-uri local-name qname))
278 (decf (textoutput-sink-depth sink)))
280 (defun invoke-with-toplevel-text-output-sink (fn)
281 (with-output-to-string (s)
282 (funcall fn (make-instance 'toplevel-text-output-sink :target s))))
285 ;;;; TEXT-FILTER
286 ;;;;
287 ;;;; A sink that passes through only text (at any level) and turns to
288 ;;;; into unescaped characters.
290 (defclass text-filter (sax:default-handler)
291 ((target :initarg :target :accessor text-filter-target)))
293 (defmethod sax:characters ((sink text-filter) data)
294 (sax:unescaped (text-filter-target sink) data))
296 (defmethod sax:unescaped ((sink text-filter) data)
297 (sax:unescaped (text-filter-target sink) data))
299 (defmethod sax:end-document ((sink text-filter))
300 (sax:end-document (text-filter-target sink)))
302 (defun make-text-filter (target)
303 (make-instance 'text-filter :target target))
306 ;;;; ESCAPER
307 ;;;;
308 ;;;; A sink that recovers from sax:unescaped using sax:characters, as per
309 ;;;; XSLT 16.4.
311 (defclass escaper (cxml:broadcast-handler)
314 (defmethod sax:unescaped ((sink escaper) data)
315 (sax:characters sink data))
317 (defun make-escaper (target)
318 (make-instance 'escaper :handlers (list target)))
321 ;;;; Names
323 (defun of-name (local-name)
324 (stp:of-name local-name *xsl*))
326 (defun namep (node local-name)
327 (and (typep node '(or stp:element stp:attribute))
328 (equal (stp:namespace-uri node) *xsl*)
329 (equal (stp:local-name node) local-name)))
332 ;;;; PARSE-STYLESHEET
334 (defstruct stylesheet
335 (modes (make-hash-table :test 'equal))
336 (global-variables (make-empty-declaration-array))
337 (output-specification (make-output-specification))
338 (strip-tests nil)
339 (named-templates (make-hash-table :test 'equal))
340 (attribute-sets (make-hash-table :test 'equal))
341 (keys (make-hash-table :test 'equal))
342 (namespace-aliases (make-hash-table :test 'equal))
343 (decimal-formats (make-hash-table :test 'equal)))
345 (defstruct mode (templates nil))
347 (defun find-mode (stylesheet local-name &optional uri)
348 (gethash (cons local-name uri) (stylesheet-modes stylesheet)))
350 (defun ensure-mode (stylesheet &optional local-name uri)
351 (or (find-mode stylesheet local-name uri)
352 (setf (gethash (cons local-name uri) (stylesheet-modes stylesheet))
353 (make-mode))))
355 (defun ensure-mode/qname (stylesheet qname env)
356 (if qname
357 (multiple-value-bind (local-name uri)
358 (decode-qname qname env nil)
359 (ensure-mode stylesheet local-name uri))
360 (find-mode stylesheet nil)))
362 (defun acons-namespaces (element &optional (bindings *namespaces*))
363 (map-namespace-declarations (lambda (prefix uri)
364 (push (cons prefix uri) bindings))
365 element)
366 bindings)
368 (defun find-key (name stylesheet)
369 (or (gethash name (stylesheet-keys stylesheet))
370 (xslt-error "unknown key: ~a" name)))
372 (defun make-key (match use) (cons match use))
374 (defun key-match (key) (car key))
376 (defun key-use (key) (cdr key))
378 (defun add-key (stylesheet name match use)
379 (if (gethash name (stylesheet-keys stylesheet))
380 (xslt-error "duplicate key: ~a" name)
381 (setf (gethash name (stylesheet-keys stylesheet))
382 (make-key match use))))
384 (defvar *excluded-namespaces* (list *xsl*))
385 (defvar *empty-mode*)
386 (defvar *default-mode*)
388 (defvar *xsl-include-stack* nil)
390 (defun uri-to-pathname (uri)
391 (cxml::uri-to-pathname (puri:parse-uri uri)))
393 (defun parse-stylesheet-to-stp (input uri-resolver)
394 (let* ((d (cxml:parse input (make-text-normalizer (cxml-stp:make-builder))))
395 (<transform> (stp:document-element d)))
396 (strip-stylesheet <transform>)
397 ;; FIXME: handle embedded stylesheets
398 (unless (and (equal (stp:namespace-uri <transform>) *xsl*)
399 (or (equal (stp:local-name <transform>) "transform")
400 (equal (stp:local-name <transform>) "stylesheet")))
401 (xslt-error "not a stylesheet"))
402 (dolist (include (stp:filter-children (of-name "include") <transform>))
403 (let* ((uri (puri:merge-uris (stp:attribute-value include "href")
404 (stp:base-uri include)))
405 (uri (if uri-resolver
406 (funcall uri-resolver (puri:render-uri uri nil))
407 uri))
408 (str (puri:render-uri uri nil))
409 (pathname
410 (handler-case
411 (uri-to-pathname uri)
412 (cxml:xml-parse-error (c)
413 (xslt-error "cannot find included stylesheet ~A: ~A"
414 uri c)))))
415 (with-open-file
416 (stream pathname
417 :element-type '(unsigned-byte 8)
418 :if-does-not-exist nil)
419 (unless stream
420 (xslt-error "cannot find included stylesheet ~A at ~A"
421 uri pathname))
422 (when (find str *xsl-include-stack* :test #'equal)
423 (xslt-error "recursive inclusion of ~A" uri))
424 (let* ((*xsl-include-stack* (cons str *xsl-include-stack*))
425 (<transform>2 (parse-stylesheet-to-stp stream uri-resolver)))
426 (stp:insert-child-after <transform>
427 (stp:copy <transform>2)
428 include)
429 (stp:detach include)))))
430 <transform>))
432 (defvar *instruction-base-uri*) ;misnamed, is also used in other attributes
433 (defvar *apply-imports-limit*)
434 (defvar *import-priority*)
435 (defvar *extension-namespaces*)
436 (defvar *forwards-compatible-p*)
438 (defmacro do-toplevel ((var xpath <transform>) &body body)
439 `(map-toplevel (lambda (,var) ,@body) ,xpath ,<transform>))
441 (defun map-toplevel (fn xpath <transform>)
442 (dolist (node (list-toplevel xpath <transform>))
443 (let ((*namespaces* *namespaces*))
444 (xpath:do-node-set (ancestor (xpath:evaluate "ancestor::node()" node))
445 (when (xpath-protocol:node-type-p ancestor :element)
446 (setf *namespaces* (acons-namespaces ancestor))))
447 (funcall fn node))))
449 (defun list-toplevel (xpath <transform>)
450 (labels ((recurse (sub)
451 (let ((subsubs
452 (xpath-sys:pipe-of
453 (xpath:evaluate "transform|stylesheet" sub))))
454 (xpath::append-pipes
455 (xpath-sys:pipe-of (xpath:evaluate xpath sub))
456 (xpath::mappend-pipe #'recurse subsubs)))))
457 (xpath::sort-nodes (recurse <transform>))))
459 (defmacro with-import-magic ((node env) &body body)
460 `(invoke-with-import-magic (lambda () ,@body) ,node ,env))
462 (defun invoke-with-import-magic (fn node env)
463 (unless (or (namep node "stylesheet") (namep node "transform"))
464 (setf node (stp:parent node)))
465 (let ((*excluded-namespaces* (list *xsl*))
466 (*extension-namespaces* '())
467 (*forwards-compatible-p*
468 (not (equal (stp:attribute-value node "version") "1.0"))))
469 (parse-exclude-result-prefixes! node env)
470 (parse-extension-element-prefixes! node env)
471 (funcall fn)))
473 (defun parse-1-stylesheet (env stylesheet designator uri-resolver)
474 (let* ((<transform> (parse-stylesheet-to-stp designator uri-resolver))
475 (instruction-base-uri (stp:base-uri <transform>))
476 (namespaces (acons-namespaces <transform>))
477 (apply-imports-limit (1+ *import-priority*))
478 (continuations '()))
479 (let ((*namespaces* namespaces))
480 (invoke-with-import-magic (constantly t) <transform> env))
481 (macrolet ((with-specials ((&optional) &body body)
482 `(let ((*instruction-base-uri* instruction-base-uri)
483 (*namespaces* namespaces)
484 (*apply-imports-limit* apply-imports-limit))
485 ,@body)))
486 (with-specials ()
487 (do-toplevel (import "import" <transform>)
488 (let ((uri (puri:merge-uris (stp:attribute-value import "href")
489 (stp:base-uri import))))
490 (push (parse-imported-stylesheet env stylesheet uri uri-resolver)
491 continuations))))
492 (let ((import-priority
493 (incf *import-priority*))
494 (var-cont (prepare-global-variables stylesheet <transform>)))
495 ;; delay the rest of compilation until we've seen all global
496 ;; variables:
497 (lambda ()
498 (mapc #'funcall (nreverse continuations))
499 (with-specials ()
500 (let ((*import-priority* import-priority))
501 (funcall var-cont)
502 (parse-keys! stylesheet <transform> env)
503 (parse-templates! stylesheet <transform> env)
504 (parse-output! stylesheet <transform>)
505 (parse-strip/preserve-space! stylesheet <transform> env)
506 (parse-attribute-sets! stylesheet <transform> env)
507 (parse-namespace-aliases! stylesheet <transform> env)
508 (parse-decimal-formats! stylesheet <transform> env))))))))
510 (defvar *xsl-import-stack* nil)
512 (defun parse-imported-stylesheet (env stylesheet uri uri-resolver)
513 (let* ((uri (if uri-resolver
514 (funcall uri-resolver (puri:render-uri uri nil))
515 uri))
516 (str (puri:render-uri uri nil))
517 (pathname
518 (handler-case
519 (uri-to-pathname uri)
520 (cxml:xml-parse-error (c)
521 (xslt-error "cannot find imported stylesheet ~A: ~A"
522 uri c)))))
523 (with-open-file
524 (stream pathname
525 :element-type '(unsigned-byte 8)
526 :if-does-not-exist nil)
527 (unless stream
528 (xslt-error "cannot find imported stylesheet ~A at ~A"
529 uri pathname))
530 (when (find str *xsl-import-stack* :test #'equal)
531 (xslt-error "recursive inclusion of ~A" uri))
532 (let ((*xsl-import-stack* (cons str *xsl-import-stack*)))
533 (parse-1-stylesheet env stylesheet stream uri-resolver)))))
535 (defun parse-stylesheet (designator &key uri-resolver)
536 (xpath:with-namespaces ((nil #.*xsl*))
537 (let* ((*import-priority* 0)
538 (puri:*strict-parse* nil)
539 (stylesheet (make-stylesheet))
540 (env (make-instance 'lexical-xslt-environment))
541 (*excluded-namespaces* *excluded-namespaces*)
542 (*global-variable-declarations* (make-empty-declaration-array)))
543 (ensure-mode stylesheet nil)
544 (funcall (parse-1-stylesheet env stylesheet designator uri-resolver))
545 ;; reverse attribute sets:
546 (let ((table (stylesheet-attribute-sets stylesheet)))
547 (maphash (lambda (k v)
548 (setf (gethash k table) (nreverse v)))
549 table))
550 ;; add default df
551 (unless (find-decimal-format "" "" stylesheet nil)
552 (setf (find-decimal-format "" "" stylesheet)
553 (make-decimal-format)))
554 stylesheet)))
556 (defun parse-attribute-sets! (stylesheet <transform> env)
557 (do-toplevel (elt "attribute-set" <transform>)
558 (with-import-magic (elt env)
559 (push (let* ((sets
560 (mapcar (lambda (qname)
561 (multiple-value-list (decode-qname qname env nil)))
562 (words
563 (stp:attribute-value elt "use-attribute-sets"))))
564 (instructions
565 (stp:map-children
566 'list
567 (lambda (child)
568 (unless (or (not (typep child 'stp:element))
569 (and (equal (stp:namespace-uri child) *xsl*)
570 (equal (stp:local-name child)
571 "attribute"))
572 (find (stp:namespace-uri child)
573 *extension-namespaces*
574 :test 'equal))
575 (xslt-error "non-attribute found in attribute set"))
576 (parse-instruction child))
577 elt))
578 (*lexical-variable-declarations*
579 (make-empty-declaration-array))
580 (thunk
581 (compile-instruction `(progn ,@instructions) env))
582 (n-variables (length *lexical-variable-declarations*)))
583 (lambda (ctx)
584 (with-stack-limit ()
585 (loop for (local-name uri nil) in sets do
586 (dolist (thunk (find-attribute-set local-name uri))
587 (funcall thunk ctx)))
588 (let ((*lexical-variable-values*
589 (make-variable-value-array n-variables)))
590 (funcall thunk ctx)))))
591 (gethash (multiple-value-bind (local-name uri)
592 (decode-qname (stp:attribute-value elt "name") env nil)
593 (cons local-name uri))
594 (stylesheet-attribute-sets stylesheet))))))
596 (defun parse-namespace-aliases! (stylesheet <transform> env)
597 (do-toplevel (elt "namespace-alias" <transform>)
598 (stp:with-attributes (stylesheet-prefix result-prefix) elt
599 (setf (gethash
600 (xpath-sys:environment-find-namespace env stylesheet-prefix)
601 (stylesheet-namespace-aliases stylesheet))
602 (xpath-sys:environment-find-namespace
604 (if (equal result-prefix "#default")
606 result-prefix))))))
608 (defun parse-decimal-formats! (stylesheet <transform> env)
609 (do-toplevel (elt "decimal-format" <transform>)
610 (stp:with-attributes (name
611 ;; strings
612 infinity
613 (nan "NaN")
614 ;; characters:
615 decimal-separator
616 grouping-separator
617 zero-digit
618 percent
619 per-mille
620 digit
621 pattern-separator
622 minus-sign)
624 (multiple-value-bind (local-name uri)
625 (if name
626 (decode-qname name env nil)
627 (values "" ""))
628 (unless (find-decimal-format local-name uri stylesheet nil)
629 (setf (find-decimal-format local-name uri stylesheet)
630 (let ((seen '()))
631 (flet ((chr (key x)
632 (when x
633 (unless (eql (length x) 1)
634 (xslt-error "not a single character: ~A" x))
635 (let ((chr (elt x 0)))
636 (when (find chr seen)
637 (xslt-error
638 "conflicting decimal format characters: ~A"
639 chr))
640 (push chr seen)
641 (list key chr))))
642 (str (key x)
643 (when x
644 (list key x))))
645 (apply #'make-decimal-format
646 (append (str :infinity infinity)
647 (str :nan nan)
648 (chr :decimal-separator decimal-separator)
649 (chr :grouping-separator grouping-separator)
650 (chr :zero-digit zero-digit)
651 (chr :percent percent)
652 (chr :per-mille per-mille)
653 (chr :digit digit)
654 (chr :pattern-separator pattern-separator)
655 (chr :minus-sign minus-sign)))))))))))
657 (defun parse-exclude-result-prefixes! (node env)
658 (stp:with-attributes (exclude-result-prefixes)
659 node
660 (dolist (prefix (words (or exclude-result-prefixes "")))
661 (if (equal prefix "#default")
662 (setf prefix nil)
663 (unless (cxml-stp-impl::nc-name-p prefix)
664 (xslt-error "invalid prefix: ~A" prefix)))
665 (push (or (xpath-sys:environment-find-namespace env prefix)
666 (xslt-error "namespace not found: ~A" prefix))
667 *excluded-namespaces*))))
669 (defun parse-extension-element-prefixes! (node env)
670 (stp:with-attributes (extension-element-prefixes)
671 node
672 (dolist (prefix (words (or extension-element-prefixes "")))
673 (if (equal prefix "#default")
674 (setf prefix nil)
675 (unless (cxml-stp-impl::nc-name-p prefix)
676 (xslt-error "invalid prefix: ~A" prefix)))
677 (let ((uri
678 (or (xpath-sys:environment-find-namespace env prefix)
679 (xslt-error "namespace not found: ~A" prefix))))
680 (unless (equal uri *xsl*)
681 (push uri *extension-namespaces*)
682 (push uri *excluded-namespaces*))))))
684 (defun parse-strip/preserve-space! (stylesheet <transform> env)
685 (xpath:with-namespaces ((nil #.*xsl*))
686 (do-toplevel (elt "strip-space|preserve-space" <transform>)
687 (let ((*namespaces* (acons-namespaces elt))
688 (mode
689 (if (equal (stp:local-name elt) "strip-space")
690 :strip
691 :preserve)))
692 (dolist (name-test (words (stp:attribute-value elt "elements")))
693 (let* ((pos (search ":*" name-test))
694 (test-function
695 (cond
696 ((eql pos (- (length name-test) 2))
697 (let* ((prefix (subseq name-test 0 pos))
698 (name-test-uri
699 (xpath-sys:environment-find-namespace env prefix)))
700 (unless (xpath::nc-name-p prefix)
701 (xslt-error "not an NCName: ~A" prefix))
702 (lambda (local-name uri)
703 (declare (ignore local-name))
704 (if (equal uri name-test-uri)
705 mode
706 nil))))
707 ((equal name-test "*")
708 (lambda (local-name uri)
709 (declare (ignore local-name uri))
710 mode))
712 (multiple-value-bind (name-test-local-name name-test-uri)
713 (decode-qname name-test env nil)
714 (lambda (local-name uri)
715 (if (and (equal local-name name-test-local-name)
716 (equal uri name-test-uri))
717 mode
718 nil)))))))
719 (push test-function (stylesheet-strip-tests stylesheet))))))))
721 (defstruct (output-specification
722 (:conc-name "OUTPUT-"))
723 method
724 indent
725 omit-xml-declaration
726 encoding
727 doctype-system
728 doctype-public)
730 (defun parse-output! (stylesheet <transform>)
731 (dolist (<output> (list-toplevel "output" <transform>))
732 (let ((spec (stylesheet-output-specification stylesheet)))
733 (stp:with-attributes ( ;; version
734 method
735 indent
736 encoding
737 ;;; media-type
738 doctype-system
739 doctype-public
740 omit-xml-declaration
741 ;;; standalone
742 ;;; cdata-section-elements
744 <output>
745 (when method
746 (setf (output-method spec) method))
747 (when indent
748 (setf (output-indent spec) indent))
749 (when encoding
750 (setf (output-encoding spec) encoding))
751 (when doctype-system
752 (setf (output-doctype-system spec) doctype-system))
753 (when doctype-public
754 (setf (output-doctype-public spec) doctype-public))
755 (when omit-xml-declaration
756 (setf (output-omit-xml-declaration spec) omit-xml-declaration))
757 ;;; (when cdata-section-elements
758 ;;; (setf (output-cdata-section-elements spec)
759 ;;; (concatenate 'string
760 ;;; (output-cdata-section-elements spec)
761 ;;; " "
762 ;;; cdata-section-elements)))
763 ))))
765 (defun make-empty-declaration-array ()
766 (make-array 1 :fill-pointer 0 :adjustable t))
768 (defun make-variable-value-array (n-lexical-variables)
769 (make-array n-lexical-variables :initial-element 'unbound))
771 (defun compile-global-variable (<variable> env) ;; also for <param>
772 (stp:with-attributes (name select) <variable>
773 (when (and select (stp:list-children <variable>))
774 (xslt-error "variable with select and body"))
775 (let* ((*lexical-variable-declarations* (make-empty-declaration-array))
776 (inner (cond
777 (select
778 (compile-xpath select env))
779 ((stp:list-children <variable>)
780 (let* ((inner-sexpr `(progn ,@(parse-body <variable>)))
781 (inner-thunk (compile-instruction inner-sexpr env)))
782 (lambda (ctx)
783 (apply-to-result-tree-fragment ctx inner-thunk))))
785 (lambda (ctx)
786 (declare (ignore ctx))
787 ""))))
788 (n-lexical-variables (length *lexical-variable-declarations*)))
789 (xslt-trace-thunk
790 (lambda (ctx)
791 (let* ((*lexical-variable-values*
792 (make-variable-value-array n-lexical-variables)))
793 (funcall inner ctx)))
794 "global ~s (~s) = ~s" name select :result))))
796 (defstruct (variable-information
797 (:constructor make-variable)
798 (:conc-name "VARIABLE-"))
799 index
800 thunk
801 local-name
803 param-p
804 thunk-setter)
806 (defun parse-global-variable! (<variable> global-env) ;; also for <param>
807 (let* ((*namespaces* (acons-namespaces <variable>))
808 (instruction-base-uri (stp:base-uri <variable>))
809 (*instruction-base-uri* instruction-base-uri)
810 (*excluded-namespaces* (list *xsl*))
811 (*extension-namespaces* '())
812 (qname (stp:attribute-value <variable> "name")))
813 (with-import-magic (<variable> global-env)
814 (unless qname
815 (xslt-error "name missing in ~A" (stp:local-name <variable>)))
816 (multiple-value-bind (local-name uri)
817 (decode-qname qname global-env nil)
818 ;; For the normal compilation environment of templates, install it
819 ;; into *GLOBAL-VARIABLE-DECLARATIONS*:
820 (let ((index (intern-global-variable local-name uri)))
821 ;; For the evaluation of a global variable itself, build a thunk
822 ;; that lazily resolves other variables, stored into
823 ;; INITIAL-GLOBAL-VARIABLE-THUNKS:
824 (let* ((value-thunk :unknown)
825 (global-variable-thunk
826 (lambda (ctx)
827 (let ((v (global-variable-value index nil)))
828 (when (eq v 'seen)
829 (xslt-error "recursive variable definition"))
830 (cond
831 ((eq v 'unbound)
832 (setf (global-variable-value index) 'seen)
833 (setf (global-variable-value index)
834 (funcall value-thunk ctx)))
836 v)))))
837 (excluded-namespaces *excluded-namespaces*)
838 (extension-namespaces *extension-namespaces*)
839 (thunk-setter
840 (lambda ()
841 (let ((*instruction-base-uri* instruction-base-uri)
842 (*excluded-namespaces* excluded-namespaces)
843 (*extension-namespaces* extension-namespaces))
844 (setf value-thunk
845 (compile-global-variable <variable> global-env))))))
846 (setf (gethash (cons local-name uri)
847 (initial-global-variable-thunks global-env))
848 global-variable-thunk)
849 (make-variable :index index
850 :local-name local-name
851 :uri uri
852 :thunk global-variable-thunk
853 :param-p (namep <variable> "param")
854 :thunk-setter thunk-setter)))))))
856 (defun parse-keys! (stylesheet <transform> env)
857 (xpath:with-namespaces ((nil #.*xsl*))
858 (do-toplevel (<key> "key" <transform>)
859 (let ((*instruction-base-uri* (stp:base-uri <key>)))
860 (stp:with-attributes (name match use) <key>
861 (unless name (xslt-error "key name attribute not specified"))
862 (unless match (xslt-error "key match attribute not specified"))
863 (unless use (xslt-error "key use attribute not specified"))
864 (multiple-value-bind (local-name uri)
865 (decode-qname name env nil)
866 (add-key stylesheet
867 (cons local-name uri)
868 (compile-xpath `(xpath:xpath ,(parse-key-pattern match)) env)
869 (compile-xpath use env))))))))
871 (defun prepare-global-variables (stylesheet <transform>)
872 (xpath:with-namespaces ((nil #.*xsl*))
873 (let* ((table (make-hash-table :test 'equal))
874 (global-env (make-instance 'global-variable-environment
875 :initial-global-variable-thunks table))
876 (specs '()))
877 (do-toplevel (<variable> "variable|param" <transform>)
878 (let ((var (parse-global-variable! <variable> global-env)))
879 (xslt-trace "parsing global variable ~s (uri ~s)"
880 (variable-local-name var)
881 (variable-uri var))
882 (when (find var
883 specs
884 :test (lambda (a b)
885 (and (equal (variable-local-name a)
886 (variable-local-name b))
887 (equal (variable-uri a)
888 (variable-uri b)))))
889 (xslt-error "duplicate definition for global variable ~A"
890 (variable-local-name var)))
891 (push var specs)))
892 (setf specs (nreverse specs))
893 (lambda ()
894 ;; now that the global environment knows about all variables, run the
895 ;; thunk setters to perform their compilation
896 (mapc (lambda (spec) (funcall (variable-thunk-setter spec))) specs)
897 (let ((table (stylesheet-global-variables stylesheet))
898 (newlen (length *global-variable-declarations*)))
899 (adjust-array table newlen :fill-pointer newlen)
900 (dolist (spec specs)
901 (setf (elt table (variable-index spec)) spec)))))))
903 (defun parse-templates! (stylesheet <transform> env)
904 (let ((i 0))
905 (do-toplevel (<template> "template" <transform>)
906 (let ((*namespaces* (acons-namespaces <template>))
907 (*instruction-base-uri* (stp:base-uri <template>)))
908 (with-import-magic (<template> env)
909 (dolist (template (compile-template <template> env i))
910 (let ((name (template-name template)))
911 (if name
912 (let* ((table (stylesheet-named-templates stylesheet))
913 (head (car (gethash name table))))
914 (when (and head (eql (template-import-priority head)
915 (template-import-priority template)))
916 ;; fixme: is this supposed to be a run-time error?
917 (xslt-error "conflicting templates for ~A" name))
918 (push template (gethash name table)))
919 (let ((mode (ensure-mode/qname stylesheet
920 (template-mode-qname template)
921 env)))
922 (setf (template-mode template) mode)
923 (push template (mode-templates mode))))))))
924 (incf i))))
927 ;;;; APPLY-STYLESHEET
929 (defvar *stylesheet*)
931 (deftype xml-designator () '(or runes:xstream runes:rod array stream pathname))
933 (defun unalias-uri (uri)
934 (let ((result
935 (gethash uri (stylesheet-namespace-aliases *stylesheet*)
936 uri)))
937 (check-type result string)
938 result))
940 (defstruct (parameter
941 (:constructor make-parameter (value local-name &optional uri)))
942 (uri "")
943 local-name
944 value)
946 (defun find-parameter-value (local-name uri parameters)
947 (dolist (p parameters)
948 (when (and (equal (parameter-local-name p) local-name)
949 (equal (parameter-uri p) uri))
950 (return (parameter-value p)))))
952 (defvar *uri-resolver*)
954 (defun parse-allowing-microsoft-bom (pathname handler)
955 (with-open-file (s pathname :element-type '(unsigned-byte 8))
956 (unless (and (eql (read-byte s nil) #xef)
957 (eql (read-byte s nil) #xbb)
958 (eql (read-byte s nil) #xbf))
959 (file-position s 0))
960 (cxml:parse s handler)))
962 (defvar *documents*)
964 (defun %document (uri-string base-uri)
965 (let* ((absolute-uri
966 (puri:merge-uris uri-string (or base-uri "")))
967 (resolved-uri
968 (if *uri-resolver*
969 (funcall *uri-resolver* (puri:render-uri absolute-uri nil))
970 absolute-uri))
971 (pathname
972 (handler-case
973 (uri-to-pathname resolved-uri)
974 (cxml:xml-parse-error (c)
975 (xslt-error "cannot find referenced document ~A: ~A"
976 resolved-uri c))))
977 (xpath-root-node
978 (or (gethash pathname *documents*)
979 (setf (gethash pathname *documents*)
980 (make-whitespace-stripper
981 (handler-case
982 (parse-allowing-microsoft-bom pathname
983 (stp:make-builder))
984 ((or file-error cxml:xml-parse-error) (c)
985 (xslt-error "cannot parse referenced document ~A: ~A"
986 pathname c)))
987 (stylesheet-strip-tests *stylesheet*))))))
988 (when (puri:uri-fragment absolute-uri)
989 (xslt-error "use of fragment identifiers in document() not supported"))
990 xpath-root-node))
992 (xpath-sys:define-extension xslt *xsl*)
994 (defun document-base-uri (node)
995 (xpath-protocol:base-uri
996 (cond
997 ((xpath-protocol:node-type-p node :document)
998 (xpath::find-in-pipe-if
999 (lambda (x)
1000 (xpath-protocol:node-type-p x :element))
1001 (xpath-protocol:child-pipe node)))
1002 ((xpath-protocol:node-type-p node :element)
1003 node)
1005 (xpath-protocol:parent-node node)))))
1007 (xpath-sys:define-xpath-function/lazy
1008 xslt :document
1009 (object &optional node-set)
1010 (let ((instruction-base-uri *instruction-base-uri*))
1011 (lambda (ctx)
1012 (let* ((object (funcall object ctx))
1013 (node-set (and node-set (funcall node-set ctx)))
1014 (base-uri
1015 (if node-set
1016 (document-base-uri (xpath::textually-first-node node-set))
1017 instruction-base-uri)))
1018 (xpath-sys:make-node-set
1019 (if (xpath:node-set-p object)
1020 (xpath:map-node-set->list
1021 (lambda (node)
1022 (%document (xpath:string-value node)
1023 (if node-set
1024 base-uri
1025 (document-base-uri node))))
1026 object)
1027 (list (%document (xpath:string-value object) base-uri))))))))
1029 (xpath-sys:define-xpath-function/lazy xslt :key (name object)
1030 (let ((namespaces *namespaces*))
1031 (lambda (ctx)
1032 (let* ((qname (xpath:string-value (funcall name ctx)))
1033 (object (funcall object ctx))
1034 (expanded-name
1035 (multiple-value-bind (local-name uri)
1036 (decode-qname/runtime qname namespaces nil)
1037 (cons local-name uri)))
1038 (key (find-key expanded-name *stylesheet*)))
1039 (labels ((get-by-key (value)
1040 (let ((value (xpath:string-value value)))
1041 (xpath::filter-pipe
1042 #'(lambda (node)
1043 (let ((uses
1044 (xpath:evaluate-compiled (key-use key) node)))
1045 (if (xpath:node-set-p uses)
1046 (xpath::find-in-pipe
1047 value
1048 (xpath-sys:pipe-of uses)
1049 :key #'xpath:string-value
1050 :test #'equal)
1051 (equal value (xpath:string-value uses)))))
1052 (xpath-sys:pipe-of
1053 (xpath:node-set-value
1054 (xpath:evaluate-compiled (key-match key) ctx)))))))
1055 (xpath-sys:make-node-set
1056 (xpath::sort-pipe
1057 (if (xpath:node-set-p object)
1058 (xpath::mappend-pipe #'get-by-key (xpath-sys:pipe-of object))
1059 (get-by-key object)))))))))
1061 ;; FIXME: add alias mechanism for XPath extensions in order to avoid duplication
1063 (xpath-sys:define-xpath-function/lazy xslt :current ()
1064 #'(lambda (ctx)
1065 (xpath-sys:make-node-set
1066 (xpath-sys:make-pipe
1067 (xpath:context-starting-node ctx)
1068 nil))))
1070 (xpath-sys:define-xpath-function/lazy xslt :unparsed-entity-uri (name)
1071 #'(lambda (ctx)
1072 (or (xpath-protocol:unparsed-entity-uri (xpath:context-node ctx)
1073 (funcall name ctx))
1074 "")))
1076 (defun %get-node-id (node)
1077 (when (xpath:node-set-p node)
1078 (setf node (xpath::textually-first-node node)))
1079 (when node
1080 (let ((id (xpath-sys:get-node-id node))
1081 (highest-base-uri
1082 (loop
1083 for parent = node then next
1084 for next = (xpath-protocol:parent-node parent)
1085 for this-base-uri = (xpath-protocol:base-uri parent)
1086 for highest-base-uri = (if (plusp (length this-base-uri))
1087 this-base-uri
1088 highest-base-uri)
1089 while next
1090 finally (return highest-base-uri))))
1091 ;; Heuristic: Reverse it so that the /home/david/alwaysthesame prefix is
1092 ;; checked only if everything else matches.
1094 ;; This might be pointless premature optimization, but I like the idea :-)
1095 (nreverse (concatenate 'string highest-base-uri "//" id)))))
1097 (xpath-sys:define-xpath-function/lazy xslt :generate-id (&optional node-set-thunk)
1098 (if node-set-thunk
1099 #'(lambda (ctx)
1100 (%get-node-id (xpath:node-set-value (funcall node-set-thunk ctx))))
1101 #'(lambda (ctx)
1102 (%get-node-id (xpath:context-node ctx)))))
1104 (declaim (special *available-instructions*))
1106 (xpath-sys:define-xpath-function/lazy xslt :element-available (qname)
1107 (let ((namespaces *namespaces*))
1108 #'(lambda (ctx)
1109 (let ((qname (funcall qname ctx)))
1110 (multiple-value-bind (local-name uri)
1111 (decode-qname/runtime qname namespaces nil)
1112 (and (equal uri *xsl*)
1113 (gethash local-name *available-instructions*)
1114 t))))))
1116 (xpath-sys:define-xpath-function/lazy xslt :function-available (qname)
1117 (let ((namespaces *namespaces*))
1118 #'(lambda (ctx)
1119 (let ((qname (funcall qname ctx)))
1120 (multiple-value-bind (local-name uri)
1121 (decode-qname/runtime qname namespaces nil)
1122 (and (zerop (length uri))
1123 (or (xpath-sys:find-xpath-function local-name *xsl*)
1124 (xpath-sys:find-xpath-function local-name uri))
1125 t))))))
1127 (xpath-sys:define-xpath-function/lazy xslt :system-property (qname)
1128 (let ((namespaces *namespaces*))
1129 (lambda (ctx)
1130 (let ((qname (funcall qname ctx)))
1131 (multiple-value-bind (local-name uri)
1132 (decode-qname/runtime qname namespaces nil)
1133 (if (equal uri *xsl*)
1134 (cond
1135 ((equal local-name "version")
1136 "1")
1137 ((equal local-name "vendor")
1138 "Xuriella")
1139 ((equal local-name "vendor-uri")
1140 "http://repo.or.cz/w/xuriella.git")
1142 ""))
1143 ""))))))
1145 (defun apply-stylesheet
1146 (stylesheet source-designator
1147 &key output parameters uri-resolver navigator)
1148 (when (typep stylesheet 'xml-designator)
1149 (setf stylesheet (parse-stylesheet stylesheet)))
1150 (invoke-with-output-sink
1151 (lambda ()
1152 (handler-case*
1153 (let* ((*documents* (make-hash-table :test 'equal))
1154 (xpath:*navigator* (or navigator :default-navigator))
1155 (puri:*strict-parse* nil)
1156 (*stylesheet* stylesheet)
1157 (*empty-mode* (make-mode))
1158 (*default-mode* (find-mode stylesheet nil))
1159 (global-variable-specs
1160 (stylesheet-global-variables stylesheet))
1161 (*global-variable-values*
1162 (make-variable-value-array (length global-variable-specs)))
1163 (*uri-resolver* uri-resolver)
1164 (source-document
1165 (if (typep source-designator 'xml-designator)
1166 (cxml:parse source-designator (stp:make-builder))
1167 source-designator))
1168 (xpath-root-node
1169 (make-whitespace-stripper
1170 source-document
1171 (stylesheet-strip-tests stylesheet)))
1172 (ctx (xpath:make-context xpath-root-node)))
1173 (when (pathnamep source-designator)
1174 (setf (gethash source-designator *documents*) xpath-root-node))
1175 (map nil
1176 (lambda (spec)
1177 (when (variable-param-p spec)
1178 (let ((value
1179 (find-parameter-value (variable-local-name spec)
1180 (variable-uri spec)
1181 parameters)))
1182 (when value
1183 (setf (global-variable-value (variable-index spec))
1184 value)))))
1185 global-variable-specs)
1186 (map nil
1187 (lambda (spec)
1188 (funcall (variable-thunk spec) ctx))
1189 global-variable-specs)
1190 ;; zzz we wouldn't have to mask float traps here if we used the
1191 ;; XPath API properly. Unfortunately I've been using FUNCALL
1192 ;; everywhere instead of EVALUATE, so let's paper over that
1193 ;; at a central place to be sure:
1194 (xpath::with-float-traps-masked ()
1195 (apply-templates ctx :mode *default-mode*)))
1196 (xpath:xpath-error (c)
1197 (xslt-error "~A" c))))
1198 (stylesheet-output-specification stylesheet)
1199 output))
1201 (defun find-attribute-set (local-name uri)
1202 (or (gethash (cons local-name uri) (stylesheet-attribute-sets *stylesheet*))
1203 (xslt-error "no such attribute set: ~A/~A" local-name uri)))
1205 (defun apply-templates/list (list &key param-bindings sort-predicate mode)
1206 (when sort-predicate
1207 (setf list
1208 (mapcar #'xpath:context-node
1209 (stable-sort (contextify-node-list list)
1210 sort-predicate))))
1211 (let* ((n (length list))
1212 (s/d (lambda () n)))
1213 (loop
1214 for i from 1
1215 for child in list
1217 (apply-templates (xpath:make-context child s/d i)
1218 :param-bindings param-bindings
1219 :mode mode))))
1221 (defvar *stack-limit* 200)
1223 (defun invoke-with-stack-limit (fn)
1224 (let ((*stack-limit* (1- *stack-limit*)))
1225 (unless (plusp *stack-limit*)
1226 (xslt-error "*stack-limit* reached; stack overflow"))
1227 (funcall fn)))
1229 (defun invoke-template (ctx template param-bindings)
1230 (let ((*lexical-variable-values*
1231 (make-variable-value-array (template-n-variables template))))
1232 (with-stack-limit ()
1233 (loop
1234 for (name-cons value) in param-bindings
1235 for (nil index nil) = (find name-cons
1236 (template-params template)
1237 :test #'equal
1238 :key #'car)
1240 (when index
1241 (setf (lexical-variable-value index) value)))
1242 (funcall (template-body template) ctx))))
1244 (defun apply-default-templates (ctx mode)
1245 (let ((node (xpath:context-node ctx)))
1246 (cond
1247 ((or (xpath-protocol:node-type-p node :processing-instruction)
1248 (xpath-protocol:node-type-p node :comment)))
1249 ((or (xpath-protocol:node-type-p node :text)
1250 (xpath-protocol:node-type-p node :attribute))
1251 (write-text (xpath-protocol:node-text node)))
1253 (apply-templates/list
1254 (xpath::force
1255 (xpath-protocol:child-pipe node))
1256 :mode mode)))))
1258 (defvar *apply-imports*)
1260 (defun apply-applicable-templates (ctx templates param-bindings finally)
1261 (labels ((apply-imports (&optional actual-param-bindings)
1262 (if templates
1263 (let* ((this (pop templates))
1264 (low (template-apply-imports-limit this))
1265 (high (template-import-priority this)))
1266 (setf templates
1267 (remove-if-not
1268 (lambda (x)
1269 (<= low (template-import-priority x) high))
1270 templates))
1271 (invoke-template ctx this actual-param-bindings))
1272 (funcall finally))))
1273 (let ((*apply-imports* #'apply-imports))
1274 (apply-imports param-bindings))))
1276 (defun apply-templates (ctx &key param-bindings mode)
1277 (apply-applicable-templates ctx
1278 (find-templates ctx (or mode *default-mode*))
1279 param-bindings
1280 (lambda ()
1281 (apply-default-templates ctx mode))))
1283 (defun call-template (ctx name &optional param-bindings)
1284 (apply-applicable-templates ctx
1285 (find-named-templates name)
1286 param-bindings
1287 (lambda ()
1288 (error "cannot find named template: ~s"
1289 name))))
1291 (defun find-templates (ctx mode)
1292 (let* ((matching-candidates
1293 (remove-if-not (lambda (template)
1294 (template-matches-p template ctx))
1295 (mode-templates mode)))
1296 (npriorities
1297 (if matching-candidates
1298 (1+ (reduce #'max
1299 matching-candidates
1300 :key #'template-import-priority))
1302 (priority-groups (make-array npriorities :initial-element nil)))
1303 (dolist (template matching-candidates)
1304 (push template
1305 (elt priority-groups (template-import-priority template))))
1306 (loop
1307 for i from (1- npriorities) downto 0
1308 for group = (elt priority-groups i)
1309 for template = (maximize #'template< group)
1310 when template
1311 collect template)))
1313 (defun find-named-templates (name)
1314 (gethash name (stylesheet-named-templates *stylesheet*)))
1316 (defun template< (a b) ;assuming same import priority
1317 (let ((p (template-priority a))
1318 (q (template-priority b)))
1319 (cond
1320 ((< p q) t)
1321 ((> p q) nil)
1323 (xslt-cerror "conflicting templates:~_~A,~_~A"
1324 (template-match-expression a)
1325 (template-match-expression b))
1326 (< (template-position a) (template-position b))))))
1328 (defun maximize (< things)
1329 (when things
1330 (let ((max (car things)))
1331 (dolist (other (cdr things))
1332 (when (funcall < max other)
1333 (setf max other)))
1334 max)))
1336 (defun template-matches-p (template ctx)
1337 (find (xpath:context-node ctx)
1338 (xpath:all-nodes (funcall (template-match-thunk template) ctx))
1339 :test #'xpath-protocol:node-equal))
1341 (defun invoke-with-output-sink (fn output-spec output)
1342 (etypecase output
1343 (pathname
1344 (with-open-file (s output
1345 :direction :output
1346 :element-type '(unsigned-byte 8)
1347 :if-exists :rename-and-delete)
1348 (invoke-with-output-sink fn output-spec s)))
1349 ((or stream null)
1350 (invoke-with-output-sink fn
1351 output-spec
1352 (make-output-sink output-spec output)))
1353 ((or hax:abstract-handler sax:abstract-handler)
1354 (with-xml-output output
1355 (when (typep output '(or combi-sink auto-detect-sink))
1356 (sax:start-dtd output
1357 :autodetect-me-please
1358 (output-doctype-public output-spec)
1359 (output-doctype-system output-spec)))
1360 (funcall fn)))))
1362 (defun make-output-sink (output-spec stream)
1363 (let* ((ystream
1364 (if stream
1365 (let ((et (stream-element-type stream)))
1366 (cond
1367 ((or (null et) (subtypep et '(unsigned-byte 8)))
1368 (runes:make-octet-stream-ystream stream))
1369 ((subtypep et 'character)
1370 (runes:make-character-stream-ystream stream))))
1371 (runes:make-rod-ystream)))
1372 (omit-xml-declaration-p
1373 (equal (output-omit-xml-declaration output-spec) "yes"))
1374 (sax-target
1375 (make-instance 'cxml::sink
1376 :ystream ystream
1377 :omit-xml-declaration-p omit-xml-declaration-p)))
1378 (flet ((make-combi-sink ()
1379 (make-instance 'combi-sink
1380 :hax-target (make-instance 'chtml::sink
1381 :ystream ystream)
1382 :sax-target sax-target
1383 :encoding (output-encoding output-spec))))
1384 (let ((method-key
1385 (cond
1386 ((equalp (output-method output-spec) "HTML") :html)
1387 ((equalp (output-method output-spec) "TEXT") :text)
1388 ((equalp (output-method output-spec) "XML") :xml)
1389 (t nil))))
1390 (cond
1391 ((and (eq method-key :html)
1392 (null (output-doctype-system output-spec))
1393 (null (output-doctype-public output-spec)))
1394 (make-combi-sink))
1395 ((eq method-key :text)
1396 (make-text-filter sax-target))
1397 ((and (eq method-key :xml)
1398 (null (output-doctype-system output-spec)))
1399 sax-target)
1401 (make-auto-detect-sink (make-combi-sink) method-key)))))))
1403 (defstruct template
1404 match-expression
1405 match-thunk
1406 name
1407 import-priority
1408 apply-imports-limit
1409 priority
1410 position
1411 mode
1412 mode-qname
1413 params
1414 body
1415 n-variables)
1417 (defun expression-priority (form)
1418 (let ((step (second form)))
1419 (if (and (null (cddr form))
1420 (listp step)
1421 (member (car step) '(:child :attribute))
1422 (null (cddr step)))
1423 (let ((name (second step)))
1424 (cond
1425 ((or (stringp name)
1426 (and (consp name)
1427 (or (eq (car name) :qname)
1428 (eq (car name) :processing-instruction))))
1429 0.0)
1430 ((and (consp name)
1431 (or (eq (car name) :namespace)
1432 (eq (car name) '*)))
1433 -0.25)
1435 -0.5)))
1436 0.5)))
1438 (defun valid-expression-p (expr)
1439 (cond
1440 ((atom expr) t)
1441 ((eq (first expr) :path)
1442 (every (lambda (x)
1443 (let ((filter (third x)))
1444 (or (null filter) (valid-expression-p filter))))
1445 (cdr expr)))
1446 ((eq (first expr) :variable) ;(!)
1447 nil)
1449 (every #'valid-expression-p (cdr expr)))))
1451 (defun parse-xpath (str)
1452 (handler-case
1453 (xpath:parse-xpath str)
1454 (xpath:xpath-error (c)
1455 (xslt-error "~A" c))))
1457 ;; zzz also use naive-pattern-expression here?
1458 (defun parse-key-pattern (str)
1459 (let ((parsed
1460 (mapcar #'(lambda (item)
1461 `(:path (:root :node)
1462 (:descendant-or-self *)
1463 ,@(cdr item)))
1464 (parse-pattern str))))
1465 (if (null (rest parsed))
1466 (first parsed)
1467 `(:union ,@parsed))))
1469 (defun parse-pattern (str)
1470 ;; zzz check here for anything not allowed as an XSLT pattern
1471 ;; zzz can we hack id() and key() here?
1472 (let ((form (parse-xpath str)))
1473 (unless (consp form)
1474 (xslt-error "not a valid pattern: ~A" str))
1475 (labels ((process-form (form)
1476 (cond ((eq (car form) :union)
1477 (alexandria:mappend #'process-form (rest form)))
1478 ((not (or (eq (car form) :path)
1479 (and (eq (car form) :filter)
1480 (let ((filter (second form)))
1481 (and (consp filter)
1482 (member (car filter)
1483 '(:key :id))))
1484 (equal (third form) '(:true)))
1485 (member (car form) '(:key :id))))
1486 (xslt-error "not a valid pattern: ~A ~A" str form))
1487 ((not (valid-expression-p form))
1488 (xslt-error "invalid filter"))
1489 (t (list form)))))
1490 (process-form form))))
1492 (defun naive-pattern-expression (x)
1493 (ecase (car x)
1494 (:path `(:path (:ancestor-or-self :node) ,@(cdr x)))
1495 ((:filter :key :id) x)))
1497 (defun compile-value-thunk (value env)
1498 (if (and (listp value) (eq (car value) 'progn))
1499 (let ((inner-thunk (compile-instruction value env)))
1500 (lambda (ctx)
1501 (apply-to-result-tree-fragment ctx inner-thunk)))
1502 (compile-xpath value env)))
1504 (defun compile-var-binding (name value env)
1505 (multiple-value-bind (local-name uri)
1506 (decode-qname name env nil)
1507 (let ((thunk (xslt-trace-thunk
1508 (compile-value-thunk value env)
1509 "local variable ~s = ~s" name :result)))
1510 (list (cons local-name uri)
1511 (push-variable local-name
1513 *lexical-variable-declarations*)
1514 thunk))))
1516 (defun compile-var-bindings (forms env)
1517 (loop
1518 for (name value) in forms
1519 collect (compile-var-binding name value env)))
1521 (defun compile-template (<template> env position)
1522 (stp:with-attributes (match name priority mode) <template>
1523 (unless (or name match)
1524 (xslt-error "missing match in template"))
1525 (multiple-value-bind (params body-pos)
1526 (loop
1527 for i from 0
1528 for child in (stp:list-children <template>)
1529 while (namep child "param")
1530 collect (parse-param child) into params
1531 finally (return (values params i)))
1532 (let* ((*lexical-variable-declarations* (make-empty-declaration-array))
1533 (param-bindings (compile-var-bindings params env))
1534 (body (parse-body <template> body-pos (mapcar #'car params)))
1535 (body-thunk (compile-instruction `(progn ,@body) env))
1536 (outer-body-thunk
1537 (xslt-trace-thunk
1538 #'(lambda (ctx)
1539 (unwind-protect
1540 (progn
1541 ;; set params that weren't initialized by apply-templates
1542 (loop for (name index param-thunk) in param-bindings
1543 when (eq (lexical-variable-value index nil) 'unbound)
1544 do (setf (lexical-variable-value index)
1545 (funcall param-thunk ctx)))
1546 (funcall body-thunk ctx))))
1547 "template: match = ~s name = ~s" match name))
1548 (n-variables (length *lexical-variable-declarations*)))
1549 (append
1550 (when name
1551 (multiple-value-bind (local-name uri)
1552 (decode-qname name env nil)
1553 (list
1554 (make-template :name (cons local-name uri)
1555 :import-priority *import-priority*
1556 :apply-imports-limit *apply-imports-limit*
1557 :params param-bindings
1558 :body outer-body-thunk
1559 :n-variables n-variables))))
1560 (when match
1561 (mapcar (lambda (expression)
1562 (let ((match-thunk
1563 (xslt-trace-thunk
1564 (compile-xpath
1565 `(xpath:xpath
1566 ,(naive-pattern-expression expression))
1567 env)
1568 "match-thunk for template (match ~s): ~s --> ~s"
1569 match expression :result))
1570 (p (if priority
1571 (parse-number:parse-number priority)
1572 (expression-priority expression))))
1573 (make-template :match-expression expression
1574 :match-thunk match-thunk
1575 :import-priority *import-priority*
1576 :apply-imports-limit *apply-imports-limit*
1577 :priority p
1578 :position position
1579 :mode-qname mode
1580 :params param-bindings
1581 :body outer-body-thunk
1582 :n-variables n-variables)))
1583 (parse-pattern match))))))))
1584 #+(or)
1585 (xuriella::parse-stylesheet #p"/home/david/src/lisp/xuriella/test.xsl")