root/trunk/bknr/datastore/src/xml-impex/xml-import.lisp

Revision 2045, 7.2 kB (checked in by hhubner, 2 years ago)

merge back from branches/xml-class-rework to trunk

  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1 (in-package :bknr.impex)
2
3 (defclass xml-class-importer ()
4   ((dtd        :initarg :dtd :initform nil :reader importer-dtd)
5    (class-hash :initarg :class-hash :accessor importer-class-hash)
6    (root-elt   :initform nil :accessor importer-root-elt)
7    (parent-elts :initform nil :accessor importer-parent-elts)))
8
9 (defmethod slot-parse-value ((slot xml-effective-slot-definition) value)
10   (with-slots (parser id-to-object) slot
11     (when parser
12       (setf value (funcall parser value)))
13     (when id-to-object
14       (setf value (funcall id-to-object value)))
15     value))
16
17 ;;; description for an object instance to be created from the xml. The
18 ;;; data is gathered while parsing the XML, and at the end of an
19 ;;; element, the corresponding object is instanciated.
20
21 (defclass xml-node ()
22   ((element :initarg :element :accessor node-element)
23    (children :initarg :children :initform (make-hash-table) :accessor node-children)
24    (elmdef    :initarg :elmdef :accessor instance-elmdef)
25    (attributes :initarg :attributes :accessor node-attributes)
26    (data :initarg :data :initform nil :accessor node-data)))
27
28 (defmethod print-object ((node xml-node) stream)
29   (print-unreadable-object (node stream :type t)
30     (format stream "~a" (node-element node))))
31
32 (defclass xml-class-instance (xml-node)
33   ((slots :initform (make-hash-table :test #'equal) :accessor instance-slots)
34    (class     :initarg :class :initform nil :accessor instance-class)))
35
36 (defmethod print-object ((instance xml-class-instance) stream)
37   (print-unreadable-object (instance stream :type t)
38     (format stream "~a" (instance-class instance))))
39
40 (defgeneric importer-add-attribute (handler node attr))
41 (defgeneric importer-add-characters (handler node data))
42 (defgeneric importer-add-element (handler node element value))
43 (defgeneric importer-finalize (handler node))
44
45 (defmethod importer-add-attribute ((handler xml-class-importer)
46                                  (class-instance xml-class-instance) attr)
47   (with-slots (class slots) class-instance
48     (let ((slot (xml-class-find-attribute-slot class (sax:attribute-qname attr))))
49       (when slot
50         (setf (gethash slot slots) (slot-parse-value slot (sax:attribute-value attr)))))))
51
52 (defmethod importer-add-attribute ((handler xml-class-importer)
53                                  (node xml-node) attr)
54   nil)
55
56 (defmethod importer-add-characters ((handler xml-class-importer)
57                                   (node xml-node) characters)
58   (unless (whitespace-p characters)
59     (setf characters (string-trim bknr.utils::+whitespace-chars+ characters))
60     (with-slots (data) node
61       (setf data (if data
62                      (concatenate 'string data characters)
63                      characters)))))
64
65 (defmethod importer-add-characters ((handler xml-class-importer)
66                                   (instance xml-class-instance) characters)
67   (with-slots (class elmdef slots children) instance
68     (let ((slot (xml-class-body-slot class)))
69       (when slot
70         (setf (gethash slot slots) (slot-parse-value slot characters))))))
71
72 (defmethod importer-add-element ((handler xml-class-importer)
73                                (node xml-node) element value)
74   (with-slots (children) node
75     (push value (gethash (make-keyword-from-string element) children))))
76
77 (defmethod importer-add-element ((handler xml-class-importer)
78                                (instance xml-class-instance) element value)
79   (with-slots (slots elmdef class children) instance
80     (let ((slot (xml-class-find-element-slot class element)))
81       (when slot
82           ;; parse the value if necessary
83           (setf value (slot-parse-value slot value))
84           (let ((containment (xml-effective-slot-definition-containment slot)))
85             (if (member containment '(:* :+))
86                 ;; if it has a plural containment, push the
87                 ;; created instance into the initargs hash
88                 (push value (gethash slot slots))
89                 ;; else set the initarg hash to the new instance
90                 (setf (gethash slot slots) value)))))))
91
92 (defmethod importer-finalize ((handler xml-class-importer)
93                             (node xml-node))
94   (with-slots (data children) node
95     (cond
96       ((and data
97             (= (hash-table-count children) 0)) data)
98       ((> (hash-table-count children) 0)
99        (children-to-initforms (node-children node)))
100       (t nil))))
101
102 (defun add-parent (handler parent child)
103   (let* ((class (class-of child))
104          (parent-slot (when (typep class 'xml-class)
105                         (xml-class-parent-slot class))))
106     (when parent-slot
107       (set-slot-value handler child (slot-definition-name parent-slot) parent))))
108
109 (defun slots-to-initforms (slots)
110   (let (initforms)
111     (loop for slot being the hash-keys of slots using (hash-value value)
112        when (listp value)
113        do (push (reverse value) initforms)
114        else do (push value initforms)
115        do (push (first (slot-definition-initargs slot)) initforms))
116     initforms))
117
118 (defmethod importer-finalize ((handler xml-class-importer)
119                             (instance xml-class-instance))
120   (with-slots (class elmdef children slots) instance
121     (let* ((initforms (slots-to-initforms slots))
122            (object (apply #'create-instance handler (class-name class) initforms)))
123
124       (loop for objs being the hash-values of slots
125          when (listp objs)
126          do (dolist (child objs)
127               (add-parent handler object child))
128          else do (add-parent handler object objs))
129
130       object)))
131
132 (defmethod sax:start-document ((handler xml-class-importer))
133   (setf (importer-root-elt handler) nil))
134
135 (defmethod sax:start-element ((handler xml-class-importer) namespace-uri local-name qname attrs)
136   (declare (ignore namespace-uri local-name))
137   (let ((class (gethash qname (importer-class-hash handler)))
138         (element (cxml::string-rod qname))
139         instance)
140     (if class
141         (setf instance
142               (make-instance 'xml-class-instance
143                              :element element
144                              :elmdef (xml-class-element class)
145                              :class class))
146         (setf instance
147               (make-instance 'xml-node
148                              :element element
149                              :elmdef (cxml::find-element element (importer-dtd handler)))))
150
151     (dolist (attr attrs)
152       (importer-add-attribute handler instance attr))
153    
154     (push instance (importer-parent-elts handler))))
155
156 (defmethod sax:characters ((handler xml-class-importer) data)
157   (unless (importer-parent-elts handler)
158     (error "Can not parse SAX:CHARACTERS without a parent element."))
159   (importer-add-characters handler (first (importer-parent-elts handler)) data))
160
161 (defmethod create-instance ((handler xml-class-importer) class-name &rest initargs)
162   (apply #'make-instance class-name initargs))
163
164 (defmethod set-slot-value ((handler xml-class-importer) object slot-name value)
165   (setf (slot-value object slot-name) value))
166
167 (defmethod sax:end-element ((handler xml-class-importer) namespace-uri local-name qname)
168   (declare (ignore namespace-uri local-name))
169
170   (let* ((instance (pop (importer-parent-elts handler)))
171          (final (importer-finalize handler instance))
172          (parent (first (importer-parent-elts handler))))
173
174     (when parent
175       (importer-add-element handler parent qname final))
176    
177     (setf (importer-root-elt handler) final)))
178
179 (defun parse-xml-file (xml-file classes &key (recoder #'cxml::rod-string)
180                        (importer-class 'xml-class-importer))
181   (let ((class-hash (make-hash-table :test #'equal)))
182     (dolist (class classes)
183       (setf (gethash (xml-class-element class) class-hash) class))
184     (let ((importer (make-instance importer-class
185                                    :class-hash class-hash)))
186       (cxml:parse-file xml-file (cxml:make-recoder importer recoder))
187       (importer-root-elt importer))))
Note: See TracBrowser for help on using the browser.