2 ;;;; Copyright (c) 2008 Zachary Beane, All Rights Reserved
4 ;;;; Redistribution and use in source and binary forms, with or without
5 ;;;; modification, are permitted provided that the following conditions
8 ;;;; * Redistributions of source code must retain the above copyright
9 ;;;; notice, this list of conditions and the following disclaimer.
11 ;;;; * Redistributions in binary form must reproduce the above
12 ;;;; copyright notice, this list of conditions and the following
13 ;;;; disclaimer in the documentation and/or other materials
14 ;;;; provided with the distribution.
16 ;;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
17 ;;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 ;;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 ;;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
20 ;;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 ;;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
22 ;;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 ;;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24 ;;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 ;;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 ;;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 (defun skip-document-start (source)
35 (let ((type (klacks:peek source
)))
36 (when (eql :start-document type
)
37 (klacks:consume source
))
40 (defun skip-characters (source)
42 (if (member (klacks:peek source
) '(:characters
:comment
))
43 (klacks:consume source
)
46 (defun collect-characters (source)
47 (with-output-to-string (stream)
49 (multiple-value-bind (type data
)
51 (cond ((eql type
:characters
)
52 (write-string data stream
)
53 (klacks:consume source
))
57 (defun collect-rest-alist (source)
58 "Collect the rest of SOURCE, up to an un-nested closing tag, as an
59 alist of element names and their character contents."
62 (multiple-value-bind (type uri lname
)
64 (declare (ignore uri
))
66 (:characters
(klacks:consume source
))
68 (return (nreverse result
)))
70 (klacks:consume source
)
71 (push (cons lname
(collect-characters source
)) result
)
72 (klacks:find-event source
:end-element
)
73 (klacks:consume source
)))))))
75 ;;; Match failure conditions
77 (define-condition xml-binding-error
(error)
85 (lambda (condition stream
)
86 (format stream
"Unexpected XML structure: expected ~S, got ~S instead"
88 (actual condition
)))))
93 (defvar *binder-definitions
*
104 (defmacro defbinder
(name &body source
)
105 `(eval-when (:compile-toplevel
:load-toplevel
:execute
)
106 (setf (gethash ',name
*binder-definitions
*)
107 (make-instance 'binder
108 :closure
(make-binder ',@source
)
109 :source
',@source
))))
111 (defun find-binder (name &optional
(errorp t
))
112 (let ((binder (gethash name
*binder-definitions
*)))
115 (error "No binder named ~S" name
)))))
117 (defun xml-bind (binder-name source
)
118 (funcall (closure (find-binder binder-name
)) source
))
120 (defun try-to-xml-bind (binder-name source
)
121 "Like XML-BIND, but catches any XML-BINDING-ERRORs; if any errors
122 are caught, NIL is the primary value and the error object is the
125 (xml-bind binder-name source
)
126 (xml-binding-error (c)
129 ;;; Creating the matchers/binders
131 (defvar *current-element-name
*)
133 (defun create-element-start-matcher (element-name kk
)
134 "Return a function that expects to see the start of ELEMENT-NAME
136 (lambda (source bindings k
)
137 (skip-characters source
)
138 (multiple-value-bind (type uri lname qname
)
140 (declare (ignore uri qname
))
141 (when (not (eql type
:start-element
))
142 (error 'xml-binding-error
143 :expected
(list :start-element element-name
)
144 :actual
(list :event type
)))
145 (when (string/= element-name lname
)
146 (error 'xml-binding-error
147 :expected
(list :start-element element-name
)
148 :actual
(list type lname
)))
149 (klacks:consume source
)
150 (funcall kk source bindings k
))))
152 (defun create-element-end-matcher (element-name kk
)
153 "Return a function that expects to see the end of ELEMENT-NAME next in
155 (lambda (source bindings k
)
156 (skip-characters source
)
157 (multiple-value-bind (type uri lname qname
)
159 (declare (ignore uri qname
))
160 (when (not (eql type
:end-element
))
161 (error 'xml-binding-error
162 :expected
(list :end-element element-name
)
163 :actual
(list :event type lname
)))
164 (when (string/= element-name lname
)
165 (error 'xml-binding-error
166 :expected
(list :end-element element-name
)
167 :actual
(list type lname
)))
168 (klacks:consume source
)
169 (funcall kk source bindings k
))))
171 (defun create-bindings-extender (key kk
)
172 "Return a function that extends BINDINGS with KEY and a value of
173 whatever character data is pending in SOURCE."
174 (lambda (source bindings k
)
176 (acons key
(collect-characters source
) bindings
)
179 (defun create-skipper (element-name kk
)
180 "Return a function that skips input in SOURCE until it sees a
181 closing tag for ELEMENT-NAME. Nested occurrences of elements with the
182 same ELEMENT-NAME are also skipped."
184 (lambda (source bindings k
)
186 (multiple-value-bind (type uri lname
)
187 (klacks:consume source
)
188 (declare (ignore uri
))
189 (cond ((and (eql type
:end-element
)
190 (string= lname element-name
))
192 (return (funcall kk source bindings k
))
194 ((and (eql type
:start-element
)
195 (string= lname element-name
))
198 (defun create-bindings-returner ()
199 "Return a function that does nothing but return its BINDINGS,
200 effectively ending matching."
201 (lambda (source bindings k
)
202 (declare (ignore source k
))
203 (nreverse bindings
)))
205 (defmacro catching-xml-errors
(&body body
)
208 (xml-binding-error (c)
211 (defun create-sequence-binder (key forms kk
)
212 "Return a function that creates a list of sub-bindings based on a
213 sub-matcher, with KEY as the key."
214 (let ((binder (create-binder forms
(create-bindings-returner))))
215 (lambda (source bindings k
)
216 (let ((sub-bindings '()))
218 (skip-characters source
)
219 (multiple-value-bind (sub-binding failure
)
221 (funcall binder source nil k
))
226 (nreverse sub-bindings
)
229 (push sub-binding sub-bindings
))))))))
231 (defun create-alist-binder (key kk
)
232 "Return a function that returns the rest of SOURCE as an alist of
233 element-name/element-content data."
234 (lambda (source bindings k
)
236 (acons key
(collect-rest-alist source
) bindings
)
239 (defun create-optional-binder (subforms kk
)
240 (let ((binder (create-binder subforms kk
)))
241 (lambda (source bindings k
)
242 (skip-characters source
)
243 (multiple-value-bind (optional-bindings failure
)
244 (catching-xml-errors (funcall binder source bindings k
))
246 (funcall kk source bindings k
)
247 optional-bindings
)))))
249 (defun create-alternate-binder (subforms kk
)
250 (let ((binders (mapcar (lambda (form) (create-binder form kk
)) subforms
)))
251 (lambda (source bindings k
)
252 ;; FIXME: This xml-binding-error needs :expected and :action
253 ;; ooptions. Can get actual with peeking and expected by getting
254 ;; the cl:cars of subforms...maybe.
255 (dolist (binder binders
(error 'xml-binding-error
))
256 (multiple-value-bind (alt-bindings failure
)
257 (catching-xml-errors (funcall binder source bindings k
))
259 (return alt-bindings
)))))))
261 (defun create-sub-binder-binder (binder-name kk
)
262 (lambda (source bindings k
)
263 (let ((binder (find-binder binder-name
)))
264 (let ((sub-bindings (funcall (closure binder
) source
)))
265 (funcall k source
(append sub-bindings bindings
) kk
)))))
267 (defun create-special-processor (operator form k
)
268 "Handle special pattern processing forms like BIND, SKIP-REST, SEQUENCE,
271 (include (create-sub-binder-binder (second form
) k
))
272 (alternate (create-alternate-binder (rest form
) k
))
273 (bind (create-bindings-extender (second form
) k
))
274 (optional (create-optional-binder (second form
) k
))
275 (skip-rest (create-skipper *current-element-name
* k
))
277 (destructuring-bind (key subforms
)
279 (create-sequence-binder key subforms k
)))
281 (create-alist-binder (second form
) k
))))
283 (defun create-binder (form &optional
(k (create-bindings-returner)))
284 "Process FORM as an XML binder pattern and return a closure to
285 process an XML source."
286 (let ((operator (first form
)))
289 (let ((*current-element-name
* operator
))
290 (create-element-start-matcher *current-element-name
*
291 (create-binder (rest form
) k
))))
293 (create-element-end-matcher *current-element-name
*
296 (create-binder operator
(create-binder (rest form
) k
)))
298 (create-special-processor operator form k
)))))
300 (defun xml-source (source)
302 (cxml::cxml-source source
)
303 (t (cxml:make-source source
))))
305 (defun make-binder (form)
306 (let ((binder (create-binder form
(create-bindings-returner))))
308 (let ((source (xml-source source
)))
309 (skip-document-start source
)
313 (create-bindings-returner))))))
317 (defun xml-document-element (source)
318 (nth-value 2 (klacks:find-event
(xml-source source
) :start-element
)))
320 (defun bvalue (key bindings
)
321 (cdr (assoc key bindings
)))
325 (bvalue key binding
)))
327 (defmacro alist-bind
(bindings alist
&body body
)
328 (let ((binds (gensym)))
329 (flet ((one-binding (var)
330 (let ((keyword (intern (symbol-name var
) :keyword
)))
331 `(when (eql (caar ,binds
) ,keyword
)
332 (setf ,var
(cdr (pop ,binds
)))))))
334 (let ((,binds
,alist
))
335 ,@(mapcar #'one-binding bindings
)
341 (defgeneric merge-bindings
(object bindings
)
342 (:documentation
"Update OBJECT with the data from BINDINGS."))