1 ;;; -*- show-trailing-whitespace: t; indent-tabs-mode: 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 defun
/unparse
(name (&rest args
) &body body
)
61 (with-profile-counter (*unparse-xml-counter
*)
62 (let ((*unparse-xml-counter
* nil
))
65 (defmacro with-element
66 ((local-name uri
&key suggested-prefix extra-namespaces process-aliases
)
68 `(invoke-with-element (lambda () ,@body
)
71 :suggested-prefix
,suggested-prefix
72 :extra-namespaces
,extra-namespaces
73 :process-aliases
,process-aliases
))
75 (defun/unparse doctype
(name public-id system-id
&optional internal-subset
)
76 (sax:start-dtd
*sink
* name public-id system-id
)
78 (sax:unparsed-internal-subset
*sink
* internal-subset
))
81 (defstruct sink-element
91 (defstruct sink-attribute
97 (defun maybe-emit-start-tag ()
98 (let ((elt *current-element
*))
99 (when (and elt
(not *start-tag-written-p
*))
100 (setf *start-tag-written-p
* t
)
101 (let* ((local-name (sink-element-local-name elt
))
102 (uri (sink-element-uri elt
))
103 (suggested-prefix (sink-element-suggested-prefix elt
))
104 (prefix (ensure-prefix-for-uri elt uri suggested-prefix
))
105 (qname (if (plusp (length prefix
))
106 (concatenate 'string prefix
":" local-name
)
109 (setf (sink-element-actual-qname elt
) qname
)
110 (dolist (attr (sink-element-attributes elt
))
111 (push (convert-attribute elt attr
) attrs
))
113 for
(prefix . uri
) in
(sink-element-new-namespaces elt
) do
114 (sax:start-prefix-mapping
*sink
* prefix uri
)
115 (push (make-xmlns-attribute prefix uri
) attrs
))
116 (sax:start-element
*sink
* uri local-name qname attrs
)))))
118 (defun convert-attribute (elt attr
)
119 (let* ((local-name (sink-attribute-local-name attr
))
120 (uri (sink-attribute-uri attr
))
121 (suggested-prefix (sink-attribute-suggested-prefix attr
))
122 (prefix (ensure-prefix-for-uri elt uri suggested-prefix t
))
123 (qname (if (plusp (length prefix
))
124 (concatenate 'string prefix
":" local-name
)
126 (sax:make-attribute
:namespace-uri uri
127 :local-name local-name
129 :value
(sink-attribute-value attr
))))
131 (defun sink-element-find-uri (prefix elt
)
135 (sink-element-all-namespaces elt
)
139 (defun ensure-prefix-for-uri (elt uri suggested-prefix
&optional attributep
)
140 (check-type uri string
)
141 (setf suggested-prefix
(or suggested-prefix
"")) ;zzz
142 (when (or (equal suggested-prefix
"xmlns")
143 (equal suggested-prefix
"xml"))
144 (setf suggested-prefix
""))
147 (sink-element-all-namespaces elt
)
150 (prefix (car prefix-cons
))
153 (sink-element-find-uri prefix elt
))))
156 (unless (or attributep
157 (equal (sink-element-find-uri "" elt
) ""))
158 (push-sink-element-namespace elt
"" ""))
160 ((and (or (plusp (length suggested-prefix
))
162 (not (find suggested-prefix
163 (sink-element-new-namespaces elt
)
166 (not (find suggested-prefix
167 (sink-element-used-prefixes elt
)
169 (push-sink-element-namespace elt suggested-prefix uri
)
172 (equal cross-check uri
)
173 (or (plusp (length prefix
))
175 (pushnew prefix
(sink-element-used-prefixes elt
) :test
#'equal
)
180 for prefix
= (format nil
"ns-~D" i
)
181 while
(sink-element-find-uri prefix elt
)
183 (push-sink-element-namespace elt prefix uri
)
186 (defun make-xmlns-attribute (prefix uri
)
188 :namespace-uri
#"http://www.w3.org/2000/xmlns/"
190 :qname
(if (zerop (length prefix
))
192 (concatenate 'string
"xmlns:" prefix
))
195 (defparameter *initial-unparse-namespaces
*
197 ("xmlns" .
#"http://www.w3.org/2000/xmlns/")
198 ("xml" .
#"http://www.w3.org/XML/1998/namespace")))
200 (defun unalias-attribute-uri (uri)
201 (if (zerop (length uri
))
205 (defun invoke-with-element
206 (fn local-name uri
&key suggested-prefix extra-namespaces process-aliases
)
207 ;; fixme: don't litter this function with calls to with-profile-counter
208 (with-profile-counter (*unparse-xml-counter
*)
209 (check-type local-name string
)
210 (check-type uri string
)
211 (check-type suggested-prefix
(or null string
))
212 (maybe-emit-start-tag)
213 (when process-aliases
214 (setf uri
(unalias-uri uri
))))
215 (let* ((parent *current-element
*)
216 (elt (make-sink-element
217 :local-name local-name
219 :suggested-prefix suggested-prefix
220 :all-namespaces
(if parent
221 (sink-element-all-namespaces parent
)
222 *initial-unparse-namespaces
*)
225 (*current-element
* elt
)
226 (*start-tag-written-p
* nil
))
227 (with-profile-counter (*unparse-xml-counter
*)
228 ;; always establish explicitly copied namespaces first
229 ;; (not including declarations of the default namespace)
230 (process-extra-namespaces elt extra-namespaces process-aliases
)
231 ;; establish the element's prefix (which might have to be the default
232 ;; namespace if it's the empty URI)
233 (ensure-prefix-for-uri elt uri suggested-prefix
))
234 ;; we'll do attributes incrementally
235 (multiple-value-prog1
237 (with-profile-counter (*unparse-xml-counter
*)
238 (maybe-emit-start-tag)
239 (sax:end-element
*sink
* uri local-name
(sink-element-actual-qname elt
))
241 for
(prefix . uri
) in
(sink-element-new-namespaces elt
) do
242 (sax:end-prefix-mapping
*sink
* prefix
))))))
244 (defun process-extra-namespace (elt prefix uri process-aliases
)
245 (when process-aliases
246 (setf uri
(unalias-uri uri
)))
249 ;; don't touch the empty prefix, since we might need it for the empty
251 (zerop (length prefix
))
252 ;; don't touch the empty URI
254 ;; allow earlier conses in extra-namespaces to hide later ones.
255 ;; FIXME: add a good explanation here why we need to do this both
256 ;; here and in remove-extra-namespaces.
258 (sink-element-new-namespaces elt
)
261 (let ((previous (sink-element-find-uri prefix elt
)))
262 (if (equal uri previous
) ;no need to declare what has already been done
263 (pushnew prefix
(sink-element-used-prefixes elt
) :test
#'equal
)
264 (push-sink-element-namespace elt prefix uri
)))))
266 (defun process-extra-namespaces (elt extra-namespaces process-aliases
)
267 (loop for
(prefix . uri
) in extra-namespaces do
268 (process-extra-namespace elt prefix uri process-aliases
)))
270 (defun push-sink-element-namespace (elt prefix uri
)
273 ((equal prefix
"xml")
274 (assert (equal uri
"http://www.w3.org/XML/1998/namespace")))
275 ((equal prefix
"xmlns")
276 (assert (equal uri
"http://www.w3.org/2000/xmlns/")))
278 (let ((cons (cons prefix uri
)))
279 (push cons
(sink-element-all-namespaces elt
))
280 (push cons
(sink-element-new-namespaces elt
))))))
282 (defun/unparse write-attribute
283 (local-name uri value
&key suggested-prefix process-aliases
)
284 (check-type local-name string
)
285 (check-type uri string
)
286 (check-type value string
)
287 (check-type suggested-prefix
(or null string
))
288 (when process-aliases
289 (setf uri
(unalias-attribute-uri uri
)))
291 ((null *current-element
*)
292 (xslt-cerror "attribute outside of element"))
293 (*start-tag-written-p
*
294 (xslt-cerror "attribute after start tag"))
295 ((and (equal local-name
"xmlns") (equal uri
""))
296 (xslt-error "attribute named xmlns"))
298 (setf (sink-element-attributes *current-element
*)
299 (cons (make-sink-attribute :local-name local-name
301 :suggested-prefix suggested-prefix
303 (delete-if (lambda (x)
304 (and (equal (sink-attribute-local-name x
)
306 (equal (sink-attribute-uri x
) uri
)))
307 (sink-element-attributes *current-element
*)))))))
309 (defun/unparse write-extra-namespace
(prefix uri process-aliases
)
310 (check-type prefix string
)
311 (check-type uri string
)
313 ((null *current-element
*)
314 (xslt-error "attribute outside of element"))
315 (*start-tag-written-p
*
316 (xslt-cerror "namespace after start tag"))
317 ((zerop (length prefix
))
318 (xslt-cerror "refusing to copy declaration for default namespace"))
320 (process-extra-namespace *current-element
* prefix uri process-aliases
))))
322 (defun/unparse write-text
(data)
323 (maybe-emit-start-tag)
324 (sax:characters
*sink
* data
)
327 (defun/unparse write-comment
(data)
328 (maybe-emit-start-tag)
329 ;; kludge: rewrite this in a nicer way
330 (setf data
(cl-ppcre:regex-replace-all
"--" data
"- -"))
331 (setf data
(cl-ppcre:regex-replace-all
"--" data
"- -"))
332 (setf data
(cl-ppcre:regex-replace
"-$" data
"- "))
333 (sax:comment
*sink
* data
)
336 (defun nc-name-p (str)
337 (and (and (not (zerop (length str
)))
338 (cxml::name-start-rune-p
(elt str
0))
339 (every #'cxml
::name-rune-p str
))
340 (cxml::nc-name-p str
)))
342 (defun/unparse write-processing-instruction
(target data
)
343 (maybe-emit-start-tag)
344 (setf data
(cl-ppcre:regex-replace-all
"[?]>" data
"? >"))
347 (sax:processing-instruction
*sink
* target data
))
349 (xslt-cerror "PI target not an NCName: ~A" target
)))
352 (defun/unparse write-unescaped
(str)
353 (maybe-emit-start-tag)
354 (sax:unescaped
*sink
* str
))