| 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)))) |
|---|