Fix "for each hash-key in ... using" ordering
[xuriella.git] / xslt.lisp
blob28b8ed208eaaf65af377d7e3cb8f25587e7a096f
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 "@unexport{}"
54 (error 'xslt-error :format-control fmt :format-arguments args))
56 ;; Many errors in XSLT are "recoverable", with a specified action that must
57 ;; be taken if the error isn't raised. My original plan was to implement
58 ;; such issues as continuable conditions, so that users are alerted about
59 ;; portability issues with their stylesheet, but can contiue anyway.
61 ;; However, our current test suite driver compares against Saxon results,
62 ;; and Saxon recovers (nearly) always. So our coverage of these errors
63 ;; is very incomplete.
65 ;; Re-enable this code once we can check that it's actually being used
66 ;; everywhere.
67 (defun xslt-cerror (fmt &rest args)
68 (declare (ignore fmt args))
69 #+(or)
70 (with-simple-restart (recover "recover")
71 (error 'recoverable-xslt-error
72 :format-control fmt
73 :format-arguments args)))
75 (defvar *debug* nil)
77 (defmacro handler-case* (form &rest clauses)
78 ;; like HANDLER-CASE if *DEBUG* is off. If it's on, don't establish
79 ;; a handler at all so that we see the real stack traces. (We could use
80 ;; HANDLER-BIND here and check at signalling time, but doesn't seem
81 ;; important.)
82 (let ((doit (gensym)))
83 `(flet ((,doit () ,form))
84 (if *debug*
85 (,doit)
86 (handler-case
87 (,doit)
88 ,@clauses)))))
90 (defmacro with-resignalled-errors ((&optional) &body body)
91 `(invoke-with-resignalled-errors (lambda () ,@body)))
93 (defun invoke-with-resignalled-errors (fn)
94 (handler-bind
95 ((xpath:xpath-error
96 (lambda (c)
97 (xslt-error "~A" c)))
98 (babel-encodings:character-encoding-error
99 (lambda (c)
100 (xslt-error "~A" c))))
101 (funcall fn)))
103 (defmacro with-forward-compatible-errors (error-form &body body)
104 `(invoke-with-forward-compatible-errors (lambda () ,@body)
105 (lambda () ,error-form)))
107 (defvar *forwards-compatible-p*)
109 (defun invoke-with-forward-compatible-errors (fn error-fn)
110 (let ((result))
111 (tagbody
112 (handler-bind
113 ((xpath:xpath-error
114 (lambda (c)
115 (declare (ignore c))
116 (when *forwards-compatible-p*
117 (go error)))))
118 (setf result (funcall fn)))
119 (go done)
120 error
121 (setf result (funcall error-fn))
122 done)
123 result))
125 (defun compile-xpath (xpath &optional env)
126 (with-resignalled-errors ()
127 (with-forward-compatible-errors
128 (lambda (ctx)
129 (xslt-error "attempt to evaluate an XPath expression with compile-time errors, delayed due to forwards compatible processing: ~A"
130 xpath))
131 (xpath:compile-xpath xpath env))))
133 (defmacro with-stack-limit ((&optional) &body body)
134 `(invoke-with-stack-limit (lambda () ,@body)))
136 (defparameter *without-xslt-current-p* nil)
138 (defmacro without-xslt-current ((&optional) &body body)
139 `(invoke-without-xslt-current (lambda () ,@body)))
141 (defun invoke-without-xslt-current (fn)
142 (let ((*without-xslt-current-p* t))
143 (funcall fn)))
145 ;;; (defun invoke-without-xslt-current (fn)
146 ;;; (let ((non-extensions (gethash "" xpath::*extensions*))
147 ;;; (xpath::*extensions*
148 ;;; ;; hide XSLT extensions
149 ;;; (make-hash-table :test #'equal)))
150 ;;; (setf (gethash "" xpath::*extensions*) non-extensions)
151 ;;; (funcall fn)))
154 ;;;; Helper functions and macros
156 (defun check-for-invalid-attributes (valid-names node)
157 (labels ((check-attribute (a)
158 (unless
159 (let ((uri (stp:namespace-uri a)))
160 (or (and (plusp (length uri)) (not (equal uri *xsl*)))
161 (find (cons (stp:local-name a) uri)
162 valid-names
163 :test #'equal)))
164 (xslt-error "attribute ~A not allowed on ~A"
165 (stp:local-name a)
166 (stp:local-name node)))))
167 (stp:map-attributes nil #'check-attribute node)))
169 (defmacro only-with-attributes ((&rest specs) node &body body)
170 (let ((valid-names
171 (mapcar (lambda (entry)
172 (if (and (listp entry) (cdr entry))
173 (destructuring-bind (name &optional (uri ""))
174 (cdr entry)
175 (cons name uri))
176 (cons (string-downcase
177 (princ-to-string
178 (symbol-name entry)))
179 "")))
180 specs))
181 (%node (gensym)))
182 `(let ((,%NODE ,node))
183 (check-for-invalid-attributes ',valid-names ,%NODE)
184 (stp:with-attributes ,specs ,%NODE
185 ,@body))))
187 (defun map-pipe-eagerly (fn pipe)
188 (xpath::enumerate pipe :key fn :result nil))
190 (defmacro do-pipe ((var pipe &optional result) &body body)
191 `(block nil
192 (map-pipe-eagerly #'(lambda (,var) ,@body) ,pipe)
193 ,result))
196 ;;;; XSLT-ENVIRONMENT and XSLT-CONTEXT
198 (defparameter *initial-namespaces*
199 '((nil . "")
200 ("xmlns" . #"http://www.w3.org/2000/xmlns/")
201 ("xml" . #"http://www.w3.org/XML/1998/namespace")))
203 (defparameter *namespaces*
204 *initial-namespaces*)
206 (defvar *global-variable-declarations*)
207 (defvar *lexical-variable-declarations*)
209 (defvar *global-variable-values*)
210 (defvar *lexical-variable-values*)
212 (defclass xslt-environment () ())
214 (defun split-qname (str)
215 (handler-case
216 (multiple-value-bind (prefix local-name)
217 (cxml::split-qname str)
218 (unless
219 ;; FIXME: cxml should really offer a function that does
220 ;; checks for NCName and QName in a sensible way for user code.
221 ;; cxml::split-qname is tailored to the needs of the parser.
223 ;; For now, let's just check the syntax explicitly.
224 (and (or (null prefix) (xpath::nc-name-p prefix))
225 (xpath::nc-name-p local-name))
226 (xslt-error "not a qname: ~A" str))
227 (values prefix local-name))
228 (cxml:well-formedness-violation ()
229 (xslt-error "not a qname: ~A" str))))
231 (defun decode-qname (qname env attributep &key allow-unknown-namespace)
232 (unless qname
233 (xslt-error "missing name"))
234 (multiple-value-bind (prefix local-name)
235 (split-qname qname)
236 (values local-name
237 (if (or prefix (not attributep))
238 (or (xpath-sys:environment-find-namespace env (or prefix ""))
239 (if allow-unknown-namespace
241 (xslt-error "namespace not found: ~A" prefix)))
243 prefix)))
245 (defmethod xpath-sys:environment-find-namespace ((env xslt-environment) prefix)
246 (or (cdr (assoc prefix *namespaces* :test 'equal))
247 ;; zzz gross hack.
248 ;; Change the entire code base to represent "no prefix" as the
249 ;; empty string consistently. unparse.lisp has already been changed.
250 (and (equal prefix "")
251 (cdr (assoc nil *namespaces* :test 'equal)))
252 (and (eql prefix nil)
253 (cdr (assoc "" *namespaces* :test 'equal)))))
255 (defun find-variable-index (local-name uri table)
256 (position (cons local-name uri) table :test 'equal))
258 (defun intern-global-variable (local-name uri)
259 (or (find-variable-index local-name uri *global-variable-declarations*)
260 (push-variable local-name uri *global-variable-declarations*)))
262 (defun push-variable (local-name uri table)
263 (prog1
264 (length table)
265 (vector-push-extend (cons local-name uri) table)))
267 (defun lexical-variable-value (index &optional (errorp t))
268 (let ((result (svref *lexical-variable-values* index)))
269 (when errorp
270 (assert (not (eq result 'unbound))))
271 result))
273 (defun (setf lexical-variable-value) (newval index)
274 (assert (not (eq newval 'unbound)))
275 (setf (svref *lexical-variable-values* index) newval))
277 (defun global-variable-value (index &optional (errorp t))
278 (let ((result (svref *global-variable-values* index)))
279 (when errorp
280 (assert (not (eq result 'unbound))))
281 result))
283 (defun (setf global-variable-value) (newval index)
284 (assert (not (eq newval 'unbound)))
285 (setf (svref *global-variable-values* index) newval))
287 (defmethod xpath-sys:environment-find-function
288 ((env xslt-environment) lname uri)
289 (or (if (string= uri "")
290 (or (xpath-sys:find-xpath-function lname *xsl*)
291 (xpath-sys:find-xpath-function lname uri))
292 (xpath-sys:find-xpath-function lname uri))
293 (when *forwards-compatible-p*
294 (lambda (&rest ignore)
295 (declare (ignore ignore))
296 (xslt-error "attempt to call an unknown XPath function (~A); error delayed until run-time due to forwards compatible processing"
297 lname)))))
299 (defmethod xpath-sys:environment-find-variable
300 ((env xslt-environment) lname uri)
301 (let ((index
302 (find-variable-index lname uri *lexical-variable-declarations*)))
303 (when index
304 (lambda (ctx)
305 (declare (ignore ctx))
306 (svref *lexical-variable-values* index)))))
308 (defclass lexical-xslt-environment (xslt-environment) ())
310 (defmethod xpath-sys:environment-find-variable
311 ((env lexical-xslt-environment) lname uri)
312 (or (call-next-method)
313 (let ((index
314 (find-variable-index lname uri *global-variable-declarations*)))
315 (when index
316 (xslt-trace-thunk
317 (lambda (ctx)
318 (declare (ignore ctx))
319 (svref *global-variable-values* index))
320 "global ~s (uri ~s) = ~s" lname uri :result)))))
322 (defclass key-environment (xslt-environment) ())
324 (defmethod xpath-sys:environment-find-variable
325 ((env key-environment) lname uri)
326 (declare (ignore lname uri))
327 (xslt-error "disallowed variable reference"))
329 (defclass global-variable-environment (xslt-environment)
330 ((initial-global-variable-thunks
331 :initarg :initial-global-variable-thunks
332 :accessor initial-global-variable-thunks)))
334 (defmethod xpath-sys:environment-find-variable
335 ((env global-variable-environment) lname uri)
336 (or (call-next-method)
337 (gethash (cons lname uri) (initial-global-variable-thunks env))))
340 ;;;; TOPLEVEL-TEXT-OUTPUT-SINK
341 ;;;;
342 ;;;; A sink that serializes only text not contained in any element.
344 (defmacro with-toplevel-text-output-sink ((var) &body body)
345 `(invoke-with-toplevel-text-output-sink (lambda (,var) ,@body)))
347 (defclass toplevel-text-output-sink (sax:default-handler)
348 ((target :initarg :target :accessor text-output-sink-target)
349 (depth :initform 0 :accessor textoutput-sink-depth)))
351 (defmethod sax:start-element ((sink toplevel-text-output-sink)
352 namespace-uri local-name qname attributes)
353 (declare (ignore namespace-uri local-name qname attributes))
354 (incf (textoutput-sink-depth sink)))
356 (defmethod sax:characters ((sink toplevel-text-output-sink) data)
357 (when (zerop (textoutput-sink-depth sink))
358 (write-string data (text-output-sink-target sink))))
360 (defmethod sax:unescaped ((sink toplevel-text-output-sink) data)
361 (sax:characters sink data))
363 (defmethod sax:end-element ((sink toplevel-text-output-sink)
364 namespace-uri local-name qname)
365 (declare (ignore namespace-uri local-name qname))
366 (decf (textoutput-sink-depth sink)))
368 (defun invoke-with-toplevel-text-output-sink (fn)
369 (with-output-to-string (s)
370 (funcall fn (make-instance 'toplevel-text-output-sink :target s))))
373 ;;;; TEXT-FILTER
374 ;;;;
375 ;;;; A sink that passes through only text (at any level) and turns to
376 ;;;; into unescaped characters.
378 (defclass text-filter (sax:default-handler)
379 ((target :initarg :target :accessor text-filter-target)))
381 (defmethod sax:characters ((sink text-filter) data)
382 (sax:unescaped (text-filter-target sink) data))
384 (defmethod sax:unescaped ((sink text-filter) data)
385 (sax:unescaped (text-filter-target sink) data))
387 (defmethod sax:end-document ((sink text-filter))
388 (sax:end-document (text-filter-target sink)))
390 (defun make-text-filter (target)
391 (make-instance 'text-filter :target target))
394 ;;;; ESCAPER
395 ;;;;
396 ;;;; A sink that recovers from sax:unescaped using sax:characters, as per
397 ;;;; XSLT 16.4.
399 (defclass escaper (cxml:broadcast-handler)
402 (defmethod sax:unescaped ((sink escaper) data)
403 (sax:characters sink data))
405 (defun make-escaper (target)
406 (make-instance 'escaper :handlers (list target)))
409 ;;;; Names
411 (defun of-name (local-name)
412 (stp:of-name local-name *xsl*))
414 (defun namep (node local-name)
415 (and (typep node '(or stp:element stp:attribute))
416 (equal (stp:namespace-uri node) *xsl*)
417 (equal (stp:local-name node) local-name)))
420 ;;;; PARSE-STYLESHEET
422 (defstruct stylesheet
423 (modes (make-hash-table :test 'equal))
424 (global-variables (make-empty-declaration-array))
425 (output-specification (make-output-specification))
426 (strip-tests nil)
427 (strip-thunk nil)
428 (named-templates (make-hash-table :test 'equal))
429 (attribute-sets (make-hash-table :test 'equal))
430 (keys (make-hash-table :test 'equal))
431 (namespace-aliases (make-hash-table :test 'equal))
432 (decimal-formats (make-hash-table :test 'equal))
433 (initial-global-variable-thunks (make-hash-table :test 'equal)))
435 (setf (documentation 'stylesheet 'type)
436 "The class of stylesheets that have been parsed and compiled.
438 Pass instances of this class to @fun{apply-stylesheet} to invoke
439 them.
441 @see-constructor{parse-stylesheet}")
443 (defstruct mode
444 (templates nil)
445 (match-thunk (lambda (ignore) (declare (ignore ignore)) nil)))
447 (defun find-mode (stylesheet local-name &optional uri)
448 (gethash (cons local-name uri) (stylesheet-modes stylesheet)))
450 (defun ensure-mode (stylesheet &optional local-name uri)
451 (or (find-mode stylesheet local-name uri)
452 (setf (gethash (cons local-name uri) (stylesheet-modes stylesheet))
453 (make-mode))))
455 (defun ensure-mode/qname (stylesheet qname env)
456 (if qname
457 (multiple-value-bind (local-name uri)
458 (decode-qname qname env nil)
459 (ensure-mode stylesheet local-name uri))
460 (find-mode stylesheet nil)))
462 (defun acons-namespaces
463 (element &optional (bindings *namespaces*) include-redeclared)
464 (map-namespace-declarations (lambda (prefix uri)
465 (push (cons prefix uri) bindings))
466 element
467 include-redeclared)
468 bindings)
470 (defun find-key (name stylesheet)
471 (or (gethash name (stylesheet-keys stylesheet))
472 (xslt-error "unknown key: ~a" name)))
474 (defun make-key (match use) (cons match use))
476 (defun key-match (key) (car key))
478 (defun key-use (key) (cdr key))
480 (defun add-key (stylesheet name match use)
481 (push (make-key match use)
482 (gethash name (stylesheet-keys stylesheet))))
484 (defvar *excluded-namespaces* (list *xsl*))
485 (defvar *empty-mode*)
486 (defvar *default-mode*)
488 (defvar *xsl-include-stack* nil)
490 (defun uri-to-pathname (uri)
491 (cxml::uri-to-pathname (puri:parse-uri uri)))
493 ;; Why this extra check for literal result element used as stylesheets,
494 ;; instead of a general check for every literal result element? Because
495 ;; Stylesheet__91804 says so.
496 (defun check-Errors_err035 (literal-result-element)
497 (let ((*namespaces* (acons-namespaces literal-result-element))
498 (env (make-instance 'lexical-xslt-environment)))
499 (stp:with-attributes ((extension-element-prefixes
500 "extension-element-prefixes"
501 *xsl*))
502 literal-result-element
503 (dolist (prefix (words (or extension-element-prefixes "")))
504 (if (equal prefix "#default")
505 (setf prefix nil)
506 (unless (cxml-stp-impl::nc-name-p prefix)
507 (xslt-error "invalid prefix: ~A" prefix)))
508 (let ((uri
509 (or (xpath-sys:environment-find-namespace env prefix)
510 (xslt-error "namespace not found: ~A" prefix))))
511 (when (equal uri (stp:namespace-uri literal-result-element))
512 (xslt-error "literal result element used as stylesheet, but is ~
513 declared as an extension element")))))))
515 (defun unwrap-2.3 (document)
516 (let ((literal-result-element (stp:document-element document))
517 (new-template (stp:make-element "template" *xsl*))
518 (new-document-element (stp:make-element "stylesheet" *xsl*)))
519 (check-Errors_err035 literal-result-element)
520 (setf (stp:attribute-value new-document-element "version")
521 (or (stp:attribute-value literal-result-element "version" *xsl*)
522 (xslt-error "not a stylesheet: root element lacks xsl:version")))
523 (setf (stp:attribute-value new-template "match") "/")
524 (setf (stp:document-element document) new-document-element)
525 (stp:append-child new-document-element new-template)
526 (stp:append-child new-template literal-result-element)
527 (setf (stp:base-uri new-template) (stp:base-uri literal-result-element))
528 new-document-element))
530 (defun parse-stylesheet-to-stp (input uri-resolver)
531 (let* ((d (cxml:parse input (make-text-normalizer (cxml-stp:make-builder))))
532 (<transform> (stp:document-element d)))
533 (unless (equal (stp:namespace-uri <transform>) *xsl*)
534 (setf <transform> (unwrap-2.3 d)))
535 (strip-stylesheet <transform>)
536 (unless (and (equal (stp:namespace-uri <transform>) *xsl*)
537 (or (equal (stp:local-name <transform>) "transform")
538 (equal (stp:local-name <transform>) "stylesheet")))
539 (xslt-error "not a stylesheet"))
540 (check-for-invalid-attributes
541 '(("version" . "")
542 ("exclude-result-prefixes" . "")
543 ("extension-element-prefixes" . "")
544 ("space" . "http://www.w3.org/XML/1998/namespace")
545 ("id" . ""))
546 <transform>)
547 (let ((invalid
548 (or (stp:find-child-if (of-name "stylesheet") <transform>)
549 (stp:find-child-if (of-name "transform") <transform>))))
550 (when invalid
551 (xslt-error "invalid top-level element ~A" (stp:local-name invalid))))
552 (dolist (include (stp:filter-children (of-name "include") <transform>))
553 (let* ((uri (puri:merge-uris (or (stp:attribute-value include "href")
554 (xslt-error "include without href"))
555 (stp:base-uri include)))
556 (uri (if uri-resolver
557 (funcall uri-resolver uri)
558 uri))
559 (str (puri:render-uri uri nil))
560 (pathname
561 (handler-case
562 (uri-to-pathname uri)
563 (cxml:xml-parse-error (c)
564 (xslt-error "cannot find included stylesheet ~A: ~A"
565 uri c)))))
566 (with-open-file
567 (stream pathname
568 :element-type '(unsigned-byte 8)
569 :if-does-not-exist nil)
570 (unless stream
571 (xslt-error "cannot find included stylesheet ~A at ~A"
572 uri pathname))
573 (when (find str *xsl-include-stack* :test #'equal)
574 (xslt-error "recursive inclusion of ~A" uri))
575 (let* ((*xsl-include-stack* (cons str *xsl-include-stack*))
576 (<transform>2 (parse-stylesheet-to-stp stream uri-resolver)))
577 (stp:insert-child-after <transform>
578 (stp:copy <transform>2)
579 include)
580 (stp:detach include)))))
581 <transform>))
583 (defvar *instruction-base-uri*) ;misnamed, is also used in other attributes
584 (defvar *apply-imports-limit*)
585 (defvar *import-priority*)
586 (defvar *extension-namespaces*)
588 (defmacro do-toplevel ((var xpath <transform>) &body body)
589 `(map-toplevel (lambda (,var) ,@body) ,xpath ,<transform>))
591 (defun map-toplevel (fn xpath <transform>)
592 (dolist (node (list-toplevel xpath <transform>))
593 (let ((*namespaces* *initial-namespaces*))
594 (xpath:do-node-set (ancestor (xpath:evaluate "ancestor::node()" node))
595 (xpath:with-namespaces (("" #.*xsl*))
596 (when (xpattern:node-matches-p ancestor "stylesheet|transform")
597 ;; discard namespaces from including stylesheets
598 (setf *namespaces* *initial-namespaces*)))
599 (when (xpath-protocol:node-type-p ancestor :element)
600 (setf *namespaces* (acons-namespaces ancestor *namespaces* t))))
601 (funcall fn node))))
603 (defun list-toplevel (xpath <transform>)
604 (labels ((recurse (sub)
605 (let ((subsubs
606 (xpath-sys:pipe-of
607 (xpath:evaluate "transform|stylesheet" sub))))
608 (xpath::append-pipes
609 (xpath-sys:pipe-of (xpath:evaluate xpath sub))
610 (xpath::mappend-pipe #'recurse subsubs)))))
611 (xpath::sort-nodes (recurse <transform>))))
613 (defmacro with-import-magic ((node env) &body body)
614 `(invoke-with-import-magic (lambda () ,@body) ,node ,env))
616 (defun invoke-with-import-magic (fn node env)
617 (unless (or (namep node "stylesheet") (namep node "transform"))
618 (setf node (stp:parent node)))
619 (let ((*excluded-namespaces* (list *xsl*))
620 (*extension-namespaces* '())
621 (*forwards-compatible-p*
622 (not (equal (stp:attribute-value node "version") "1.0"))))
623 (parse-exclude-result-prefixes! node env)
624 (parse-extension-element-prefixes! node env)
625 (funcall fn)))
627 (defun parse-1-stylesheet (env stylesheet designator uri-resolver)
628 (let* ((<transform> (parse-stylesheet-to-stp designator uri-resolver))
629 (instruction-base-uri (stp:base-uri <transform>))
630 (namespaces (acons-namespaces <transform>))
631 (apply-imports-limit (1+ *import-priority*))
632 (continuations '()))
633 (let ((*namespaces* namespaces))
634 (invoke-with-import-magic (constantly t) <transform> env))
635 (do-toplevel (elt "node()" <transform>)
636 (let ((version (stp:attribute-value (stp:parent elt) "version")))
637 (cond
638 ((null version)
639 (xslt-error "stylesheet lacks version"))
640 ((equal version "1.0")
641 (if (typep elt 'stp:element)
642 (when (or (equal (stp:namespace-uri elt) "")
643 (and (equal (stp:namespace-uri elt) *xsl*)
644 (not (find (stp:local-name elt)
645 '("key" "template" "output"
646 "strip-space" "preserve-space"
647 "attribute-set" "namespace-alias"
648 "decimal-format" "variable" "param"
649 "import" "include"
650 ;; for include handling:
651 "stylesheet" "transform")
652 :test #'equal))))
653 (xslt-error "unknown top-level element ~A" (stp:local-name elt)))
654 (xslt-error "text at top-level"))))))
655 (macrolet ((with-specials ((&optional) &body body)
656 `(let ((*instruction-base-uri* instruction-base-uri)
657 (*namespaces* namespaces)
658 (*apply-imports-limit* apply-imports-limit))
659 ,@body)))
660 (with-specials ()
661 (do-toplevel (import "import" <transform>)
662 (when (let ((prev (xpath:first-node
663 (xpath:evaluate "preceding-sibling::*"
664 import
665 t))))
666 (and prev (not (namep prev "import"))))
667 (xslt-error "import not at beginning of stylesheet"))
668 (let ((uri (puri:merge-uris (or (stp:attribute-value import "href")
669 (xslt-error "import without href"))
670 (stp:base-uri import))))
671 (push (parse-imported-stylesheet env stylesheet uri uri-resolver)
672 continuations))))
673 (let ((import-priority
674 (incf *import-priority*))
675 (var-cont (prepare-global-variables stylesheet <transform>)))
676 (parse-namespace-aliases! stylesheet <transform> env)
677 ;; delay the rest of compilation until we've seen all global
678 ;; variables:
679 (lambda ()
680 (mapc #'funcall (nreverse continuations))
681 (with-specials ()
682 (let ((*import-priority* import-priority))
683 (funcall var-cont)
684 (parse-keys! stylesheet <transform> env)
685 (parse-templates! stylesheet <transform> env)
686 (parse-output! stylesheet <transform> env)
687 (parse-strip/preserve-space! stylesheet <transform> env)
688 (parse-attribute-sets! stylesheet <transform> env)
689 (parse-decimal-formats! stylesheet <transform> env))))))))
691 (defvar *xsl-import-stack* nil)
693 (defun parse-imported-stylesheet (env stylesheet uri uri-resolver)
694 (let* ((uri (if uri-resolver
695 (funcall uri-resolver uri)
696 uri))
697 (str (puri:render-uri uri nil))
698 (pathname
699 (handler-case
700 (uri-to-pathname uri)
701 (cxml:xml-parse-error (c)
702 (xslt-error "cannot find imported stylesheet ~A: ~A"
703 uri c)))))
704 (with-open-file
705 (stream pathname
706 :element-type '(unsigned-byte 8)
707 :if-does-not-exist nil)
708 (unless stream
709 (xslt-error "cannot find imported stylesheet ~A at ~A"
710 uri pathname))
711 (when (find str *xsl-import-stack* :test #'equal)
712 (xslt-error "recursive inclusion of ~A" uri))
713 (let ((*xsl-import-stack* (cons str *xsl-import-stack*)))
714 (parse-1-stylesheet env stylesheet stream uri-resolver)))))
716 (defvar *included-attribute-sets*)
718 (defvar *stylesheet*)
720 (defun parse-stylesheet (designator &key uri-resolver)
721 "@arg[designator]{an XML designator}
722 @arg[uri-resolver]{optional function of one argument}
723 @return{An instance of @class{stylesheet}.}
725 @short{Parse a stylesheet.}
727 This function parses and compiles an XSLT stylesheet.
728 The precompiled stylesheet object can then be passed to
729 @fun{apply-stylesheet}.
731 Also refer to @fun{apply-stylesheet} for details on XML designators."
732 (with-resignalled-errors ()
733 (xpath:with-namespaces ((nil #.*xsl*))
734 (let* ((*import-priority* 0)
735 (xpattern:*allow-variables-in-patterns* nil)
736 (puri:*strict-parse* nil)
737 (stylesheet (make-stylesheet))
738 (*stylesheet*
739 ;; zzz this is for remove-excluded-namespaces only
740 stylesheet)
741 (env (make-instance 'lexical-xslt-environment))
742 (*excluded-namespaces* *excluded-namespaces*)
743 (*global-variable-declarations* (make-empty-declaration-array))
744 (*included-attribute-sets* nil))
745 (ensure-mode stylesheet nil)
746 (funcall (parse-1-stylesheet env stylesheet designator uri-resolver))
747 ;; reverse attribute sets:
748 (let ((table (stylesheet-attribute-sets stylesheet)))
749 (maphash (lambda (k v)
750 (setf (gethash k table) (nreverse v)))
751 table))
752 ;; for Errors_err011
753 (dolist (sets *included-attribute-sets*)
754 (loop for (local-name uri nil) in sets do
755 (find-attribute-set local-name uri stylesheet)))
756 ;; add default df
757 (unless (find-decimal-format "" "" stylesheet nil)
758 (setf (find-decimal-format "" "" stylesheet)
759 (make-decimal-format)))
760 ;; compile a template matcher for each mode:
761 (loop
762 for mode being each hash-value in (stylesheet-modes stylesheet)
764 (setf (mode-match-thunk mode)
765 (xpattern:make-pattern-matcher
766 (mapcar #'template-compiled-pattern
767 (mode-templates mode)))))
768 ;; and for the strip tests
769 (setf (stylesheet-strip-thunk stylesheet)
770 (let ((patterns (stylesheet-strip-tests stylesheet)))
771 (and patterns
772 (xpattern:make-pattern-matcher
773 (mapcar #'strip-test-compiled-pattern patterns)))))
774 stylesheet))))
776 (defun parse-attribute-sets! (stylesheet <transform> env)
777 (do-toplevel (elt "attribute-set" <transform>)
778 (with-import-magic (elt env)
779 (push (let* ((sets
780 (mapcar (lambda (qname)
781 (multiple-value-list (decode-qname qname env nil)))
782 (words
783 (stp:attribute-value elt "use-attribute-sets"))))
784 (instructions
785 (stp:map-children
786 'list
787 (lambda (child)
788 (unless
789 (and (typep child 'stp:element)
790 (or (and (equal (stp:namespace-uri child) *xsl*)
791 (equal (stp:local-name child)
792 "attribute"))
793 (find (stp:namespace-uri child)
794 *extension-namespaces*
795 :test 'equal)))
796 (xslt-error "non-attribute found in attribute set"))
797 (parse-instruction child))
798 elt))
799 (*lexical-variable-declarations*
800 (make-empty-declaration-array))
801 (thunk
802 (compile-instruction `(progn ,@instructions) env))
803 (n-variables (length *lexical-variable-declarations*)))
804 (push sets *included-attribute-sets*)
805 (lambda (ctx)
806 (with-stack-limit ()
807 (loop for (local-name uri nil) in sets do
808 (dolist (thunk (find-attribute-set local-name uri))
809 (funcall thunk ctx)))
810 (let ((*lexical-variable-values*
811 (make-variable-value-array n-variables)))
812 (funcall thunk ctx)))))
813 (gethash (multiple-value-bind (local-name uri)
814 (decode-qname (or (stp:attribute-value elt "name")
815 (xslt-error "missing name"))
817 nil)
818 (cons local-name uri))
819 (stylesheet-attribute-sets stylesheet))))))
821 (defun parse-namespace-aliases! (stylesheet <transform> env)
822 (do-toplevel (elt "namespace-alias" <transform>)
823 (let ((*namespaces* (acons-namespaces elt)))
824 (only-with-attributes (stylesheet-prefix result-prefix) elt
825 (unless stylesheet-prefix
826 (xslt-error "missing stylesheet-prefix in namespace-alias"))
827 (unless result-prefix
828 (xslt-error "missing result-prefix in namespace-alias"))
829 (setf (gethash
830 (if (equal stylesheet-prefix "#default")
832 (or (xpath-sys:environment-find-namespace
834 stylesheet-prefix)
835 (xslt-error "stylesheet namespace not found in alias: ~A"
836 stylesheet-prefix)))
837 (stylesheet-namespace-aliases stylesheet))
838 (or (xpath-sys:environment-find-namespace
840 (if (equal result-prefix "#default")
842 result-prefix))
843 (xslt-error "result namespace not found in alias: ~A"
844 result-prefix)))))))
846 (defun parse-decimal-formats! (stylesheet <transform> env)
847 (do-toplevel (elt "decimal-format" <transform>)
848 (stp:with-attributes (name
849 ;; strings
850 infinity
851 (nan "NaN")
852 ;; characters:
853 decimal-separator
854 grouping-separator
855 zero-digit
856 percent
857 per-mille
858 digit
859 pattern-separator
860 minus-sign)
862 (multiple-value-bind (local-name uri)
863 (if name
864 (decode-qname name env nil)
865 (values "" ""))
866 (let ((current (find-decimal-format local-name uri stylesheet nil))
867 (new
868 (let ((seen '()))
869 (flet ((chr (key x)
870 (when x
871 (unless (eql (length x) 1)
872 (xslt-error "not a single character: ~A" x))
873 (let ((chr (elt x 0)))
874 (when (find chr seen)
875 (xslt-error
876 "conflicting decimal format characters: ~A"
877 chr))
878 (push chr seen)
879 (list key chr))))
880 (str (key x)
881 (when x
882 (list key x))))
883 (apply #'make-decimal-format
884 (append (str :infinity infinity)
885 (str :nan nan)
886 (chr :decimal-separator decimal-separator)
887 (chr :grouping-separator grouping-separator)
888 (chr :zero-digit zero-digit)
889 (chr :percent percent)
890 (chr :per-mille per-mille)
891 (chr :digit digit)
892 (chr :pattern-separator pattern-separator)
893 (chr :minus-sign minus-sign)))))))
894 (if current
895 (unless (decimal-format= current new)
896 (xslt-error "decimal format mismatch for ~S" local-name))
897 (setf (find-decimal-format local-name uri stylesheet) new)))))))
899 (defun parse-exclude-result-prefixes! (node env)
900 (stp:with-attributes (exclude-result-prefixes)
901 node
902 (dolist (prefix (words (or exclude-result-prefixes "")))
903 (if (equal prefix "#default")
904 (setf prefix nil)
905 (unless (cxml-stp-impl::nc-name-p prefix)
906 (xslt-error "invalid prefix: ~A" prefix)))
907 (push (or (xpath-sys:environment-find-namespace env prefix)
908 (xslt-error "namespace not found: ~A" prefix))
909 *excluded-namespaces*))))
911 (defun parse-extension-element-prefixes! (node env)
912 (stp:with-attributes (extension-element-prefixes)
913 node
914 (dolist (prefix (words (or extension-element-prefixes "")))
915 (if (equal prefix "#default")
916 (setf prefix nil)
917 (unless (cxml-stp-impl::nc-name-p prefix)
918 (xslt-error "invalid prefix: ~A" prefix)))
919 (let ((uri
920 (or (xpath-sys:environment-find-namespace env prefix)
921 (xslt-error "namespace not found: ~A" prefix))))
922 (unless (equal uri *xsl*)
923 (push uri *extension-namespaces*)
924 (push uri *excluded-namespaces*))))))
926 (defun parse-nametest-tokens (str)
927 (labels ((check (boolean)
928 (unless boolean
929 (xslt-error "invalid nametest token")))
930 (check-null (boolean)
931 (check (not boolean))))
932 (cons
933 :patterns
934 (mapcar (lambda (name-test)
935 (destructuring-bind (&optional path &rest junk)
936 (cdr (xpattern:parse-pattern-expression name-test))
937 (check-null junk)
938 (check (eq (car path) :path))
939 (destructuring-bind (&optional child &rest junk) (cdr path)
940 (check-null junk)
941 (check (eq (car child) :child))
942 (destructuring-bind (nodetest &rest junk) (cdr child)
943 (check-null junk)
944 (check (or (stringp nodetest)
945 (eq nodetest '*)
946 (and (consp nodetest)
947 (or (eq (car nodetest) :namespace)
948 (eq (car nodetest) :qname)))))))
949 path))
950 (words str)))))
952 (defstruct strip-test
953 compiled-pattern
954 priority
955 position
956 value)
958 (defun parse-strip/preserve-space! (stylesheet <transform> env)
959 (let ((i 0))
960 (do-toplevel (elt "strip-space|preserve-space" <transform>)
961 (let ((*namespaces* (acons-namespaces elt))
962 (value
963 (if (equal (stp:local-name elt) "strip-space")
964 :strip
965 :preserve)))
966 (dolist (expression
967 (cdr (parse-nametest-tokens
968 (stp:attribute-value elt "elements"))))
969 (let* ((compiled-pattern
970 (car (without-xslt-current ()
971 (xpattern:compute-patterns
972 `(:patterns ,expression)
973 *import-priority*
974 "will set below"
975 env))))
976 (strip-test
977 (make-strip-test :compiled-pattern compiled-pattern
978 :priority (expression-priority expression)
979 :position i
980 :value value)))
981 (setf (xpattern:pattern-value compiled-pattern) strip-test)
982 (push strip-test (stylesheet-strip-tests stylesheet)))))
983 (incf i))))
985 (defstruct (output-specification
986 (:conc-name "OUTPUT-"))
987 method
988 indent
989 omit-xml-declaration
990 encoding
991 doctype-system
992 doctype-public
993 cdata-section-matchers
994 standalone-p
995 media-type)
997 (defun parse-output! (stylesheet <transform> env)
998 (dolist (<output> (list-toplevel "output" <transform>))
999 (let ((spec (stylesheet-output-specification stylesheet)))
1000 (only-with-attributes (version
1001 method
1002 indent
1003 encoding
1004 media-type
1005 doctype-system
1006 doctype-public
1007 omit-xml-declaration
1008 standalone
1009 cdata-section-elements)
1010 <output>
1011 (declare (ignore version))
1012 (when method
1013 (multiple-value-bind (local-name uri)
1014 (decode-qname method env t)
1015 (setf (output-method spec)
1016 (if (plusp (length uri))
1018 (cond
1019 ((equalp local-name "HTML") :html)
1020 ((equalp local-name "TEXT") :text)
1021 ((equalp local-name "XML") :xml)
1023 (xslt-error "invalid output method: ~A" method)))))))
1024 (when indent
1025 (setf (output-indent spec) indent))
1026 (when encoding
1027 (setf (output-encoding spec) encoding))
1028 (when doctype-system
1029 (setf (output-doctype-system spec) doctype-system))
1030 (when doctype-public
1031 (setf (output-doctype-public spec) doctype-public))
1032 (when omit-xml-declaration
1033 (setf (output-omit-xml-declaration spec) omit-xml-declaration))
1034 (when cdata-section-elements
1035 (dolist (qname (words cdata-section-elements))
1036 (decode-qname qname env nil) ;check the syntax
1037 (push (xpattern:make-pattern-matcher* qname env)
1038 (output-cdata-section-matchers spec))))
1039 (when standalone
1040 (setf (output-standalone-p spec)
1041 (boolean-or-error standalone)))
1042 (when media-type
1043 (setf (output-media-type spec) media-type))))))
1045 (defun make-empty-declaration-array ()
1046 (make-array 1 :fill-pointer 0 :adjustable t))
1048 (defun make-variable-value-array (n-lexical-variables)
1049 (make-array n-lexical-variables :initial-element 'unbound))
1051 (defun compile-global-variable (<variable> env) ;; also for <param>
1052 (stp:with-attributes (name select) <variable>
1053 (when (and select (stp:list-children <variable>))
1054 (xslt-error "variable with select and body"))
1055 (let* ((*lexical-variable-declarations* (make-empty-declaration-array))
1056 (inner (cond
1057 (select
1058 (compile-xpath select env))
1059 ((stp:list-children <variable>)
1060 (let* ((inner-sexpr `(progn ,@(parse-body <variable>)))
1061 (inner-thunk (compile-instruction inner-sexpr env)))
1062 (lambda (ctx)
1063 (apply-to-result-tree-fragment ctx inner-thunk))))
1065 (lambda (ctx)
1066 (declare (ignore ctx))
1067 ""))))
1068 (n-lexical-variables (length *lexical-variable-declarations*)))
1069 (xslt-trace-thunk
1070 (lambda (ctx)
1071 (let* ((*lexical-variable-values*
1072 (make-variable-value-array n-lexical-variables)))
1073 (funcall inner ctx)))
1074 "global ~s (~s) = ~s" name select :result))))
1076 (defstruct (variable-chain
1077 (:constructor make-variable-chain)
1078 (:conc-name "VARIABLE-CHAIN-"))
1079 definitions
1080 index
1081 local-name
1082 thunk
1083 uri)
1085 (defstruct (import-variable
1086 (:constructor make-variable)
1087 (:conc-name "VARIABLE-"))
1088 value-thunk
1089 value-thunk-setter
1090 param-p)
1092 (defun parse-global-variable! (stylesheet <variable> global-env)
1093 (let* ((*namespaces* (acons-namespaces <variable>))
1094 (instruction-base-uri (stp:base-uri <variable>))
1095 (*instruction-base-uri* instruction-base-uri)
1096 (*excluded-namespaces* (list *xsl*))
1097 (*extension-namespaces* '())
1098 (qname (stp:attribute-value <variable> "name")))
1099 (with-import-magic (<variable> global-env)
1100 (unless qname
1101 (xslt-error "name missing in ~A" (stp:local-name <variable>)))
1102 (multiple-value-bind (local-name uri)
1103 (decode-qname qname global-env nil)
1104 ;; For the normal compilation environment of templates, install it
1105 ;; into *GLOBAL-VARIABLE-DECLARATIONS*:
1106 (let ((index (intern-global-variable local-name uri)))
1107 ;; For the evaluation of a global variable itself, build a thunk
1108 ;; that lazily resolves other variables, stored into
1109 ;; INITIAL-GLOBAL-VARIABLE-THUNKS:
1110 (let* ((value-thunk :unknown)
1111 (sgv (stylesheet-global-variables stylesheet))
1112 (chain
1113 (if (< index (length sgv))
1114 (elt sgv index)
1115 (make-variable-chain
1116 :index index
1117 :local-name local-name
1118 :uri uri)))
1119 (next (car (variable-chain-definitions chain)))
1120 (global-variable-thunk
1121 (lambda (ctx)
1122 (let ((v (global-variable-value index nil)))
1123 (cond
1124 ((eq v 'seen)
1125 (unless next
1126 (xslt-error "no next definition for: ~A"
1127 local-name))
1128 (funcall (variable-value-thunk next) ctx))
1129 ((eq v 'unbound)
1130 (setf (global-variable-value index) 'seen)
1131 (setf (global-variable-value index)
1132 (funcall value-thunk ctx)))
1134 v)))))
1135 (excluded-namespaces *excluded-namespaces*)
1136 (extension-namespaces *extension-namespaces*)
1137 (variable
1138 (make-variable :param-p (namep <variable> "param")))
1139 (forwards-compatible-p *forwards-compatible-p*)
1140 (value-thunk-setter
1141 (lambda ()
1142 (let* ((*instruction-base-uri* instruction-base-uri)
1143 (*excluded-namespaces* excluded-namespaces)
1144 (*extension-namespaces* extension-namespaces)
1145 (*forwards-compatible-p* forwards-compatible-p)
1147 (compile-global-variable <variable> global-env)))
1148 (setf value-thunk fn)
1149 (setf (variable-value-thunk variable) fn)))))
1150 (setf (variable-value-thunk-setter variable)
1151 value-thunk-setter)
1152 (setf (gethash (cons local-name uri)
1153 (initial-global-variable-thunks global-env))
1154 global-variable-thunk)
1155 (setf (variable-chain-thunk chain) global-variable-thunk)
1156 (push variable (variable-chain-definitions chain))
1157 chain))))))
1159 (defun parse-keys! (stylesheet <transform> env)
1160 (xpath:with-namespaces ((nil #.*xsl*))
1161 (do-toplevel (<key> "key" <transform>)
1162 (with-import-magic (<key> env)
1163 (let ((*instruction-base-uri* (stp:base-uri <key>)))
1164 (only-with-attributes (name match use) <key>
1165 (unless name (xslt-error "key name attribute not specified"))
1166 (unless match (xslt-error "key match attribute not specified"))
1167 (unless use (xslt-error "key use attribute not specified"))
1168 (multiple-value-bind (local-name uri)
1169 (decode-qname name env nil)
1170 (add-key stylesheet
1171 (cons local-name uri)
1172 (compile-xpath `(xpath:xpath ,(parse-key-pattern match))
1173 env)
1174 (compile-xpath use
1175 (make-instance 'key-environment))))))))))
1177 (defun prepare-global-variables (stylesheet <transform>)
1178 (xpath:with-namespaces ((nil #.*xsl*))
1179 (let* ((igvt (stylesheet-initial-global-variable-thunks stylesheet))
1180 (global-env (make-instance 'global-variable-environment
1181 :initial-global-variable-thunks igvt))
1182 (chains '()))
1183 (do-toplevel (<variable> "variable|param" <transform>)
1184 (let ((chain
1185 (parse-global-variable! stylesheet <variable> global-env)))
1186 (xslt-trace "parsing global variable ~s (uri ~s)"
1187 (variable-chain-local-name chain)
1188 (variable-chain-uri chain))
1189 (when (find chain
1190 chains
1191 :test (lambda (a b)
1192 (and (equal (variable-chain-local-name a)
1193 (variable-chain-local-name b))
1194 (equal (variable-chain-uri a)
1195 (variable-chain-uri b)))))
1196 (xslt-error "duplicate definition for global variable ~A"
1197 (variable-chain-local-name chain)))
1198 (push chain chains)))
1199 (setf chains (nreverse chains))
1200 (let ((table (stylesheet-global-variables stylesheet))
1201 (newlen (length *global-variable-declarations*)))
1202 (adjust-array table newlen :fill-pointer newlen)
1203 (dolist (chain chains)
1204 (setf (elt table (variable-chain-index chain)) chain)))
1205 (lambda ()
1206 ;; now that the global environment knows about all variables, run the
1207 ;; thunk setters to perform their compilation
1208 (mapc (lambda (chain)
1209 (dolist (var (variable-chain-definitions chain))
1210 (funcall (variable-value-thunk-setter var))))
1211 chains)))))
1214 (defstruct template
1215 match-expression
1216 compiled-pattern
1217 name
1218 import-priority
1219 apply-imports-limit
1220 priority
1221 position
1222 mode
1223 mode-qname
1224 params
1225 body
1226 n-variables
1227 ;; for profiling output only:
1228 unparsed-qname
1229 stylesheet
1230 base-uri)
1232 (defun parse-templates! (stylesheet <transform> env)
1233 (let ((i 0))
1234 (do-toplevel (<template> "template" <transform>)
1235 (let ((*namespaces* (acons-namespaces <template>))
1236 (*instruction-base-uri* (stp:base-uri <template>)))
1237 (with-import-magic (<template> env)
1238 (dolist (template (compile-template <template> env i))
1239 (setf (template-stylesheet template) stylesheet)
1240 (setf (template-base-uri template) (stp:base-uri <template>))
1241 (let ((name (template-name template)))
1242 (if name
1243 (let* ((table (stylesheet-named-templates stylesheet))
1244 (head (car (gethash name table))))
1245 (when (and head (eql (template-import-priority head)
1246 (template-import-priority template)))
1247 ;; fixme: is this supposed to be a run-time error?
1248 (xslt-error "conflicting templates for ~A" name))
1249 (push template (gethash name table)))
1250 (let ((mode (ensure-mode/qname stylesheet
1251 (template-mode-qname template)
1252 env)))
1253 (setf (template-mode template) mode)
1254 (push template (mode-templates mode))))))))
1255 (incf i))))
1258 ;;;; APPLY-STYLESHEET
1260 (deftype xml-designator () '(or runes:xstream runes:rod array stream pathname))
1262 (defun unalias-uri (uri)
1263 (let ((result
1264 (gethash uri (stylesheet-namespace-aliases *stylesheet*)
1265 uri)))
1266 (check-type result string)
1267 result))
1269 (defstruct (parameter
1270 (:constructor make-parameter (value local-name &optional uri)))
1271 (uri "")
1272 local-name
1273 value)
1275 (setf (documentation 'parameter 'type)
1276 "The class of top-level parameters to XSLT stylesheets.
1278 Parameters are identified by expanded names, i.e. a namespace URI
1279 and local-name.
1281 Their value is string.
1283 @see-constructor{make-parameter}
1284 @see-slot{parameter-uri}
1285 @see-slot{parameter-local-name}
1286 @see-slot{parameter-value}")
1288 (setf (documentation 'make-parameter 'function)
1289 "@arg[value]{The parameter's value, a string.}
1290 @arg[local-name]{The parameter's local name, a string.}
1291 @arg[local-name]{The parameter's namespace URI, a string.}
1292 @return{An instance of @class{parameter}.}
1294 @short{Creates a paramater.}
1296 @see{parameter-uri}
1297 @see{parameter-local-name}
1298 @see{parameter-value}")
1300 (setf (documentation 'parameter-uri 'function)
1301 "@arg[instance]{An instance of @class{parameter}.}
1302 @return{A string}
1303 @return{Returns the parameter's namespace URI.}
1305 @see{parameter-local-name}
1306 @see{parameter-value}")
1308 (setf (documentation 'parameter-local-name 'function)
1309 "@arg[instance]{An instance of @class{parameter}.}
1310 @return{A string}
1311 @return{Returns the parameter's local name.}
1313 @see{parameter-uri}
1314 @see{parameter-value}")
1316 (setf (documentation 'parameter-value 'function)
1317 "@arg[instance]{An instance of @class{parameter}.}
1318 @return{A string}
1319 @return{Returns the parameter's value.}
1321 @see{parameter-uri}
1322 @see{parameter-local-name}")
1324 (defun find-parameter-value (local-name uri parameters)
1325 (dolist (p parameters)
1326 (when (and (equal (parameter-local-name p) local-name)
1327 (equal (parameter-uri p) uri))
1328 (return (parameter-value p)))))
1330 (defvar *uri-resolver*)
1332 (defun parse-allowing-microsoft-bom (pathname handler)
1333 (with-open-file (s pathname :element-type '(unsigned-byte 8))
1334 (unless (and (eql (read-byte s nil) #xef)
1335 (eql (read-byte s nil) #xbb)
1336 (eql (read-byte s nil) #xbf))
1337 (file-position s 0))
1338 (cxml:parse s handler)))
1340 (defstruct source-document
1342 root-node
1343 (indices (make-hash-table)))
1345 (defvar *uri-to-document*)
1346 (defvar *root-to-document*)
1348 (defun %document (uri-string base-uri)
1349 (let* ((absolute-uri
1350 (puri:merge-uris uri-string (or base-uri "")))
1351 (resolved-uri
1352 (if *uri-resolver*
1353 (funcall *uri-resolver* absolute-uri)
1354 absolute-uri))
1355 (pathname
1356 (handler-case
1357 (uri-to-pathname resolved-uri)
1358 (cxml:xml-parse-error (c)
1359 (xslt-error "cannot find referenced document ~A: ~A"
1360 resolved-uri c))))
1361 (document (gethash pathname *uri-to-document*)))
1362 (unless document
1363 (let ((root-node
1364 (make-whitespace-stripper
1365 (handler-case
1366 (parse-allowing-microsoft-bom pathname
1367 (stp:make-builder))
1368 ((or file-error cxml:xml-parse-error) (c)
1369 (xslt-error "cannot parse referenced document ~A: ~A"
1370 pathname c)))
1371 (stylesheet-strip-thunk *stylesheet*)))
1372 (id (hash-table-count *root-to-document*)))
1373 (setf document (make-source-document :id id :root-node root-node))
1374 (setf (gethash pathname *uri-to-document*) document)
1375 (setf (gethash root-node *root-to-document*) document)))
1376 (when (puri:uri-fragment absolute-uri)
1377 (xslt-error "use of fragment identifiers in document() not supported"))
1378 (source-document-root-node document)))
1380 (xpath-sys:define-extension xslt *xsl*)
1382 (defun document-base-uri (node)
1383 (xpath-protocol:base-uri
1384 (cond
1385 ((xpath-protocol:node-type-p node :document)
1386 (xpath::find-in-pipe-if
1387 (lambda (x)
1388 (xpath-protocol:node-type-p x :element))
1389 (xpath-protocol:child-pipe node)))
1390 ((xpath-protocol:node-type-p node :element)
1391 node)
1393 (xpath-protocol:parent-node node)))))
1395 (xpath-sys:define-xpath-function/lazy
1396 xslt :document
1397 (object &optional node-set)
1398 (let ((instruction-base-uri *instruction-base-uri*))
1399 (lambda (ctx)
1400 (with-profile-counter (*parse-xml-counter*)
1401 (let* ((object (funcall object ctx))
1402 (node-set (and node-set (funcall node-set ctx)))
1403 (base-uri
1404 (if node-set
1405 (document-base-uri (xpath::textually-first-node node-set))
1406 instruction-base-uri)))
1407 (xpath-sys:make-node-set
1408 (if (xpath:node-set-p object)
1409 (xpath:map-node-set->list
1410 (lambda (node)
1411 (%document (xpath:string-value node)
1412 (if node-set
1413 base-uri
1414 (document-base-uri node))))
1415 object)
1416 (list (%document (xpath:string-value object) base-uri)))))))))
1419 (defun build-key-index (document key-conses)
1420 (let ((index (make-hash-table :test 'equal)))
1421 (dolist (key key-conses)
1422 (xpath:do-node-set
1423 (node
1424 (xpath:evaluate-compiled (key-match key)
1425 (xpath:make-context
1426 (source-document-root-node document))))
1427 (let* ((use-result (xpath:evaluate-compiled (key-use key) node))
1428 (uses (if (xpath:node-set-p use-result)
1429 (xpath:all-nodes use-result)
1430 (list use-result))))
1431 (dolist (use uses)
1432 (push node (gethash (xpath:string-value use) index))))))
1433 index))
1435 (defun %key (document key-conses value)
1436 (let* ((indices (source-document-indices document))
1437 (index (or (gethash key-conses indices)
1438 (setf (gethash key-conses indices)
1439 (build-key-index document key-conses)))))
1440 (gethash value index)))
1442 (xpath-sys:define-xpath-function/lazy xslt :key (name object)
1443 (let ((namespaces *namespaces*))
1444 (lambda (ctx)
1445 (let* ((qname (xpath:string-value (funcall name ctx)))
1446 (object (funcall object ctx))
1447 (expanded-name
1448 (multiple-value-bind (local-name uri)
1449 (decode-qname/runtime qname namespaces nil)
1450 (cons local-name uri)))
1451 (key-conses (find-key expanded-name *stylesheet*)))
1452 (xpath-sys:make-node-set
1453 (labels ((get-by-key (value)
1454 (%key (node-to-source-document (xpath:context-node ctx))
1455 key-conses
1456 (xpath:string-value value))))
1457 (xpath::sort-pipe
1458 (if (xpath:node-set-p object)
1459 (xpath::mappend-pipe #'get-by-key (xpath-sys:pipe-of object))
1460 (get-by-key object)))))))))
1462 ;; FIXME: add alias mechanism for XPath extensions in order to avoid duplication
1464 (xpath-sys:define-xpath-function/lazy xslt :current ()
1465 (when *without-xslt-current-p*
1466 (xslt-error "current() not allowed here"))
1467 #'(lambda (ctx)
1468 (xpath-sys:make-node-set
1469 (xpath-sys:make-pipe
1470 (xpath:context-starting-node ctx)
1471 nil))))
1473 (xpath-sys:define-xpath-function/lazy xslt :unparsed-entity-uri (name)
1474 #'(lambda (ctx)
1475 (or (xpath-protocol:unparsed-entity-uri (xpath:context-node ctx)
1476 (funcall name ctx))
1477 "")))
1479 (defun node-to-source-document (node)
1480 (gethash (xpath:first-node
1481 (xpath:evaluate (xpath:xpath (:path (:root :node))) node))
1482 *root-to-document*))
1484 (defun %get-node-id (node)
1485 (when (xpath:node-set-p node)
1486 (setf node (xpath::textually-first-node node)))
1487 (when node
1488 (format nil "d~D~A"
1489 (source-document-id (node-to-source-document node))
1490 (xpath-sys:get-node-id node))))
1492 (xpath-sys:define-xpath-function/lazy xslt :generate-id (&optional node-set-thunk)
1493 (if node-set-thunk
1494 #'(lambda (ctx)
1495 (%get-node-id (xpath:node-set-value (funcall node-set-thunk ctx))))
1496 #'(lambda (ctx)
1497 (%get-node-id (xpath:context-node ctx)))))
1499 (declaim (special *builtin-instructions*))
1501 (xpath-sys:define-xpath-function/lazy xslt :element-available (qname)
1502 (let ((namespaces *namespaces*)
1503 (extensions *extension-namespaces*))
1504 #'(lambda (ctx)
1505 (let ((qname (funcall qname ctx)))
1506 (multiple-value-bind (local-name uri)
1507 (decode-qname/runtime qname namespaces nil)
1508 (cond
1509 ((equal uri *xsl*)
1510 (and (gethash local-name *builtin-instructions*) t))
1511 ((find uri extensions :test #'equal)
1512 (and (find-extension-element local-name uri) t))
1514 nil)))))))
1516 (xpath-sys:define-xpath-function/lazy xslt :function-available (qname)
1517 (let ((namespaces *namespaces*))
1518 #'(lambda (ctx)
1519 (let ((qname (funcall qname ctx)))
1520 (multiple-value-bind (local-name uri)
1521 (decode-qname/runtime qname namespaces nil)
1522 (and (zerop (length uri))
1523 (or (xpath-sys:find-xpath-function local-name *xsl*)
1524 (xpath-sys:find-xpath-function local-name uri))
1525 t))))))
1527 (xpath-sys:define-xpath-function/lazy xslt :system-property (qname)
1528 (let ((namespaces *namespaces*))
1529 (lambda (ctx)
1530 (let ((qname (xpath:string-value (funcall qname ctx))))
1531 (multiple-value-bind (local-name uri)
1532 (decode-qname/runtime qname namespaces nil)
1533 (if (equal uri *xsl*)
1534 (cond
1535 ((equal local-name "version")
1536 "1")
1537 ((equal local-name "vendor")
1538 "Xuriella")
1539 ((equal local-name "vendor-url")
1540 "http://repo.or.cz/w/xuriella.git")
1542 ""))
1543 ""))))))
1545 ;; FIXME: should there be separate uri-resolver arguments for stylesheet
1546 ;; and data?
1547 (defun apply-stylesheet
1548 (stylesheet source-designator
1549 &key output parameters uri-resolver navigator)
1550 "@arg[stylesheet]{a stylesheet designator (see below for details)}
1551 @arg[source-designator]{a source document designator (see below for details)}
1552 @arg[output]{optional output sink designator (see below for details)}
1553 @arg[parameters]{a list of @class{parameter} instances}
1554 @arg[uri-resolver]{optional function of one argument}
1555 @arg[navigator]{optional XPath navigator}
1556 @return{The value returned by sax:end-document when called on the
1557 designated output sink.}
1559 @short{Apply a stylesheet to a document.}
1561 This function takes @code{stylesheet} (either a parsed @class{stylesheet}
1562 or a designator for XML file to be parsed) and a source document, specified
1563 using the XML designator @code{source-designator}, and applies the
1564 stylesheet to the document.
1566 An XML designator is any value accepted by @code{cxml:parse}, in particular:
1567 @begin{ul}
1568 @item{Pathnames -- The file referred to by the pathname will parsed
1569 using cxml.}
1570 @item{Stream -- The stream will be parsed using cxml.}
1571 @item{Xstream -- Similar to the stream case, but using cxml's internal
1572 representation of rune streams.}
1573 @item{String -- The string itself will be parsed as an XML document,
1574 and is assumed to have been decoded into characters already.}
1575 @item{Array of (unsigned-byte 8) -- The array itself will be parsed as
1576 an XML document (which has not been decoded yet).}
1577 @end{ul}
1579 Note: Since strings are interpreted as documents, namestrings are
1580 not acceptable. Use pathnames instead of namestrings.
1582 An output sink designator is has the following form:
1583 @begin{ul}
1584 @item{Null -- Designates a string sink. I.e., the result document
1585 of the stylesheet will be returned as a string. This as the default.}
1586 @item{Pathnames -- The file referred to by the pathname will created
1587 and written to.}
1588 @item{Stream -- The stream will be written to.}
1589 @item{SAX or HAX handler -- Events will be sent directly to this sink.}
1590 @end{ul}
1592 Note: Specificaton of a sink overrides the specification of XML or HTML
1593 output method in the styl.sheet.
1595 Parameters are identified by names, and have values that are strings.
1596 Top-level parameters of these names are bound accordingly. If a paramater
1597 is not specified, its default value is computed as implemented in the
1598 stylesheet. If parameters are specified that the stylesheet does not
1599 recognize, they will be ignored.
1601 A @code{uri-resolver} is a function taking a PURI object as an argument
1602 and returning a PURI object as a value. The URI resolver will be invoked
1603 for all references to external files, e.g. at compilation time using
1604 xsl:import and xsl:include, and at run-time using the document() function.
1606 The URI resolver can be used to rewrite URLs so that file http:// links
1607 are replaced by file:// links, for example. Another application are
1608 URI resolvers that signal an error instead of returning, for example
1609 so that file:// links forbidden.
1611 The specified @code{navigator} will be passed to XPath protocol functions.
1613 @see{parse-stylesheet}"
1614 (with-profile-counter (*apply-stylesheet-counter*)
1615 (when (typep stylesheet 'xml-designator)
1616 (with-profile-counter (*parse-stylesheet-counter*)
1617 (setf stylesheet
1618 (handler-bind
1619 ((cxml:xml-parse-error
1620 (lambda (c)
1621 (xslt-error "cannot parse stylesheet: ~A" c))))
1622 (parse-stylesheet stylesheet :uri-resolver uri-resolver)))))
1623 (with-resignalled-errors ()
1624 (invoke-with-output-sink
1625 (lambda ()
1626 (let* ((*uri-to-document* (make-hash-table :test 'equal))
1627 (*root-to-document*
1628 ;; fixme? should be xpath-protocol:node-equal
1629 (make-hash-table :test 'equal))
1630 (xpath:*navigator* (or navigator :default-navigator))
1631 (puri:*strict-parse* nil)
1632 (*stylesheet* stylesheet)
1633 (*empty-mode* (make-mode))
1634 (*default-mode* (find-mode stylesheet nil))
1635 (global-variable-chains
1636 (stylesheet-global-variables stylesheet))
1637 (*global-variable-values*
1638 (make-variable-value-array (length global-variable-chains)))
1639 (*uri-resolver* uri-resolver)
1640 (source-document
1641 (if (typep source-designator 'xml-designator)
1642 (with-profile-counter (*parse-xml-counter*)
1643 (cxml:parse source-designator (stp:make-builder)))
1644 source-designator))
1645 (xpath-root-node
1646 (make-whitespace-stripper
1647 source-document
1648 (stylesheet-strip-thunk stylesheet)))
1649 (ctx (xpath:make-context xpath-root-node))
1650 (document (make-source-document
1651 :id 0
1652 :root-node xpath-root-node)))
1653 (when (pathnamep source-designator) ;fixme: else use base uri?
1654 (setf (gethash source-designator *uri-to-document*) document))
1655 (setf (gethash xpath-root-node *root-to-document*) document)
1656 (map nil
1657 (lambda (chain)
1658 (let ((head (car (variable-chain-definitions chain))))
1659 (when (variable-param-p head)
1660 (let ((value
1661 (find-parameter-value
1662 (variable-chain-local-name chain)
1663 (variable-chain-uri chain)
1664 parameters)))
1665 (when value
1666 (setf (global-variable-value
1667 (variable-chain-index chain))
1668 value))))))
1669 global-variable-chains)
1670 (map nil
1671 (lambda (chain)
1672 (funcall (variable-chain-thunk chain) ctx))
1673 global-variable-chains)
1674 ;; zzz we wouldn't have to mask float traps here if we used the
1675 ;; XPath API properly. Unfortunately I've been using FUNCALL
1676 ;; everywhere instead of EVALUATE, so let's paper over that
1677 ;; at a central place to be sure:
1678 (xpath::with-float-traps-masked ()
1679 (apply-templates ctx :mode *default-mode*))))
1680 (stylesheet-output-specification stylesheet)
1681 output))))
1683 (defun find-attribute-set (local-name uri &optional (stylesheet *stylesheet*))
1684 (or (gethash (cons local-name uri) (stylesheet-attribute-sets stylesheet))
1685 (xslt-error "no such attribute set: ~A/~A" local-name uri)))
1687 (defun apply-templates/list (list &key param-bindings sort-predicate mode)
1688 (when sort-predicate
1689 (setf list
1690 (mapcar #'xpath:context-node
1691 (stable-sort (contextify-node-list list)
1692 sort-predicate))))
1693 (let* ((n (length list))
1694 (s/d (lambda () n)))
1695 (loop
1696 for i from 1
1697 for child in list
1699 (apply-templates (xpath:make-context child s/d i)
1700 :param-bindings param-bindings
1701 :mode mode))))
1703 (defvar *stack-limit* 200)
1705 (defun invoke-with-stack-limit (fn)
1706 (let ((*stack-limit* (1- *stack-limit*)))
1707 (unless (plusp *stack-limit*)
1708 (xslt-error "*stack-limit* reached; stack overflow"))
1709 (funcall fn)))
1711 (defun invoke-template (ctx template param-bindings)
1712 (let ((*lexical-variable-values*
1713 (make-variable-value-array (template-n-variables template))))
1714 (with-stack-limit ()
1715 (loop
1716 for (name-cons value) in param-bindings
1717 for (nil index nil) = (find name-cons
1718 (template-params template)
1719 :test #'equal
1720 :key #'car)
1722 (when index
1723 (setf (lexical-variable-value index) value)))
1724 (funcall (template-body template) ctx))))
1726 (defun apply-default-templates (ctx mode)
1727 (let ((node (xpath:context-node ctx)))
1728 (cond
1729 ((or (xpath-protocol:node-type-p node :processing-instruction)
1730 (xpath-protocol:node-type-p node :comment)))
1731 ((or (xpath-protocol:node-type-p node :text)
1732 (xpath-protocol:node-type-p node :attribute))
1733 (write-text (xpath-protocol:node-text node)))
1735 (apply-templates/list
1736 (xpath::force
1737 (xpath-protocol:child-pipe node))
1738 :mode mode)))))
1740 (defvar *apply-imports*)
1742 (defun apply-applicable-templates (ctx templates param-bindings finally)
1743 (labels ((apply-imports (&optional actual-param-bindings)
1744 (if templates
1745 (let* ((this (pop templates))
1746 (low (template-apply-imports-limit this))
1747 (high (template-import-priority this)))
1748 (setf templates
1749 (remove-if-not
1750 (lambda (x)
1751 (<= low (template-import-priority x) high))
1752 templates))
1753 (if *profiling-enabled-p*
1754 (invoke-template/profile ctx this actual-param-bindings)
1755 (invoke-template ctx this actual-param-bindings)))
1756 (funcall finally))))
1757 (let ((*apply-imports* #'apply-imports))
1758 (apply-imports param-bindings))))
1760 (defun apply-templates (ctx &key param-bindings mode)
1761 (apply-applicable-templates ctx
1762 (find-templates ctx (or mode *default-mode*))
1763 param-bindings
1764 (lambda ()
1765 (apply-default-templates ctx mode))))
1767 (defun call-template (ctx name &optional param-bindings)
1768 (apply-applicable-templates ctx
1769 (find-named-templates name)
1770 param-bindings
1771 (lambda ()
1772 (xslt-error "cannot find named template: ~s"
1773 name))))
1775 (defun find-templates (ctx mode)
1776 (let* ((matching-candidates
1777 (xpattern:matching-values (mode-match-thunk mode)
1778 (xpath:context-node ctx)))
1779 (npriorities
1780 (if matching-candidates
1781 (1+ (reduce #'max
1782 matching-candidates
1783 :key #'template-import-priority))
1785 (priority-groups (make-array npriorities :initial-element nil)))
1786 (dolist (template matching-candidates)
1787 (push template
1788 (elt priority-groups (template-import-priority template))))
1789 (loop
1790 for i from (1- npriorities) downto 0
1791 for group = (elt priority-groups i)
1792 for template = (maximize #'template< group)
1793 when template
1794 collect template)))
1796 (defun find-named-templates (name)
1797 (gethash name (stylesheet-named-templates *stylesheet*)))
1799 (defun template< (a b) ;assuming same import priority
1800 (let ((p (template-priority a))
1801 (q (template-priority b)))
1802 (cond
1803 ((< p q) t)
1804 ((> p q) nil)
1806 (xslt-cerror "conflicting templates:~_~A,~_~A"
1807 (template-match-expression a)
1808 (template-match-expression b))
1809 (< (template-position a) (template-position b))))))
1811 (defun maximize (< things)
1812 (when things
1813 (let ((max (car things)))
1814 (dolist (other (cdr things))
1815 (when (funcall < max other)
1816 (setf max other)))
1817 max)))
1819 (defun invoke-with-output-sink (fn output-spec output)
1820 (etypecase output
1821 (pathname
1822 (with-open-file (s output
1823 :direction :output
1824 :element-type '(unsigned-byte 8)
1825 :if-exists :rename-and-delete)
1826 (invoke-with-output-sink fn output-spec s)))
1827 ((or stream null)
1828 (invoke-with-output-sink fn
1829 output-spec
1830 (make-output-sink output-spec output)))
1831 ((or hax:abstract-handler sax:abstract-handler)
1832 (with-xml-output output
1833 (when (typep output '(or combi-sink auto-detect-sink))
1834 (sax:start-dtd output
1835 :autodetect-me-please
1836 (output-doctype-public output-spec)
1837 (output-doctype-system output-spec)))
1838 (funcall fn)))))
1840 (defun make-output-sink (output-spec stream)
1841 (let* ((ystream
1842 (if stream
1843 (let ((et (stream-element-type stream)))
1844 (cond
1845 ((or (null et) (subtypep et '(unsigned-byte 8)))
1846 (runes:make-octet-stream-ystream stream))
1847 ((subtypep et 'character)
1848 (runes:make-character-stream-ystream stream))))
1849 (runes:make-rod-ystream)))
1850 (omit-xml-declaration-p
1851 (boolean-or-error (output-omit-xml-declaration output-spec)))
1852 (sink-encoding (or (output-encoding output-spec) "UTF-8"))
1853 (sax-target
1854 (progn
1855 (setf (runes:ystream-encoding ystream)
1856 (cxml::find-output-encoding sink-encoding))
1857 (make-instance 'cxml::sink
1858 :ystream ystream
1859 :omit-xml-declaration-p omit-xml-declaration-p
1860 :encoding sink-encoding))))
1861 (flet ((make-combi-sink ()
1862 (make-instance 'combi-sink
1863 :hax-target (make-instance 'chtml::sink
1864 :ystream ystream)
1865 :sax-target sax-target
1866 :media-type (output-media-type output-spec)
1867 :encoding sink-encoding)))
1868 (let ((method-key (output-method output-spec)))
1869 (cond
1870 ((and (eq method-key :html)
1871 (null (output-doctype-system output-spec))
1872 (null (output-doctype-public output-spec)))
1873 (make-combi-sink))
1874 ((eq method-key :text)
1875 (make-text-filter sax-target))
1876 ((and (eq method-key :xml)
1877 (null (output-doctype-system output-spec)))
1878 sax-target)
1880 (make-auto-detect-sink (make-combi-sink) method-key)))))))
1882 (defun expression-priority (form)
1883 (let ((step (second form)))
1884 (if (and (null (cddr form))
1885 (listp step)
1886 (member (car step) '(:child :attribute))
1887 (null (cddr step)))
1888 (let ((name (second step)))
1889 (cond
1890 ((or (stringp name)
1891 (and (consp name)
1892 (or (eq (car name) :qname)
1893 (eq (car name) :processing-instruction))))
1894 0.0)
1895 ((and (consp name)
1896 (or (eq (car name) :namespace)
1897 (eq (car name) '*)))
1898 -0.25)
1900 -0.5)))
1901 0.5)))
1903 (defun parse-key-pattern (str)
1904 (with-resignalled-errors ()
1905 (with-forward-compatible-errors
1906 (xpath:parse-xpath "compile-time-error()") ;hack
1907 (let ((parsed
1908 (mapcar #'(lambda (item)
1909 `(:path (:root :node)
1910 (:descendant-or-self :node)
1911 ,@(cdr item)))
1912 (cdr (xpath::parse-pattern-expression str)))))
1913 (if (null (rest parsed))
1914 (first parsed)
1915 `(:union ,@parsed))))))
1917 (defun compile-value-thunk (value env)
1918 (if (and (listp value) (eq (car value) 'progn))
1919 (let ((inner-thunk (compile-instruction value env)))
1920 (lambda (ctx)
1921 (apply-to-result-tree-fragment ctx inner-thunk)))
1922 (compile-xpath value env)))
1924 (defun compile-var-binding (name value env)
1925 (multiple-value-bind (local-name uri)
1926 (decode-qname name env nil)
1927 (let ((thunk (xslt-trace-thunk
1928 (compile-value-thunk value env)
1929 "local variable ~s = ~s" name :result)))
1930 (list (cons local-name uri)
1931 (push-variable local-name
1933 *lexical-variable-declarations*)
1934 thunk))))
1936 (defun compile-var-bindings (forms env)
1937 (loop
1938 for (name value) in forms
1939 collect (compile-var-binding name value env)))
1941 (defmacro sometimes-with-attributes ((&rest attrs) node &body body)
1942 (let ((x (gensym)))
1943 `(let ((,x ,node))
1944 (if *forwards-compatible-p*
1945 (stp:with-attributes (,@attrs) ,x ,@body)
1946 (only-with-attributes (,@attrs) ,x ,@body)))))
1948 (defun compile-template (<template> env position)
1949 (sometimes-with-attributes (match name priority mode) <template>
1950 (unless (or name match)
1951 (xslt-error "missing match in template"))
1952 (multiple-value-bind (params body-pos)
1953 (loop
1954 for i from 0
1955 for child in (stp:list-children <template>)
1956 while (namep child "param")
1957 collect (parse-param child) into params
1958 finally (return (values params i)))
1959 (let* ((*lexical-variable-declarations* (make-empty-declaration-array))
1960 (param-bindings (compile-var-bindings params env))
1961 (body (parse-body <template> body-pos (mapcar #'car params)))
1962 (body-thunk (compile-instruction `(progn ,@body) env))
1963 (outer-body-thunk
1964 (xslt-trace-thunk
1965 #'(lambda (ctx)
1966 (unwind-protect
1967 (progn
1968 ;; set params that weren't initialized by apply-templates
1969 (loop for (name index param-thunk) in param-bindings
1970 when (eq (lexical-variable-value index nil) 'unbound)
1971 do (setf (lexical-variable-value index)
1972 (funcall param-thunk ctx)))
1973 (funcall body-thunk ctx))))
1974 "template: match = ~s name = ~s" match name))
1975 (n-variables (length *lexical-variable-declarations*)))
1976 (append
1977 (when name
1978 (multiple-value-bind (local-name uri)
1979 (decode-qname name env nil)
1980 (list
1981 (make-template :name (cons local-name uri)
1982 :import-priority *import-priority*
1983 :apply-imports-limit *apply-imports-limit*
1984 :params param-bindings
1985 :body outer-body-thunk
1986 :n-variables n-variables
1987 ;; record unparsed `name' for profiler output:
1988 :unparsed-qname name))))
1989 (when match
1990 (mapcar (lambda (expression)
1991 (let* ((compiled-pattern
1992 (xslt-trace-thunk
1993 (car (without-xslt-current ()
1994 (xpattern:compute-patterns
1995 `(:patterns ,expression)
1997 :dummy
1998 env)))
1999 "match-thunk for template (match ~s): ~s --> ~s"
2000 match expression :result))
2001 (p (if priority
2002 (xpath::parse-xnum priority)
2003 (expression-priority expression)))
2005 (progn
2006 (unless (and (numberp p)
2007 (not (xpath::inf-p p))
2008 (not (xpath::nan-p p)))
2009 (xslt-error "failed to parse priority"))
2010 (float p 1.0d0)))
2011 (template
2012 (make-template :match-expression expression
2013 :compiled-pattern compiled-pattern
2014 :import-priority *import-priority*
2015 :apply-imports-limit *apply-imports-limit*
2016 :priority p
2017 :position position
2018 :mode-qname mode
2019 :params param-bindings
2020 :body outer-body-thunk
2021 :n-variables n-variables)))
2022 (setf (xpattern:pattern-value compiled-pattern)
2023 template)
2024 template))
2025 (cdr (xpattern:parse-pattern-expression match)))))))))
2026 #+(or)
2027 (xuriella::parse-stylesheet #p"/home/david/src/lisp/xuriella/test.xsl")