;;; -*- Mode: lisp; Syntax: ansi-common-lisp; Base: 10; Package: xml-parser; -*- #| print methods for xml data models separate WRITE-NODE and ENCODE-NODE functions. the first acts as the interface and takes a stream arg. the second is internal and expects bindings for the writer function. corrected (encode-node symbol) to permit uninterned names -> no prefix; bind *document* in (write-node doc-node) for consistency parse/serialize. fixed spacing in attribute declarations
fixed encoding printing for doc-node
WITH-XML-WRITER macro encode-node for doc-node takes a default doctype name from the root element adjustments to encode-node for *-model for PCDATA
*xml-writer-node-cache* to support recursive DEF-NODE printing, which makes it possible to effect prefixes analogous to those used in the document entity.
changed respective WRITE-NODE and ENCODE-NODE to specialize on ABSTRACT-ELEM-NODE rather than on ELEM-NODE in order to support specialized instantiation. ENCODE-NODE now relies on the interface rather than the presence of slots. reified ENCODE-NEWLINE introduced uniform -interface specializers document, element, and property nodes modified ENCODE-NODE for name instances fix to encode node for default namespace names use ephemeral property to control printing *generated-ns-bindings*as a special binding with a single global handler writer-stream (ecode-node symbol) delegates keywords to the string method some CormanLisp elevated *xml-writer-node-cache* from encode-node (doc-node) to the with-xml-writer macro to correct isolated encoding of dtd nodes. serialization for qname-attr-node definitions and model components; recognizing the contingent prefix value as the default when generating namespace bindings bind*prefix-count* when encoding element definitions
|# (in-package "XML-PARSER") ;; ;; ;; interface functions #| fully macro'd version (defMacro with-xml-writer ((stream &optional (encoding :utf-8)) &rest body) "execute the body in a context appropriate to encode xml to the provided stream. this includes *default-namespaces* as the initial value for *namespace-bindings* and a null *xml-writer-node-cache*." `(multiple-value-bind (*writer-function *writer-arg) (encoding-stream-writer ,stream ,encoding) (let ((*namespace-bindings* *default-namespaces*) (*xml-writer-node-cache* nil)) (handler-bind ((|NSC: Namespace Declared| #'(lambda (condition &aux node) (with-slots (namespace prefix) condition (if prefix (handler-case (if (prefix-value prefix) (setf prefix (next-prefix))) (|NSC: Prefix Declared| (c) (declare (ignore c)))) (setf prefix (next-prefix))) (setf node (cons prefix namespace)) (push node *namespace-bindings*) (push node *generated-ns-bindings*) (use-value prefix))))) ,@body)))) |# (defMacro with-xml-writer ((stream &optional (encoding :utf-8)) &rest body) "execute the body in a context appropriate to encode xml to the provided stream. this includes *default-namespaces* as the initial value for *namespace-bindings* and a null *xml-writer-node-cache*." `(call-with-xml-writer #'(lambda () ,@body) ,stream :encoding ,encoding)) (defun call-with-xml-writer (function stream &key encoding ((:pretty *print-pretty*) *print-pretty*)) (multiple-value-bind (*writer-function *writer-arg) (encoding-stream-writer stream encoding) (let ((*namespace-bindings* *default-namespace-bindings*) (*xml-writer-node-cache* nil)) (handler-bind ((|NSC: Namespace Declared| #'(lambda (condition &aux node value) (with-slots (namespace name) condition (print (list namespace name *namespace-bindings*)) (if name (handler-case (if (and (setf value (prefix-value name)) (not (eq value *null-namespace*))) (setf name (next-prefix))) (|NSC: Prefix Declared| (c) (declare (ignore c)))) (setf name (next-prefix))) (setf node (cons (local-part name) namespace)) (push node *namespace-bindings*) (push node *generated-ns-bindings*) (use-value name))))) (funcall function))))) (defun encode-format (&rest args) (apply #'format *xml-writer-stream* args)) (defClass writer-stream (#+ALLEGRO excl::fundamental-binary-output-stream #+LispWorks stream:fundamental-stream #+MCL ccl::output-binary-stream #+CormanLisp stream) () (:default-initargs :direction :output #+CormanLisp :element-type #+CormanLisp 'unsigned-byte)) (defMethod stream-tyo ((stream writer-stream) char) (funcall *writer-function *writer-arg char)) (defMethod stream-element-type ((stream writer-stream)) 'character) (setq *xml-writer-stream* (make-instance 'writer-stream)) (defGeneric write-node (datum stream &key encoding) (:documentation "encode the node as xml to the provided stream.") (:method ((*document* doc-node) *output-destination* &key (encoding (encoding *document*))) (with-xml-writer (*output-destination* encoding) (encode-node *document*) *document*)) (:method ((node abstract-elem-node) *output-destination* &key (encoding (encoding node))) (with-xml-writer (*output-destination* encoding) (encode-node node) node))) #+CCL (progn (pushnew '(with-xml-writer . 1) *FRED-SPECIAL-INDENT-ALIST* :key #'first) nil) ;; ;; ;; primitives (defun encode-char (char) "encode the provided character to the current output destination." (funcall *writer-function *writer-arg char)) (defun encode-string (string) "encode the provided string to the output destination. used when it is known that no escapes are necessary." (dotimes (i (length string)) (funcall *writer-function *writer-arg (char string i)))) (defun encode-newline () "emit a newline to the output destination." (encode-char #.(code-char #x0a))) (defun encode-pretty-eol () (when *print-pretty* (encode-char #\newline) (dotimes (i *node-level*) (encode-char #\space)))) (defGeneric encode-node (node) (:documentation "encode the provided node to the current output destination. perform a tree walk for linked nodes. escape character data in content and attribute values as appropriate. introduce namespace bindings as required.")) (defMethod encode-node ((string string) &aux char) (dotimes (x (length string)) (setf char (char string x)) (case char (#\< (encode-string "<")) (#\> (encode-string ">")) (#\& ; here an attempt to both escape and not (if (position #\; string :start x) (funcall *writer-function *writer-arg char) (encode-string "&"))) (t (funcall *writer-function *writer-arg char))))) (defMethod encode-node ((node list)) (mapc #'encode-node node)) (defmethod encode-node ((node number)) (encode-node (write-to-string node))) ;; ;; ;; node methods (defMethod encode-node ((node symbol) &aux (name (local-part node)) (namespace (namespace node)) (prefix (prefix node))) (if (keywordp node) (encode-node (string node)) (cond ((string= (namespace-name namespace) *xmlns-prefix-namestring*) (encode-string *xmlns-prefix-namestring*) (unless (eq node *default-namespace-attribute-name*) (encode-char #\:) (encode-node name))) (namespace (setf prefix (local-part (namespace-prefix namespace prefix))) (unless (string= prefix "") (encode-node (local-part prefix)) (encode-char #\:)) (encode-node name)) (t ;; allow uninterned names (encode-node name))))) (defMethod encode-node ((node null)) ) (defMethod encode-node ((node abstract-name) &aux (name (local-part node)) (namespace (namespace node)) (prefix (prefix node))) (cond ((string= (namespace-name namespace) *xmlns-prefix-namestring*) (encode-string *xmlns-prefix-namestring*) (unless (eq node *default-namespace-attribute-name*) (encode-char #\:) (encode-node name))) (namespace (setf prefix (local-part (namespace-prefix namespace prefix))) (unless (string= prefix "") (encode-node (local-part prefix)) (encode-char #\:)) (encode-node name)) (t ;; allow uninterned names (encode-node name)))) (defMethod encode-node ((node doc-node-interface) &aux (*prefix-count* *prefix-count*) (*namespace-bindings* *namespace-bindings*) (name nil) (encoding (encoding node)) (standalone (standalone node)) (version (version node))) (encode-string "") (encode-char #\newline) (setf name (name (entity-info node))) (when (or (eq name *null-name*) (null name)) (setf name (name (root node)))) (encode-string ""))) (ecase *xml-writer-dtd-order* (:unspecified (maphash #'encode-def (types node))) (:content (when root-type (encode-def-with-cache nil root-type)) (maphash #'encode-def-with-cache (types node))) (:ordinality (map nil #'(lambda (def) (encode-def nil def)) (defs-by-ordinality (types node)))) (:alphabetical (map nil #'(lambda (def) (encode-def nil def)) (defs-by-name (types node))))))) (encode-newline) (encode-string " ]>") (dolist (child (children node)) (encode-newline) (encode-node child))) (defun encode-generated-ns-bindings (generated-ns-bindings) (dolist (binding generated-ns-bindings) (destructuring-bind (prefix . namespace) binding (encode-char #\space) (typecase prefix (string (encode-string "xmlns:") (encode-string prefix)) (t (encode-node prefix))) (encode-char #\=) (encode-char #\') (encode-node (namespace-name namespace)) (encode-char #\')))) (defMethod encode-node ((node elem-node-interface)) (let ((*namespace-bindings* *namespace-bindings*) (*node-level* (1+ *node-level*)) (*prefix-count* *prefix-count*) (*generated-ns-bindings* nil)) (declare (dynamic-extent *namespace-bindings*)) (with-accessors ((name name) (children children) (attributes attributes) (namespaces namespaces)) node (dolist (ns-node namespaces) (push ns-node *namespace-bindings*)) ; (break "namespaces: ~s/~s." *namespace-bindings* namespaces) (encode-char #\<) (encode-node name) (dolist (node namespaces) (encode-char #\space) (encode-node node)) (dolist (node attributes) (encode-char #\space) (encode-node node)) (when *generated-ns-bindings* (encode-generated-ns-bindings *generated-ns-bindings*)) (cond (children (encode-char #\>) (dolist (node children) (encode-pretty-eol) (encode-node node)) (encode-pretty-eol) (encode-string ")) (t (encode-string " />")))))) (defMethod encode-node ((node elem-property-node-interface)) (with-slots (name children value) node (encode-node name) (encode-char #\=) (encode-char #\') (cond (children (dolist (node children) (encode-node node))) (value (encode-node value))) (encode-char #\'))) (defMethod encode-node ((node abstract-ns-node)) (with-slots (name children value namespace) node (cond (namespace (encode-node name) (encode-char #\=) (encode-char #\') (encode-node (namespace-name namespace)) (encode-char #\')) (t (call-next-method))))) (defMethod encode-node ((node qname-attr-node)) (with-slots (name value children) node (cond (value (encode-node name) (encode-char #\=) (encode-char #\') (encode-node value) (encode-char #\')) (t (call-next-method))))) (defmethod encode-node ((node pi-node)) (encode-string "")) (defMethod encode-node ((node comment-node)) (encode-string "")) ;; ;; ;; declarations (defMethod encode-node ((node def-elem-type) &aux (*namespace-bindings* *namespace-bindings*) (*prefix-count* *prefix-count*) (*generated-ns-bindings* nil) c-def) (with-slots (name children properties) node ;; first do the definition itself, that is, the content model and the ;; attribute declarations. these all in the same namespace context (dolist (node properties) (when (is-ns-node (prototype node)) (push (prototype node) *namespace-bindings*))) (print *namespace-bindings*) ;; write the element declaration (encode-string " ") ;; write the attribute declarations (when properties (encode-newline) (encode-string " ")) (when *generated-ns-bindings* (encode-newline) (encode-string " ")) (when (eq *xml-writer-dtd-order* :content) ;; then do type definitions referenced by virtue of their presence in the ;; content model (dolist (c-name (collect-model-names (first (bnfp::bnf-rhs (model node))))) (cond ((eq c-name *empty-name*) nil) ((eq c-name *wild-name*) nil) ((setf c-def (find-def-elem-type c-name (document node))) (unless (find c-def *xml-writer-node-cache*) (push c-def *xml-writer-node-cache*) (encode-newline) (encode-node c-def))) (t (encode-string (format nil " ~%