Author: hhubner Date: 2006-02-23 00:35:29 -0600 (Thu, 23 Feb 2006) New Revision: 1871
Modified: branches/xml-class-rework/bknr/src/packages.lisp branches/xml-class-rework/bknr/src/xml-impex/package.lisp branches/xml-class-rework/bknr/src/xml-impex/xml-class.lisp branches/xml-class-rework/bknr/src/xml-impex/xml-export.lisp Log: Another commit of the DTD-less xml-impex. The current version is able to export objects with references, objects which have a unique-id-slot specified in their classes are exported only once. The print-to-xml function has been converted to a generic function in order to be able to specialize it for arbitary objects. This makes it possible to serialize objects to XML which do not have xml-class as metaclass of their class. I'm not sure whether this is really a good idea or if it makes the whole thing less intuitive.
Next, I will check the XML specifications to see whether a standardized mechanism to serialize object references into XML exists.
Modified: branches/xml-class-rework/bknr/src/packages.lisp =================================================================== --- branches/xml-class-rework/bknr/src/packages.lisp 2006-02-23 06:29:32 UTC (rev 1870) +++ branches/xml-class-rework/bknr/src/packages.lisp 2006-02-23 06:35:29 UTC (rev 1871) @@ -128,9 +128,17 @@ #:user-preferences #:user-subscriptions
+ ;; Export slot names so that derived classes can overload + ;; slots (e.g. to add XML impex attributes) + #:login + #:flags + #:email #:full-name - #:email + #:last-login #:password + #:preferences + #:subscriptions + #:mail-error
#:find-user #:user-with-email @@ -271,6 +279,7 @@ #:website-menu #:website-url #:website-session-info + #:website-base-href #:host #:publish-site #:publish-handler
Modified: branches/xml-class-rework/bknr/src/xml-impex/package.lisp =================================================================== --- branches/xml-class-rework/bknr/src/xml-impex/package.lisp 2006-02-23 06:29:32 UTC (rev 1870) +++ branches/xml-class-rework/bknr/src/xml-impex/package.lisp 2006-02-23 06:35:29 UTC (rev 1871) @@ -20,5 +20,9 @@ #:write-to-xml #:xml-class-importer
+ #:with-xml-export + #:with-xml-export* + #:write-to-xml + #:create-instance #:set-slot-value))
Modified: branches/xml-class-rework/bknr/src/xml-impex/xml-class.lisp =================================================================== --- branches/xml-class-rework/bknr/src/xml-impex/xml-class.lisp 2006-02-23 06:29:32 UTC (rev 1870) +++ branches/xml-class-rework/bknr/src/xml-impex/xml-class.lisp 2006-02-23 06:35:29 UTC (rev 1871) @@ -20,26 +20,38 @@ (defmethod validate-superclass ((sub xml-class) (super indexed-class)) t)
+(defun princ-to-string-1 (object) + (when object + (princ-to-string object))) + (defclass xml-direct-slot-definition (bknr.indices::index-direct-slot-definition) - ((attribute :initarg :attribute - :initform nil - :documentation "Name of attribute to use to impex the slot.") - (element :initarg :element - :initform nil - :documentation "Name of the element to use to impex the slot.") - (body :initarg :body - :initform nil - :documentation "Whether the value of the slot has to be stored in the body of the class element.") + ((attribute :initarg :attribute + :initform nil + :documentation "Name of attribute to use to impex the slot.") + (element :initarg :element + :initform nil + :documentation "Name of the element to use to impex the slot.") + (body :initarg :body + :initform nil + :documentation "Whether the value of the slot has to be stored in the body of the class element.") (containment :initarg :containment :initform nil :documentation "Containment specification for this slot, either nil, :* or :+") - (parser :initarg :parser - :initform #'identity - :documentation "Function used to parse the slot value from the XML string.") - (serializer :initarg :serializer - :initform #'princ-to-string - :documentation "Function used to serialize the slot back to XML.") - + (parser :initarg :parser + :initform #'identity + :documentation "Function used to parse the slot value from the XML string.") + (serializer :initarg :serializer + :initform #'princ-to-string-1 + :documentation "Function used to serialize the slot back to XML.") + + (object-id-slot :initarg :object-id-slot + :initform nil + :documentation "If this slot is non-nil, the slot's +value is considered to be the unique object id of the object. During +export, objects which have an object-id-slot will only be serialized +once. Further occurances of the same object will be referenced +through the object-id-slot (either an element or an attribute)") + (id-to-object :initarg :id-to-object :initform nil :documentation "Function used to get the value pointed to by the ID.") @@ -47,9 +59,9 @@ :initform nil :documentation "Function used to get the ID of the object stored in the slot.")
- (parent :initarg :parent - :initform nil - :documentation "Slot is a pointer to the parent object."))) + (parent :initarg :parent + :initform nil + :documentation "Slot is a pointer to the parent object.")))
(defclass xml-effective-slot-definition (bknr.indices::index-effective-slot-definition) ((body :initform nil) @@ -58,6 +70,8 @@
(parser :initform nil :reader xml-effective-slot-definition-parser) (serializer :initform nil :reader xml-effective-slot-definition-serializer) + + (object-id-slot :initform nil :reader xml-effective-slot-definition-object-id-slot)
(id-to-object :initform nil) (object-to-id :initform nil) @@ -69,7 +83,7 @@ (defmethod print-object ((slot xml-effective-slot-definition) stream) (print-unreadable-object (slot stream :type t :identity t) (with-slots (attribute element body parent) slot - (format stream "~A (~A~@[~S~])" (slot-definition-name slot) + (format stream "~A (~A~@[ ~S~])" (slot-definition-name slot) (cond (attribute "ATTRIBUTE") (element "ELEMENT") (body "BODY") @@ -128,11 +142,8 @@ (unless (class-finalized-p class) (finalize-inheritance class))
- (let ((slots (class-slots class)) - (elmdef (xml-class-element class))) + (class-slots class))
- slots)) - (defmethod direct-slot-definition-class ((class xml-class) &key parent attribute element body &allow-other-keys) (if (or attribute element body parent) 'xml-direct-slot-definition @@ -142,13 +153,15 @@ (declare (ignore initargs)) 'xml-effective-slot-definition)
-(defmethod compute-effective-slot-definition :around - ((class xml-class) name direct-slots) +(defmethod compute-effective-slot-definition :around ((class xml-class) name direct-slots) (let* ((xml-directs (remove-if-not #'(lambda (class) (typep class 'xml-direct-slot-definition)) direct-slots)) (xml-direct (first xml-directs))) + (when (> (length xml-directs) 1) - (error "Can't overload slots with xml options.")) + (dolist (slot-def (class-slots (class-of (first xml-directs)))) + (unless (apply #'equal (mapcar #'(lambda (slot) (slot-value slot (slot-definition-name slot-def))) xml-directs)) + (warn "Possibly conflicting slot options for overloaded slot ~A." (slot-definition-name slot-def)))))
(let ((normal-slot (call-next-method))) (when (and xml-direct @@ -172,7 +185,7 @@ (setf (slot-value normal-slot slot) (slot-value xml-direct slot))))
- (dolist (slot '(parser serializer object-to-id id-to-object) normal-slot) + (dolist (slot '(parser serializer object-id-slot object-to-id id-to-object) normal-slot) (let ((value (slot-value normal-slot slot))) (when value (setf (slot-value normal-slot slot)
Modified: branches/xml-class-rework/bknr/src/xml-impex/xml-export.lisp =================================================================== --- branches/xml-class-rework/bknr/src/xml-impex/xml-export.lisp 2006-02-23 06:29:32 UTC (rev 1870) +++ branches/xml-class-rework/bknr/src/xml-impex/xml-export.lisp 2006-02-23 06:35:29 UTC (rev 1871) @@ -7,83 +7,106 @@ (when serializer (setf value (funcall serializer value)))))
-(defun write-to-xml (object &key sink name (string-rod-fn #'cxml::string-rod)) - (unless sink - (setf sink (if (boundp 'cxml::*sink*) - cxml::*sink* - (cxml:make-character-stream-sink *standard-output* - :indentation 3 :canonical nil)))) +(defvar *objects-written*)
- (cond ((listp object) - (unless name - (error "No element name was given~%")) - (sax:start-element sink nil nil (funcall string-rod-fn name) nil) - (dolist (obj object) - (write-to-xml obj :sink sink :string-rod-fn string-rod-fn)) - (sax:end-element sink nil nil (funcall string-rod-fn name))) +(defmacro with-xml-export* ((&key output indentation canonical) &body body) + `(let ((*objects-written* (make-hash-table :test #'equal)) + (cxml::*sink* (cxml:make-character-stream-sink ,output + :indentation ,indentation :canonical ,canonical))) + ,@body))
- ((typep (class-of object) 'xml-class) - (xml-object-check-validity object) - (let ((class (class-of object))) - (unless (typep class 'xml-class) - (error "~a is not of metaclass XML-CLASS." object)) - (unless (xml-class-element class) - (error "Class ~a has no element definition." class)) - - (let* ((attr-slots (xml-class-attribute-slots class)) - (elt-slots (xml-class-element-slots class)) - (body-slot (xml-class-body-slot class)) - (qname (cxml::string-rod (xml-class-element class))) - ;; attributes - (attributes (loop for slot in attr-slots - for name = (slot-definition-name slot) - for attdef = (cxml::string-rod (xml-effective-slot-definition-attribute slot)) - when (and (slot-boundp object name) - (slot-value object name)) - collect (sax:make-attribute - :qname attdef - :value - (funcall string-rod-fn - (slot-serialize-value slot (slot-value object name))))))) - (sax:start-element sink nil nil qname attributes) +(defmacro with-xml-export (nil &body body) + `(with-xml-export* (:output *standard-output* :indentation 1 :canonical nil) + ,@body))
- ;; elements - (dolist (slot elt-slots) - (let ((name (slot-definition-name slot)) - (elmdef-name (cxml::rod-string - (cxml::elmdef-name - (xml-effective-slot-definition-element slot)))) - (containment (xml-effective-slot-definition-containment slot))) - (when (slot-boundp object name) - (if (member containment '(:+ :*)) - (dolist (child (slot-value object name)) - (if (typep (class-of child) 'xml-class) - (write-to-xml child :sink sink :string-rod-fn string-rod-fn) - (write-to-xml (slot-serialize-value slot child) - :sink sink :name elmdef-name :string-rod-fn string-rod-fn))) - (let ((child (slot-value object name))) - (if (typep (class-of child) 'xml-class) - (write-to-xml child :sink sink :string-rod-fn string-rod-fn) - (write-to-xml (slot-serialize-value slot child) - :sink sink :name elmdef-name :string-rod-fn string-rod-fn))))))) +(defgeneric write-to-xml (object &key name no-recurse) + (:documentation "Write object to XML stream"))
- ;; body slot - (when body-slot - (let ((name (slot-definition-name body-slot))) - (when (slot-boundp object name) - (sax:characters - sink - (funcall string-rod-fn - (funcall (xml-effective-slot-definition-serializer body-slot) - (slot-value object name))))))) - - (sax:end-element sink nil nil qname)))) +(defmethod write-to-xml ((object (eql nil)) &key name no-recurse) + (declare (ignore name)))
- ((stringp object) - (unless name - (error "Can not serialize string ~A to XML without an element name." object)) - (sax:start-element sink nil nil (funcall string-rod-fn name) nil) - (sax:characters sink (funcall string-rod-fn object)) - (sax:end-element sink nil nil (funcall string-rod-fn name))) +(defmethod write-to-xml ((object list) &key (name (error "Can not serialize list to XML without an element name~%")) no-recurse) + (sax:start-element cxml::*sink* nil nil (cxml::string-rod name) nil) + (dolist (obj object) + (write-to-xml obj)) + (sax:end-element cxml::*sink* nil nil (cxml::string-rod name)))
- (t (error "Can not serialize unknown object ~A." object)))) +(defmethod write-to-xml ((object string) &key (name (error "Can not serialize string ~A to XML without an element name." object)) no-recurse) + (sax:start-element cxml::*sink* nil nil (cxml::string-rod name) nil) + (sax:characters cxml::*sink* (cxml::string-rod object)) + (sax:end-element cxml::*sink* nil nil (cxml::string-rod name))) + +(defun write-object-reference (class object unique-id-slot-name name) + (let ((slotdef (find unique-id-slot-name (class-slots class) :key #'slot-definition-name))) + (unless (xml-effective-slot-definition-attribute slotdef) + (error "Slot ~A is not defined as :attribute slot and cannot be used as unique-id slot for class ~A" unique-id-slot-name (class-name class))) + (sax:start-element cxml::*sink* nil nil name + (list (sax:make-attribute :qname (cxml::string-rod (xml-effective-slot-definition-attribute slotdef)) + :value (cxml::string-rod (slot-serialize-value slotdef (slot-value object unique-id-slot-name)))))) + (sax:end-element cxml::*sink* nil nil name))) + +(defmethod write-to-xml ((object t) &key name no-recurse) + (let ((class (class-of object))) + (cond + ((typep class 'xml-class) + (xml-object-check-validity object) + (let ((qname (cxml::string-rod (or name (xml-class-element class))))) + + ;; If this object has been serialized to the XML stream, + ;; write a reference to the object and return. + + (with-slots (unique-id-slot) class + (when unique-id-slot + (if (gethash (slot-value object (first unique-id-slot)) *objects-written*) + (progn + (write-object-reference class object (first unique-id-slot) qname) + (return-from write-to-xml)) + (setf (gethash (slot-value object (first unique-id-slot)) *objects-written*) t)))) + + ;; Object has not been written to the XML file or no + ;; unique-id-slot is defined for this class. + + (let* ((attr-slots (xml-class-attribute-slots class)) + (elt-slots (xml-class-element-slots class)) + (body-slot (xml-class-body-slot class)) + ;; attributes + (attributes (loop for slot in attr-slots + for name = (slot-definition-name slot) + for attdef = (cxml::string-rod (xml-effective-slot-definition-attribute slot)) + when (and (slot-boundp object name) + (slot-value object name)) + collect (sax:make-attribute + :qname attdef + :value + (cxml::string-rod + (slot-serialize-value slot (slot-value object name))))))) + (sax:start-element cxml::*sink* nil nil qname attributes) + + ;; elements + (dolist (slot elt-slots) + (let ((name (slot-definition-name slot)) + (element-name (xml-effective-slot-definition-element slot)) + (containment (xml-effective-slot-definition-containment slot))) + (when (slot-boundp object name) + (if (consp (slot-value object name)) + (dolist (child (slot-value object name)) + (if (typep (class-of child) 'xml-class) + (write-to-xml child) + (write-to-xml (slot-serialize-value slot child) :name element-name))) + (let ((child (slot-value object name))) + (if (typep (class-of child) 'xml-class) + (write-to-xml child) + (write-to-xml (slot-serialize-value slot child) :name element-name))))))) + + ;; body slot + (when body-slot + (let ((name (slot-definition-name body-slot))) + (when (slot-boundp object name) + (sax:characters + cxml::*sink* + (cxml::string-rod + (funcall (xml-effective-slot-definition-serializer body-slot) + (slot-value object name))))))) + + (sax:end-element cxml::*sink* nil nil qname)))) + (t nil)))))