| 1 |
(in-package :bknr.impex) |
|---|
| 2 |
|
|---|
| 3 |
(defclass xml-class (indexed-class) |
|---|
| 4 |
((element :initarg :element :initform nil :accessor xml-class-element) |
|---|
| 5 |
(unique-id-slot :initarg :unique-id-slot :initform nil |
|---|
| 6 |
:documentation "if set to a slot name, this |
|---|
| 7 |
signals that the slot can be used as a unique id to refer to an |
|---|
| 8 |
instance of the object in a n XML update operation") |
|---|
| 9 |
(unique-id-reader :initarg :unique-id-reader :initform nil |
|---|
| 10 |
:documentation "if set to a function, this |
|---|
| 11 |
signals that the function can be used as a unique index-reader when |
|---|
| 12 |
used in XML update operations."))) |
|---|
| 13 |
|
|---|
| 14 |
(defmethod xml-class-unique-id-slot ((class xml-class)) |
|---|
| 15 |
(first (slot-value class 'unique-id-slot))) |
|---|
| 16 |
|
|---|
| 17 |
(defmethod xml-class-unique-id-reader ((class xml-class)) |
|---|
| 18 |
(eval (first (slot-value class 'unique-id-reader)))) |
|---|
| 19 |
|
|---|
| 20 |
(defmethod validate-superclass ((sub xml-class) (super indexed-class)) |
|---|
| 21 |
t) |
|---|
| 22 |
|
|---|
| 23 |
(defun princ-to-string-1 (object) |
|---|
| 24 |
(when object |
|---|
| 25 |
(princ-to-string object))) |
|---|
| 26 |
|
|---|
| 27 |
(defclass xml-direct-slot-definition (bknr.indices::index-direct-slot-definition) |
|---|
| 28 |
((attribute :initarg :attribute |
|---|
| 29 |
:initform nil |
|---|
| 30 |
:documentation "Name of attribute to use to impex the slot.") |
|---|
| 31 |
(element :initarg :element |
|---|
| 32 |
:initform nil |
|---|
| 33 |
:documentation "Name of the element to use to impex the slot.") |
|---|
| 34 |
(body :initarg :body |
|---|
| 35 |
:initform nil |
|---|
| 36 |
:documentation "Whether the value of the slot has to be stored in the body of the class element.") |
|---|
| 37 |
(containment :initarg :containment |
|---|
| 38 |
:initform nil |
|---|
| 39 |
:documentation "Containment specification for this slot, either nil, :* or :+") |
|---|
| 40 |
(parser :initarg :parser |
|---|
| 41 |
:initform #'identity |
|---|
| 42 |
:documentation "Function used to parse the slot value from the XML string.") |
|---|
| 43 |
(serializer :initarg :serializer |
|---|
| 44 |
:initform #'princ-to-string-1 |
|---|
| 45 |
:documentation "Function used to serialize the slot back to XML.") |
|---|
| 46 |
|
|---|
| 47 |
(object-id-slot :initarg :object-id-slot |
|---|
| 48 |
:initform nil |
|---|
| 49 |
:documentation "If this slot is non-nil, the slot's |
|---|
| 50 |
value is considered to be the unique object id of the object. During |
|---|
| 51 |
export, objects which have an object-id-slot will only be serialized |
|---|
| 52 |
once. Further occurances of the same object will be referenced |
|---|
| 53 |
through the object-id-slot (either an element or an attribute)") |
|---|
| 54 |
|
|---|
| 55 |
(id-to-object :initarg :id-to-object |
|---|
| 56 |
:initform nil |
|---|
| 57 |
:documentation "Function used to get the value pointed to by the ID.") |
|---|
| 58 |
(object-to-id :initarg :object-to-id |
|---|
| 59 |
:initform nil |
|---|
| 60 |
:documentation "Function used to get the ID of the object stored in the slot.") |
|---|
| 61 |
|
|---|
| 62 |
(parent :initarg :parent |
|---|
| 63 |
:initform nil |
|---|
| 64 |
:documentation "Slot is a pointer to the parent object."))) |
|---|
| 65 |
|
|---|
| 66 |
(defclass xml-effective-slot-definition (bknr.indices::index-effective-slot-definition) |
|---|
| 67 |
((body :initform nil) |
|---|
| 68 |
(element :initform nil :reader xml-effective-slot-definition-element) |
|---|
| 69 |
(attribute :initform nil :reader xml-effective-slot-definition-attribute) |
|---|
| 70 |
|
|---|
| 71 |
(parser :initform nil :reader xml-effective-slot-definition-parser) |
|---|
| 72 |
(serializer :initform nil :reader xml-effective-slot-definition-serializer) |
|---|
| 73 |
|
|---|
| 74 |
(object-id-slot :initform nil :reader xml-effective-slot-definition-object-id-slot) |
|---|
| 75 |
|
|---|
| 76 |
(id-to-object :initform nil) |
|---|
| 77 |
(object-to-id :initform nil) |
|---|
| 78 |
(parent :initform nil) |
|---|
| 79 |
|
|---|
| 80 |
(containment :initform nil :reader xml-effective-slot-definition-containment) |
|---|
| 81 |
(required-p :initform nil :reader xml-effective-slot-definition-required-p))) |
|---|
| 82 |
|
|---|
| 83 |
(defmethod print-object ((slot xml-effective-slot-definition) stream) |
|---|
| 84 |
(print-unreadable-object (slot stream :type t :identity t) |
|---|
| 85 |
(with-slots (attribute element body parent) slot |
|---|
| 86 |
(format stream "~A (~A~@[ ~S~])" (slot-definition-name slot) |
|---|
| 87 |
(cond (attribute "ATTRIBUTE") |
|---|
| 88 |
(element "ELEMENT") |
|---|
| 89 |
(body "BODY") |
|---|
| 90 |
(parent "PARENT") |
|---|
| 91 |
(t "UNKNOWN")) |
|---|
| 92 |
(or attribute element))))) |
|---|
| 93 |
|
|---|
| 94 |
(defmethod xml-class-attribute-slots ((class xml-class)) |
|---|
| 95 |
(remove-if #'(lambda (slot) |
|---|
| 96 |
(or (not (typep slot 'xml-effective-slot-definition)) |
|---|
| 97 |
(not (slot-value slot 'attribute)))) (class-slots class))) |
|---|
| 98 |
|
|---|
| 99 |
(defmethod xml-class-element-slots ((class xml-class)) |
|---|
| 100 |
(remove-if #'(lambda (slot) |
|---|
| 101 |
(or (not (typep slot 'xml-effective-slot-definition)) |
|---|
| 102 |
(not (slot-value slot 'element)))) (class-slots class))) |
|---|
| 103 |
|
|---|
| 104 |
(defmethod xml-class-body-slot ((class xml-class)) |
|---|
| 105 |
(let ((body-slots |
|---|
| 106 |
(remove-if #'(lambda (slot) |
|---|
| 107 |
(or (not (typep slot 'xml-effective-slot-definition)) |
|---|
| 108 |
(not (slot-value slot 'body)))) (class-slots class)))) |
|---|
| 109 |
(when (> (length body-slots) 1) |
|---|
| 110 |
(error "Class ~A has more than one body slot: ~A." class body-slots)) |
|---|
| 111 |
(first body-slots))) |
|---|
| 112 |
|
|---|
| 113 |
(defmethod xml-class-find-attribute-slot ((class xml-class) attribute) |
|---|
| 114 |
(find attribute (xml-class-attribute-slots class) |
|---|
| 115 |
:test #'string-equal |
|---|
| 116 |
:key #'(lambda (slot) (slot-value slot 'attribute)))) |
|---|
| 117 |
|
|---|
| 118 |
(defmethod xml-class-find-element-slot ((class xml-class) element) |
|---|
| 119 |
(find element (xml-class-element-slots class) |
|---|
| 120 |
:test #'string-equal |
|---|
| 121 |
:key #'(lambda (slot) (slot-value slot 'element)))) |
|---|
| 122 |
|
|---|
| 123 |
(defmethod xml-class-parent-slot ((class xml-class)) |
|---|
| 124 |
(let ((parent-slots |
|---|
| 125 |
(remove-if #'(lambda (slot) |
|---|
| 126 |
(or (not (typep slot 'xml-effective-slot-definition)) |
|---|
| 127 |
(not (slot-value slot 'parent)))) |
|---|
| 128 |
(class-slots class)))) |
|---|
| 129 |
(when (> (length parent-slots) 1) |
|---|
| 130 |
(error "Class ~A has more than one parent slot: ~A." class parent-slots)) |
|---|
| 131 |
(first parent-slots))) |
|---|
| 132 |
|
|---|
| 133 |
(defmethod initialize-instance :after ((class xml-class) &key element &allow-other-keys) |
|---|
| 134 |
(setf (xml-class-element class) (or (first element) (string-downcase (class-name class)))) |
|---|
| 135 |
(xml-class-finalize class)) |
|---|
| 136 |
|
|---|
| 137 |
(defmethod reinitialize-instance :after ((class xml-class) &key element &allow-other-keys) |
|---|
| 138 |
(setf (xml-class-element class) (or (first element) (string-downcase (class-name class)))) |
|---|
| 139 |
(xml-class-finalize class)) |
|---|
| 140 |
|
|---|
| 141 |
(defmethod xml-class-finalize ((class xml-class)) |
|---|
| 142 |
(unless (class-finalized-p class) |
|---|
| 143 |
(finalize-inheritance class)) |
|---|
| 144 |
|
|---|
| 145 |
(class-slots class)) |
|---|
| 146 |
|
|---|
| 147 |
(defmethod direct-slot-definition-class ((class xml-class) &key parent attribute element body &allow-other-keys) |
|---|
| 148 |
(if (or attribute element body parent) |
|---|
| 149 |
'xml-direct-slot-definition |
|---|
| 150 |
(call-next-method))) |
|---|
| 151 |
|
|---|
| 152 |
(defmethod effective-slot-definition-class ((class xml-class) &rest initargs) |
|---|
| 153 |
(declare (ignore initargs)) |
|---|
| 154 |
'xml-effective-slot-definition) |
|---|
| 155 |
|
|---|
| 156 |
(defmethod compute-effective-slot-definition :around ((class xml-class) name direct-slots) |
|---|
| 157 |
(let* ((xml-directs (remove-if-not #'(lambda (class) (typep class 'xml-direct-slot-definition)) |
|---|
| 158 |
direct-slots)) |
|---|
| 159 |
(xml-direct (first xml-directs))) |
|---|
| 160 |
|
|---|
| 161 |
;; Commented out this check because I could not determine what it does and it warned me. |
|---|
| 162 |
#+(or) |
|---|
| 163 |
(when (> (length xml-directs) 1) |
|---|
| 164 |
(dolist (slot-def (class-slots (class-of (first xml-directs)))) |
|---|
| 165 |
(unless (apply #'equal (mapcar #'(lambda (slot) (slot-value slot (slot-definition-name slot-def))) xml-directs)) |
|---|
| 166 |
(warn "Possibly conflicting slot options for overloaded slot ~A." (slot-definition-name slot-def))))) |
|---|
| 167 |
|
|---|
| 168 |
(let ((normal-slot (call-next-method))) |
|---|
| 169 |
(when (and xml-direct |
|---|
| 170 |
(typep normal-slot 'xml-effective-slot-definition)) |
|---|
| 171 |
(with-slots (attribute element body parent) xml-direct |
|---|
| 172 |
(when (> (length (remove nil (list parent element attribute body))) 1) |
|---|
| 173 |
(error "Only one of ELEMENT, ATTRIBUTE, PARENT or BODY is possible for a slot definition.")) |
|---|
| 174 |
(unless (or body parent) |
|---|
| 175 |
(unless (or element attribute) |
|---|
| 176 |
(setf element (string-downcase name))) |
|---|
| 177 |
(when element |
|---|
| 178 |
(setf element (if (eq t element) (string-downcase name) element))) |
|---|
| 179 |
(when attribute |
|---|
| 180 |
(setf attribute (if (eq t attribute) (string-downcase name) attribute))) |
|---|
| 181 |
(unless (or element attribute) |
|---|
| 182 |
(error "Could not find element or attribute for slot ~A." name)))) |
|---|
| 183 |
|
|---|
| 184 |
;; copy direct-slot-definition slots to effective-slot-definition |
|---|
| 185 |
(dolist (slot '(parser serializer body id-to-object object-to-id |
|---|
| 186 |
parent attribute element containment)) |
|---|
| 187 |
(setf (slot-value normal-slot slot) |
|---|
| 188 |
(slot-value xml-direct slot)))) |
|---|
| 189 |
|
|---|
| 190 |
(dolist (slot '(parser serializer object-id-slot object-to-id id-to-object) normal-slot) |
|---|
| 191 |
(let ((value (slot-value normal-slot slot))) |
|---|
| 192 |
(when value |
|---|
| 193 |
(setf (slot-value normal-slot slot) |
|---|
| 194 |
(eval value))))) |
|---|
| 195 |
|
|---|
| 196 |
normal-slot))) |
|---|
| 197 |
|
|---|
| 198 |
(defmethod xml-object-check-validity (object) |
|---|
| 199 |
(let ((class (class-of object))) |
|---|
| 200 |
(unless (typep class 'xml-class) |
|---|
| 201 |
(error "Object ~a is not of metaclass XML-CLASS." object)) |
|---|
| 202 |
(dolist (slot (class-slots class)) |
|---|
| 203 |
(when (typep slot 'xml-effective-slot-definition) |
|---|
| 204 |
(when (and (xml-effective-slot-definition-required-p slot) |
|---|
| 205 |
(not (slot-boundp object (slot-definition-name slot)))) |
|---|
| 206 |
(error "Required slot ~A is not bound in ~a." |
|---|
| 207 |
(slot-definition-name slot) object)) |
|---|
| 208 |
(let ((containment (xml-effective-slot-definition-containment slot))) |
|---|
| 209 |
(when (and containment |
|---|
| 210 |
(eql containment :+) |
|---|
| 211 |
(null (slot-value object (slot-definition-name slot)))) |
|---|
| 212 |
(error "Slot ~a with containment :+ has no value." |
|---|
| 213 |
(slot-definition-name slot)))))))) |
|---|