Coverage report: /home/ellis/comp/core/lib/dat/xml/xml.lisp
Kind | Covered | All | % |
expression | 558 | 1429 | 39.0 |
branch | 79 | 210 | 37.6 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; lib/dat/xml.lisp --- XML Data Format
3
;; based on the re-implementation of https://github.com/rpgoldman/xmls
9
(defvar *strip-comments* t)
10
(defvar *compress-whitespace* t)
11
(defvar *discard-processing-instructions*)
13
(eval-when (:compile-toplevel :load-toplevel :execute)
14
(declaim (type vector *entities*))
21
(defvar *whitespace* (remove-duplicates
22
'(#\Newline #\Space #\Tab #\Return #\Linefeed))))
23
(defvar *char-escapes*
24
(let ((table (make-array 256 :element-type 'string :initial-element "")))
26
for code from 0 to 255
27
for char = (code-char code)
28
for entity = (first (find char *entities* :test #'char= :key #'second))
29
do (setf (svref table code)
32
(concatenate 'string "&" entity))
33
((and (or (< code 32) (> code 126))
36
(format nil "&#x~x;" code))
38
(format nil "~x" char))))
39
finally (return table))
42
(defvar *parser-stream* nil
43
"The currently-being-parsed stream. Used so that we can appropriately track
45
(defvar *parser-line-number* nil)
48
(define-condition xml-parse-error (error)
52
(:report (lambda (xpe stream)
53
(format stream "XML-PARSE-ERROR~@[ at line ~d~]"
56
(defmethod initialize-instance :after ((obj xml-parse-error) &key)
57
(unless (slot-value obj 'line)
58
(when *parser-line-number*
59
(setf (slot-value obj 'line) *parser-line-number*))))
62
(defstruct (xml-node (:constructor %make-xml-node))
68
(std:defaccessor ast:ast ((self xml-node)) (xml-node-children self))
69
(std:defaccessor std:name ((self xml-node)) (xml-node-name self))
71
(defun make-xml-node (&key name ns attrs child children)
72
"Convenience function for creating a new xml node."
73
(when (and child children)
74
(error "Cannot specify both :child and :children for MAKE-NODE."))
75
(let ((children (if child
78
(%make-xml-node :name name :ns ns
84
(target "" :type string)
85
(contents "" :type string))
88
(defun compress-whitespace (str)
89
(if *compress-whitespace*
91
(setf str (string-trim *whitespace* str))
92
(if (= 0 (length str))
97
(defun write-escaped (string stream)
98
(write-string (escape-for-html string) stream))
100
(defun escape-for-html (string)
101
"Escapes the characters #\\<, #\\>, #\\', #\\\", and #\\& for HTML output."
102
(with-output-to-string (out)
103
(with-input-from-string (in string)
104
(loop for char = (read-char in nil nil)
107
((#\<) (write-string "<" out))
108
((#\>) (write-string ">" out))
109
((#\") (write-string """ out))
110
((#\') (write-string "'" out))
111
((#\&) (write-string "&" out))
112
(otherwise (write-char char out)))))))
114
(defun make-extendable-string (&optional (size 10))
115
"Creates an adjustable string with a fill pointer."
117
:element-type 'character
121
(defun push-string (c string)
122
"Shorthand function for adding characters to an extendable string."
123
(vector-push-extend c string))
125
(defun translate-raw-value (raw-value)
126
"Helper function for xml generation."
129
(symbol (symbol-name raw-value))
130
(integer (format nil "~D" raw-value))
131
(float (format nil "~G" raw-value))))
133
(defun generate-xml (e s indent)
134
"Renders a lisp node tree to an xml string stream."
135
(if (> indent 0) (incf indent))
139
(dotimes (i (* 2 (- indent 2)))
140
(write-char #\Space s))
141
(format s "<~A~@[ xmlns=\"~A\"~]" (xml-node-name e) (xml-node-ns e))
142
(loop for a in (xml-node-attrs e)
144
(write-char #\Space s)
145
(write-string (first a) s)
148
(write-escaped (translate-raw-value (second a)) s)
149
(write-char #\" s))))
150
(if (null (xml-node-children e))
152
(write-string "/>" s)
153
(if (> indent 0) (write-char #\Newline s)))
156
(if (> indent 0) (write-char #\Newline s))
157
(mapc #'(lambda (c) (generate-xml c s indent)) (xml-node-children e))
160
(dotimes (i (* 2 (- indent 2)))
161
(write-char #\Space s))))
162
(format s "</~A>" (xml-node-name e))
163
(if (> indent 0) (write-char #\Newline s)))))
165
(generate-xml (translate-raw-value e) s indent))
167
(generate-xml (translate-raw-value e) s indent))
172
(dotimes (i (* 2 (- indent 2)))
173
(write-char #\Space s))))
175
(if (> indent 0) (write-char #\Newline s))))))
179
"Represents parser state. Passed among rules to avoid threading issues."
181
(lines 1 :type integer)
185
(defun resolve-entity (ent)
186
"Resolves the xml entity ENT to a character. Numeric entities are
187
converted using CODE-CHAR, which only works in implementations that
188
internally encode strings in US-ASCII, ISO-8859-1 or UCS."
189
(declare (type simple-string ent))
190
(or (and (>= (length ent) 2)
191
(char= (char ent 0) #\#)
193
(if (char= (char ent 1) #\x)
194
(parse-integer ent :start 2 :end (- (length ent) 1) :radix 16)
195
(parse-integer ent :start 1 :end (- (length ent) 1)))))
196
(second (find ent *entities* :test #'string= :key #'first))
197
(error "Unable to resolve entity ~S" ent)))
199
(declaim (inline peek-stream))
200
(defun peek-stream (stream)
201
"Looks one character ahead in the input stream. Serves as a potential hook for
202
character translation."
203
(peek-char nil stream nil))
205
(defun read-stream (stream)
206
"Reads a character from the stream, translating entities as it goes."
207
(let ((c (read-char stream nil)))
208
(if (and c (not (char= c #\&)))
210
(loop with ent = (make-extendable-string 5)
211
for char = (read-char stream)
212
do (push-string char ent)
213
until (char= char #\;)
214
finally (return (resolve-entity (coerce ent 'simple-string)))))))
216
;; Shadow READ-CHAR and UNREAD-CHAR
217
(defun read-char (&optional (stream *standard-input*) (eof-error-p t) eof-value recursive-p)
227
(throw 'char-return eof-value)))))
228
(common-lisp:read-char stream t nil recursive-p)))))
229
(when (and (eq stream *parser-stream*)
232
(incf *parser-line-number*))
235
(defun unread-char (char &optional (stream *standard-input*))
236
(when (char= char #\newline)
237
(decf *parser-line-number*))
238
(common-lisp:unread-char char stream))
240
(define-symbol-macro next-char (peek-stream (state-stream s)))
243
"Consumes one character from the input stream."
244
`(read-char (state-stream s)))
246
(defmacro puke (char)
247
"The opposite of EAT."
248
`(unread-char ,char (state-stream s)))
250
(defmacro match (&rest matchers)
251
"Attempts to match the next input character with one of the supplied matchers."
252
`(let ((c (peek-stream (state-stream s))))
254
(or ,@(loop for m in matchers
256
(standard-char `(char= ,m c))
258
;; cheat here a little bit - eat entire char entity instead
260
(read-stream (state-stream s)))))
262
(defmacro match-seq (&rest sequence)
263
"Tries to match the supplied matchers in sequence with characters in the input stream."
264
`(and ,@(loop for s in sequence
265
collect `(match ,s))))
267
(defmacro match* (&rest sequence)
268
"Matches any occurances of any of the supplied matchers."
269
`(loop with data = (make-extendable-string 10)
270
for c = (match ,@sequence)
272
do (push-string c data)
273
finally (return data)))
275
(defmacro match+ (&rest sequence)
276
"Matches one or more occurances of any of the supplied matchers."
277
`(and (peek ,@sequence)
278
(match* ,@sequence)))
280
(defmacro peek (&rest matchers)
281
"Looks ahead for an occurance of any of the supplied matchers."
282
`(let ((c (peek-stream (state-stream s))))
283
(or ,@(loop for m in matchers
285
(standard-char `(char= ,m c))
286
(symbol `(,m c)))))))
288
(defmacro must (&rest body)
289
"Throws a parse error if the supplied forms do not succeed."
291
(error 'xml-parse-error)))
295
"Common return type of all rule functions."
296
(type nil :type symbol)
299
(defun resolve-namespace (elem env)
300
"Maps the ns prefix to its associated url via the supplied ns env."
301
(let ((ns (xml-node-ns elem)))
303
(let ((nsurl (assoc ns e :test #'string=)))
305
(setf (xml-node-ns elem) (cadr nsurl))
308
;;; Match and Rule Utils
309
(defmacro defmatch (name &rest body)
310
"Match definition macro that provides a common lexical environment for matchers."
314
(defmacro defrule (name &rest body)
315
"Rule definition macro that provides a common lexical environment for rules."
319
(defmacro matchfn (name)
320
"Convenience macro for creating an anonymous function wrapper around a matcher macro."
321
`(lambda (s) (match ,name)))
323
(defun none-or-more (s func)
324
"Collects any matches of the supplied rule with the input stream."
325
(declare (type function func))
326
(let ((val (funcall func s)))
328
(multiple-value-bind (res nextval)
329
(none-or-more s func)
330
(values res (cons val nextval)))
333
(defun one-or-more (s func)
334
"Collects one or more matches of the supplied rule with the input stream."
335
(declare (type function func))
336
(let ((val (funcall func s)))
338
(multiple-value-bind (res nextval)
339
(none-or-more s func)
340
(declare (ignore res))
346
(and c (digit-char-p c)))
349
(and c (alpha-char-p c)))
351
;; Modified because *whitespace* is not defined at compile
352
;; time. [2004/08/31:rpg]
354
(member c *whitespace*))
356
;;; (#.*whitespace* t)
359
(defmatch namechar ()
361
(and c (alpha-char-p c))
362
(and c (digit-char-p c))
364
((#\. #\- #\_ #\:) t))))
366
(defmatch ncname-char ()
368
(and c (alpha-char-p c))
369
(and c (digit-char-p c))
373
(defmatch attr-text-dq ()
374
(and c (not (member c (list #\< #\")))))
376
(defmatch attr-text-sq ()
377
(and c (not (member c (list #\< #\')))))
379
(defmatch chardata ()
380
(and c (not (char= c #\<))))
382
(defmatch comment-char ()
383
(and c (not (eql c #\-))))
387
(and (peek letter #\_)
388
(match+ ncname-char)))
393
(setf name (ncname s))
397
(setf suffix (ncname s)))
399
(values name suffix)))
401
(defrule attr-or-nsdecl ()
402
(let (suffix name val)
404
(setf (values name suffix) (qname s))
415
(setf val (match* attr-text-dq))
421
(setf val (match* attr-text-sq))
424
(if (string= "xmlns" name)
425
(list 'nsdecl suffix val)
426
;; If SUFFIX is true, then NAME is Prefix and SUFFIX is
429
(list 'attr suffix val :attr-ns name)
430
(list 'attr name val))))))
433
(and (match+ ws-char)
434
(make-element :type 'whitespace :val nil)))
438
(peek namechar #\_ #\:)
441
(defrule ws-attr-or-nsdecl ()
446
(defrule start-tag ()
447
(let (name suffix attrs nsdecls)
450
(setf (values name suffix) (qname s))
451
(multiple-value-bind (res a)
452
(none-or-more s #'ws-attr-or-nsdecl)
453
(mapcar (lambda (x) (if (eq (car x) 'attr)
455
(push (cdr x) nsdecls)))
461
:name (or suffix name)
462
:ns (and suffix name)
470
(setf (values name suffix) (qname s))
473
(make-element :type 'end-tag :val (or suffix name)))))
477
(match-seq #\! #\- #\-)
479
(loop until (match-seq #\- #\- #\>)
482
(make-element :type 'comment)))
484
;; For the CDATA matching of ]]> I by hand generated an NFA, and then
485
;; determinized it (also by hand). Then I did a simpler thing of just pushing
486
;; ALL the data onto the data string, and truncating it when done.
487
(defrule comment-or-cdata ()
490
(must (or (comment s)
492
(match-seq #\[ #\C #\D #\A #\T #\A #\[)
493
(loop with data = (make-extendable-string 50)
496
do (push-string char data)
501
(trace! :cdata "State 0 Match #\], go to state {0,1} = 4.")
504
(trace! :cdata "State 0 Non-], go to (remain in) state 0."))))
508
(trace! :cdata "State 4 {0, 1}, match ], go to state {0,1,2} = 5")
511
(trace! :cdata "State 4 {0, 1}, Non-], go to state 0.")
516
(trace! :cdata "State 5 {0, 1, 2}, match ], stay in state 5."))
518
(trace! :cdata "State 5 {0, 1, 2}, match >, finish match and go to state 3.")
521
(trace! :cdata "State 5 {0, 1, 2}, find neither ] nor >; go to state 0.")
525
finally (return (make-element
528
;; rip the ]]> off the end of the data and return it...
529
(subseq data 0 (- (fill-pointer data) 3))
530
'simple-string)))))))))
533
(declaim (ftype function element)) ; forward decl for content rule
536
(must (or (comment-or-cdata s)
537
(processing-instruction s)
541
(and (setf content (match+ chardata))
542
(make-element :type 'data :val (compress-whitespace content)))))))
545
(let (elem children nsdecls end-name)
547
;; parse front end of tag
548
(multiple-value-bind (e n)
553
;; resolve namespaces *before* parsing children
554
(if nsdecls (push nsdecls (state-nsstack s)) t)
555
(or (if (or nsdecls (state-nsstack s))
556
(resolve-namespace elem (state-nsstack s)))
558
;; parse end-tag and children
563
(loop for c = (content s)
566
(element (case (element-type c)
568
(return (setf end-name (element-val c))))
569
;; processing instructions may be discarded
571
(unless *discard-processing-instructions*
572
(when (element-val c)
573
(push (element-val c) children))))
574
(t (if (element-val c)
575
(push (element-val c) children)))))))
576
(string= (xml-node-name elem) end-name)))
577
;; package up new node
579
(setf (xml-node-children elem) (nreverse children))
580
(make-element :type 'elem :val elem)))))
582
(defrule processing-instruction ()
587
(not (string= name "xml"))
588
;; contents of a processing instruction can be arbitrary stuff, as long
589
;; as it doesn't contain ?>...
590
(setf contents (pi-contents s))
591
;; if we get here, we have eaten ?> off the input in the course of
592
;; processing PI-CONTENTS
593
(make-element :type 'pi :val (make-proc-inst :target name :contents contents)))))
595
(defrule pi-contents ()
596
(loop with data = (make-extendable-string 50)
599
do (push-string char data)
604
(trace! :pi-contents "State 0 Match #\?, go to state 1.")
607
(trace! :pi-contents "State 0 ~c, go to (remain in) state 0." char))))
611
(trace! :pi-contents "State 1 Match #\>, done.")
614
(trace! :pi-contents "State 1, ~c, do not match #\>, return to 0." char)
617
finally (return (coerce
618
;; rip the ?> off the end of the data and return it...
619
(subseq data 0 (max 0 (- (fill-pointer data) 2)))
628
(setf contents (none-or-more s #'ws-attr-or-nsdecl))
630
(make-element :type 'xmldecl :val contents))))
632
(defrule comment-or-doctype ()
633
;; skip dtd - bail out to comment if it's a comment
634
;; only match doctype once
638
(and (not (state-got-doctype s))
639
(must (match-seq #\D #\O #\C #\T #\Y #\P #\E))
646
(setf (state-got-doctype s) t)
647
(make-element :type 'doctype)))))
652
(and (match #\<) (must (or (processing-instruction s)
653
(comment-or-doctype s)
659
(must (or (xmldecl s)
660
(comment-or-doctype s)
661
(setf elem (element s)))))
662
;; NOTE: I don't understand this: it seems to parse arbitrary crap
664
(loop for c = (misc s)
666
do (cond ((eql (element-type c) 'elem)
667
(return (setf elem c)))
668
((and (eql (element-type c) 'pi)
669
(not *discard-processing-instructions*))
670
(return (setf elem c))))))
672
(and elem (element-val elem))))
675
(defun write-xml (e s &key (indent nil))
676
"Renders a lisp node tree to an xml stream. Indents if indent is non-nil."
678
(toxml e :indent indent)
679
(generate-xml e s (if indent 1 0))))
681
(defun write-prologue (xml-decl doctype s)
682
"Render the leading <?xml ... ?> and <!DOCTYPE ... > tags to an xml stream."
684
(dolist (attrib xml-decl)
685
(format s " ~A=\"~A\"" (car attrib) (cdr attrib)))
688
(format s "<!DOCTYPE ~A>~%" doctype)))
690
(defun write-prolog (xml-decl doctype s)
691
(write-prologue xml-decl doctype s))
693
(defun toxml (e &key (indent nil))
694
"Renders a lisp node tree to an xml string."
695
(with-output-to-string (s)
696
(write-xml e s :indent indent)))
698
(defun xml-parse (s &key (compress-whitespace t) (quash-errors t))
699
"Parses the supplied stream or string into a lisp node tree."
700
(let* ((*compress-whitespace* compress-whitespace)
701
(*discard-processing-instructions* t)
704
(string (make-string-input-stream s))
706
(*parser-stream* stream)
707
(*parser-line-number* 1))
710
(document (make-state :stream stream))
712
(xml-parse-error () nil))
713
(document (make-state :stream stream)))))
716
(defun make-xmlrep (tag &key (representation-kind :node) namespace attribs children)
717
(case representation-kind
721
(list (list tag namespace) (list attribs) children))
723
(list tag (list attribs) children))))
725
(make-xml-node :name tag :ns namespace :attrs attribs :children children))
727
(error "REPRESENTATION-KIND must be :LIST or :NODE, got ~s" representation-kind))))
729
(defgeneric xmlrep-add-child! (xmlrep child)
730
(:method ((xmlrep xml-node) child)
731
(setf (xml-node-children xmlrep)
732
(append (xml-node-children xmlrep)
734
(:method ((xmlrep cons) child)
736
(append (cddr xmlrep)
739
(defgeneric xmlrep-tag (treenode)
740
(:method ((treenode xml-node))
741
(xml-node-name treenode))
742
(:method ((treenode cons))
743
(let ((tag-name (car treenode)))
744
;; detect the "namespaced" case
746
((consp tag-name) (car tag-name))
749
(defun xmlrep-tagmatch (tag treenode)
750
;;child nodes to XMLREPs could be strings or nodes
751
(unless (stringp treenode)
752
(string-equal tag (xmlrep-tag treenode))))
754
(defgeneric xmlrep-attribs (treenode)
755
(:method ((treenode xml-node))
756
(xml-node-attrs treenode))
757
(:method ((treenode cons))
760
(defgeneric (setf xmlrep-attribs) (attribs treenode)
761
(:argument-precedence-order treenode attribs)
762
(:method (attribs (treenode xml-node))
763
(setf (xml-node-attrs treenode) attribs))
764
(:method (attribs (treenode cons))
765
(setf (cadr treenode) attribs)))
767
(defgeneric xmlrep-children (treenode)
768
(:method ((treenode xml-node))
769
(xml-node-children treenode))
770
(:method ((treenode cons))
773
(defgeneric (setf xmlrep-children) (children treenode)
774
(:argument-precedence-order treenode children)
775
(:method (children (treenode xml-node))
776
(setf (xml-node-children treenode) children))
777
(:method (children (treenode cons))
778
(setf (cddr treenode) children)))
780
(defun xmlrep-string-child (treenode &optional (if-unfound :error))
781
(let ((children (xmlrep-children treenode)))
782
(if (and (eq (length children) 1) (typep (first children) 'string))
784
(if (eq if-unfound :error)
785
(error "Node does not have a single string child: ~a" treenode)
789
(defun xmlrep-integer-child (treenode)
790
(parse-integer (xmlrep-string-child treenode)))
792
(defun xmlrep-find-child-tags (tag treenode)
793
"Find all the children of TREENODE with TAG."
794
(remove-if-not #'(lambda (child) (xmlrep-tagmatch tag child))
795
(xmlrep-children treenode)))
797
(defun xmlrep-find-child-tag (tag treenode
798
&optional (if-unfound :error))
799
"Find a single child of TREENODE with TAG. Returns an error
800
if there is more or less than one such child."
801
(let ((matches (xmlrep-find-child-tags tag treenode)))
802
(case (length matches)
803
(0 (if (eq if-unfound :error)
804
(error "Couldn't find child tag ~A in ~A"
808
(otherwise (error "Child tag ~A multiply defined in ~A"
811
(defun xmlrep-attrib-value (attrib treenode
812
&optional (if-undefined :error))
813
"Find the value of ATTRIB, a string, in TREENODE.
814
if there is no ATTRIB, will return the value of IF-UNDEFINED,
815
which defaults to :ERROR."
816
(let ((found-attrib (find-attrib attrib treenode)))
818
(second found-attrib))
819
((eq if-undefined :error)
820
(error "XML attribute ~S undefined in ~S"
825
(defun find-attrib (attrib treenode)
826
"Returns the attrib CELL (not the attrib value) from
827
TREENODE, if found. This cell will be a list of length 2,
828
the attrib name (a string) and its value."
829
(find attrib (xmlrep-attribs treenode)
833
(defun (setf xmlrep-attrib-value) (value attrib treenode)
834
;; ideally, we would check this...
835
(let ((old-val (xmlrep-attrib-value attrib treenode nil)))
838
;; just delete this attribute...
839
(setf (xmlrep-attribs treenode)
840
(remove attrib (xmlrep-attribs treenode)
844
(t (let ((cell (find-attrib attrib treenode)))
845
(setf (second cell) value)
849
nil) ; no old value to delete
851
(setf (xmlrep-attribs treenode)
852
(append (xmlrep-attribs treenode)
853
(list (list attrib value))))
856
(defun xmlrep-boolean-attrib-value (attrib treenode
857
&optional (if-undefined :error))
858
"Find the value of ATTRIB, a string, in TREENODE.
859
The value should be either \"true\" or \"false\". The
860
function will return T or NIL, accordingly. If there is no ATTRIB,
861
will return the value of IF-UNDEFINED, which defaults to :ERROR."
862
(let ((val (xmlrep-attrib-value attrib treenode
864
(cond ((string-equal val "true")
866
((string-equal val "false") nil)
867
(t (error "Not a boolean value, ~A for attribute ~A."
871
(defun extract-path (key-list xml)
872
"Extracts data from XML parse tree. KEY-LIST is a path for descending down
873
named objects in the XML parse tree. For each KEY-LIST element, XML subforms
874
are searched for a matching tag name. Finally the whole last XML subform on the
875
path is normally returned if found; however the symbol * may be added at the end
876
of KEY-LIST to return list of all objects /enclosed/ by the last subform on
877
KEY-LIST. Also KEY-LIST may be dotted as explained below to return XML tag
878
attributes from the last subform on KEY-LIST.
880
XML is to have the forms as returned by PARSE-TO-LIST or PARSE:
881
(tag-name (attributes-list) subform*),
882
((tag-name . name-space) (attributes-list) subform*), or
883
#s(node :name tag-name
885
:attrs attributes-list
888
The first element in KEY-LIST must match the top level form in XML.
889
Subsequently each element in the KEY-LIST is to match a subform.
891
An element of KEY-LIST may be a string atom. In that case the first subform
892
with tag-name matching the string is matched. An element of KEY-LIST may also
893
be a list of string atoms in this format:
894
(tag-name (attribute-name attribute-value) ...)
896
The first subform with name matching TAG-NAME /and/ having attributes matching
897
attribute-names and attribute-values is matched. Zero or more attribute/value
900
Normally the whole subform matching last element in KEY-LIST is returned. The
901
symbol * can be the last element of KEY-LIST to return list of all subforms
902
enclosed by the last matched form. Attributes of last matched subform may be
903
searched by ending KEY-LIST in dot notation, in which case the string after dot
904
matches an attribute name. The two element list of attribute name and value is
905
returned. The symbol * may be used after dot to return the whole attribute list.
907
In the case where the search fails NIL is returned. However it is possible that
908
the search partially succeeds down the key path. Three values are returned
909
altogether and the 2nd and 3rd values give information about how much of
910
KEY-LIST was matched, and at what point in XML:
911
(values RESULT KEY-LIST-FRAGMENT XML-FRAGMENT)
913
When RESULT is non-NIL, the others are NIL. When result is NIL however, the
916
The last XML form that /did/ match in the key list. It matches the first
917
element of KEY-LIST-FRAGMENT.
920
The /remaining/ part of the KEY-LIST that did not succeed. However the
921
/first/ item on KEY-LIST-FRAGMENT matches the XML-FRAGMENT returned. The
922
failure is at the second item on KEY-LIST-FRAGMENT.
924
In the case of complete failure, where even the very first item on KEY-LIST does not
925
match the top XML form given, all three return values are NIL. (It suffices to check
926
the first two return values.)"
927
(labels ((attribs-match-p ( key-attribs-list xml-attribs-list )
928
;; search for (attr-name attr-value) pairs from KEY-ATTRIBS-LIST on
929
;; XML-ATTRIBS-LIST. true if all key pairs found.
931
:with attribs-match-var := t
932
:for attrib-key-pair :in key-attribs-list
934
(setq attribs-match-var
935
(and attribs-match-var
936
(find attrib-key-pair xml-attribs-list :test #'equal)))
937
:finally (return attribs-match-var)))
938
(find-test ( key xml-form )
939
;; test whether the XML-FORM matches KEY
941
;; just the XML tag name in key
942
;; XML name is simple string
944
(stringp (xmlrep-tag xml-form)))
945
(string-equal key (xmlrep-tag xml-form)))
946
;; key form (tag-name (attr-name attr-value) ...)
947
((and (find-test (car key) xml-form)
948
(attribs-match-p (cdr key) (xmlrep-attribs xml-form))))))
949
(descend ( key-list xml-form )
950
;; recursive run down KEY-LIST. If XML-FORM runs down to NIL before reaching
951
;; the end of KEY-LIST, it will be NIL at the end. If not, what is
952
;; remaining of XML-FORM is the found item.
954
;; KEY-LIST ends without dotted item, at the target XML form
955
((null (cdr key-list))
956
(values xml-form nil nil))
957
;; dotted item at the end of KEY-LIST, search attribute list of target XML form
958
((atom (cdr key-list))
959
(if (eq '* (cdr key-list))
960
(values (xmlrep-attribs xml-form) nil nil)
961
(find (cdr key-list) (xmlrep-attribs xml-form)
962
:test (lambda (key item) (equal key (car item))))))
963
;; more tag names to match on KEY-LIST
965
(if (eq '* (cadr key-list))
966
(values (xmlrep-children xml-form) nil nil)
967
(let ((selected-xml-form (find (cadr key-list) (xmlrep-children xml-form)
969
(if selected-xml-form
970
(descend (cdr key-list) selected-xml-form)
971
;; no matching sub-form, indicate what part of KEY-LIST did not match
972
(values nil key-list xml-form))))))))
973
;; empty list, degenerate usage
974
(when (null key-list)
975
(error "KEY-LIST is empty."))
976
;; search down after initial match
977
(if (find-test (car key-list) xml)
978
(descend key-list xml)
979
(values nil nil nil))))
982
(defmethod deserialize ((self string) (fmt (eql :xml)) &key)
983
(declare (ignore fmt))
985
(defmethod deserialize ((self stream) (fmt (eql :xml)) &key)
986
(declare (ignore fmt))
988
(defmethod deserialize ((self pathname) (fmt (eql :xml)) &key)
989
(declare (ignore fmt))
990
(with-open-file (f self)
993
(defmethod serialize (self (fmt (eql :xml)) &key indent stream)
994
(write-xml self stream :indent indent))