1 ;;; -*- show-trailing-whitespace: t; indent-tabs: nil -*-
3 ;;; Copyright (c) 2007,2008 David Lichteblau, Ivan Shvedunov.
4 ;;; Copyright (c) 2004 David Lichteblau (for headcraft.de)
5 ;;; All rights reserved.
7 ;;; Redistribution and use in source and binary forms, with or without
8 ;;; modification, are permitted provided that the following conditions
11 ;;; * Redistributions of source code must retain the above copyright
12 ;;; notice, this list of conditions and the following disclaimer.
14 ;;; * Redistributions in binary form must reproduce the above
15 ;;; copyright notice, this list of conditions and the following
16 ;;; disclaimer in the documentation and/or other materials
17 ;;; provided with the distribution.
19 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
20 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
25 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 (in-package :xuriella
)
34 ;;; Convenience functions for serialization to SAX, similar in syntax
35 ;;; to what cxml offers, but with namespace handling as required for XSLT.
37 (defvar *current-element
*)
39 (defvar *start-tag-written-p
*)
41 (defmacro with-xml-output
(sink &body body
)
42 `(invoke-with-xml-output (lambda () ,@body
) ,sink
))
44 (defmacro with-output-sink-bound
((var) &body body
)
45 `(invoke-with-output-sink-bound (lambda (,var
) ,@body
)))
47 (defun invoke-with-xml-output (fn sink
)
49 (*current-element
* nil
)
50 (*start-tag-written-p
* t
))
51 (sax:start-document
*sink
*)
53 (sax:end-document
*sink
*)))
55 (defun invoke-with-output-sink-bound (fn)
56 (maybe-emit-start-tag)
59 (defmacro with-element
((local-name uri
&key suggested-prefix extra-namespaces
)
61 `(invoke-with-element (lambda () ,@body
)
64 :suggested-prefix
,suggested-prefix
65 :extra-namespaces
,extra-namespaces
))
67 (defun doctype (name public-id system-id
&optional internal-subset
)
68 (sax:start-dtd
*sink
* name public-id system-id
)
70 (sax:unparsed-internal-subset
*sink
* internal-subset
))
73 (defun maybe-emit-start-tag ()
74 (let ((elt *current-element
*))
75 (when (and elt
(not *start-tag-written-p
*))
76 (setf *start-tag-written-p
* t
)
77 (let* ((local-name (sink-element-local-name elt
))
78 (uri (sink-element-uri elt
))
79 (suggested-prefix (sink-element-suggested-prefix elt
))
80 (prefix (ensure-prefix-for-uri elt uri suggested-prefix
))
81 (qname (if (plusp (length prefix
))
82 (concatenate 'string prefix
":" local-name
)
85 (setf (sink-element-actual-qname elt
) qname
)
86 (dolist (attr (sink-element-attributes elt
))
87 (push (convert-attribute elt attr
) attrs
))
89 for
(prefix . uri
) in
(sink-element-new-namespaces elt
) do
90 (sax:start-prefix-mapping
*sink
* prefix uri
)
91 (push (make-xmlns-attribute prefix uri
) attrs
))
92 (sax:start-element
*sink
* uri local-name qname attrs
)))))
94 (defun convert-attribute (elt attr
)
95 (let* ((local-name (sink-attribute-local-name attr
))
96 (uri (sink-attribute-uri attr
))
97 (suggested-prefix (sink-attribute-suggested-prefix attr
))
98 (prefix (ensure-prefix-for-uri elt uri suggested-prefix
))
99 (qname (if (plusp (length prefix
))
100 (concatenate 'string prefix
":" local-name
)
102 (sax:make-attribute
:namespace-uri uri
103 :local-name local-name
105 :value
(sink-attribute-value attr
))))
107 (defun sink-element-find-uri (prefix elt
)
110 (sink-element-all-namespaces elt
)
114 (defun ensure-prefix-for-uri (elt uri
&optional suggested-prefix
)
117 (sink-element-all-namespaces elt
)
120 (prefix (car prefix-cons
))
123 (sink-element-find-uri prefix elt
))))
124 (if (and prefix-cons
(equal cross-check uri
))
128 for prefix
= suggested-prefix then
(format nil
"ns-~D" i
)
130 (sink-element-find-uri prefix elt
)
132 (let ((cons (cons prefix uri
)))
133 (push cons
(sink-element-all-namespaces elt
))
134 (push cons
(sink-element-new-namespaces elt
)))
137 (defun make-xmlns-attribute (prefix uri
)
139 :namespace-uri
#"http://www.w3.org/2000/xmlns/"
141 :qname
(if (zerop (length prefix
))
143 (concatenate 'string
"xmlns:" prefix
))
146 (defstruct sink-element
155 (defstruct sink-attribute
161 (defun invoke-with-element
162 (fn local-name uri
&key suggested-prefix extra-namespaces
)
163 (check-type local-name string
)
164 (check-type uri string
)
165 (check-type suggested-prefix
(or null string
))
166 (maybe-emit-start-tag)
167 (let* ((parent *current-element
*)
168 (elt (make-sink-element
169 :local-name local-name
171 :suggested-prefix suggested-prefix
172 :all-namespaces
(if parent
173 (sink-element-all-namespaces parent
)
174 *initial-namespaces
*)
177 (*current-element
* elt
)
178 (*start-tag-written-p
* nil
))
179 (process-extra-namespaces elt extra-namespaces
)
180 (multiple-value-prog1
182 (maybe-emit-start-tag)
183 (sax:end-element
*sink
* uri local-name
(sink-element-actual-qname elt
))
185 for
(prefix . uri
) in
(sink-element-new-namespaces elt
) do
186 (sax:end-prefix-mapping
*sink
* prefix
)))))
188 (defun process-extra-namespaces (elt extra-namespaces
)
190 for
(prefix . uri
) in extra-namespaces
193 ;; allow earlier conses in extra-namespaces to hide later ones.
195 (sink-element-new-namespaces elt
)
198 (let ((previous (sink-element-find-uri prefix elt
)))
200 ;; no need to declare what has already been done
202 (let ((cons (cons prefix uri
)))
203 (push cons
(sink-element-all-namespaces elt
))
204 (push cons
(sink-element-new-namespaces elt
))))))))
206 (defun write-attribute (local-name uri value
&key suggested-prefix
)
207 (check-type local-name string
)
208 (check-type uri string
)
209 (check-type value string
)
210 (check-type suggested-prefix
(or null string
))
212 ((null *current-element
*)
213 (xslt-error "attribute outside of element"))
214 (*start-tag-written-p
*
215 (xslt-cerror "attribute after start tag"))
216 ((equal local-name
"xmlns")
217 (xslt-error "attribute named xmlns"))
219 (setf (sink-element-attributes *current-element
*)
220 (cons (make-sink-attribute :local-name local-name
222 :suggested-prefix suggested-prefix
224 (delete-if (lambda (x)
225 (and (equal (sink-attribute-local-name x
)
227 (equal (sink-attribute-uri x
) uri
)))
228 (sink-element-attributes *current-element
*)))))))
230 (defun write-text (data)
231 (maybe-emit-start-tag)
232 (sax:characters
*sink
* data
)
235 (defun write-comment (data)
236 (maybe-emit-start-tag)
237 (sax:comment
*sink
* data
)
240 (defun write-processing-instruction (target data
)
241 (maybe-emit-start-tag)
242 (sax:processing-instruction
*sink
* target data
)
245 (defun write-unescaped (str)
246 (maybe-emit-start-tag)
247 (sax:unescaped
*sink
* str
))