;;;; dom-builder.lisp -- DOM-building SAX handler ;;;; ;;;; This file is part of the CXML parser, released under Lisp-LGPL. ;;;; See file COPYING for details. ;;;; ;;;; Author: Gilbert Baumann ;;;; Author: Henrik Motakef ;;;; Author: David Lichteblau ;;;; Author: knowledgeTools Int. GmbH #-cxml-system::utf8dom-file (in-package :rune-dom) #+cxml-system::utf8dom-file (in-package :utf8-dom) (defclass dom-builder (sax:content-handler) ((document :initform nil :accessor document) (element-stack :initform '() :accessor element-stack) (internal-subset :accessor internal-subset) (text-buffer :initform nil :accessor text-buffer))) #+(and rune-is-integer (not cxml-system::utf8dom-file)) (defmethod hax:%want-strings-p ((handler dom-builder)) nil) (defun make-dom-builder () (make-instance 'dom-builder)) (defun fast-push (new-element vector) (vector-push-extend new-element vector (max 1 (array-dimension vector 0)))) (defmethod sax:start-document ((handler dom-builder)) (when (and sax:*namespace-processing* (not (and sax:*include-xmlns-attributes* sax:*use-xmlns-namespace*))) (error "SAX configuration is incompatible with DOM: *namespace-processing* is activated, but *include-xmlns-attributes* or *use-xmlns-namespace* are not")) (let ((document (make-instance 'document))) (setf (slot-value document 'owner) nil (slot-value document 'doc-type) nil) (setf (document handler) document) (push document (element-stack handler)))) ;; fixme (defmethod sax::dtd ((handler dom-builder) dtd) (setf (slot-value (document handler) 'dtd) dtd)) (defmethod sax:end-document ((handler dom-builder)) (let ((doctype (dom:doctype (document handler)))) (when doctype (setf (slot-value (dom:entities doctype) 'read-only-p) t) (setf (slot-value (dom:notations doctype) 'read-only-p) t))) (document handler)) (defmethod sax:entity-resolver ((handler dom-builder) resolver) (setf (slot-value (document handler) 'entity-resolver) resolver)) (defmethod sax:start-dtd ((handler dom-builder) name publicid systemid) (let* ((document (document handler)) (doctype (%create-document-type name publicid systemid))) (setf (slot-value doctype 'owner) document (slot-value (dom:notations doctype) 'owner) document (slot-value (dom:entities doctype) 'owner) document (slot-value document 'doc-type) doctype))) (defmethod sax:start-internal-subset ((handler dom-builder)) (setf (internal-subset handler) nil)) (defmethod sax:end-internal-subset ((handler dom-builder)) (setf (dom::%internal-subset (slot-value (document handler) 'doc-type)) (nreverse (internal-subset handler))) (slot-makunbound handler 'internal-subset)) (macrolet ((defhandler (name &rest args) `(defmethod ,name ((handler dom-builder) ,@args) (when (slot-boundp handler 'internal-subset) (push (list ',name ,@args) (internal-subset handler)))))) (defhandler sax:unparsed-entity-declaration name public-id system-id notation-name) (defhandler sax:external-entity-declaration kind name public-id system-id) (defhandler sax:internal-entity-declaration kind name value) (defhandler sax:notation-declaration name public-id system-id) (defhandler sax:element-declaration name model) (defhandler sax:attribute-declaration element-name attribute-name type default)) (defmethod sax:start-element ((handler dom-builder) namespace-uri local-name qname attributes) (check-type qname rod) ;catch recoder/builder mismatch (flush-characters handler) (with-slots (document element-stack) handler (let* ((nsp sax:*namespace-processing*) (element (make-instance 'element :tag-name qname :owner document :namespace-uri (when nsp namespace-uri) :local-name (when nsp local-name) :prefix (%rod (when nsp (cxml::split-qname (real-rod qname)))))) (parent (car element-stack)) (anodes '())) (dolist (attr attributes) (let ((anode (if nsp (dom:create-attribute-ns document (sax:attribute-namespace-uri attr) (sax:attribute-qname attr)) (dom:create-attribute document (sax:attribute-qname attr)))) (text (dom:create-text-node document (sax:attribute-value attr)))) (setf (slot-value anode 'specified-p) (sax:attribute-specified-p attr)) (setf (slot-value anode 'owner-element) element) (dom:append-child anode text) (push anode anodes))) (setf (slot-value element 'parent) parent) (fast-push element (slot-value parent 'children)) (let ((map (make-instance 'attribute-node-map :items anodes :element-type :attribute :element element :owner document))) (setf (slot-value element 'attributes) map) (dolist (anode anodes) (setf (slot-value anode 'map) map))) (push element element-stack)))) (defmethod sax:end-element ((handler dom-builder) namespace-uri local-name qname) (declare (ignore namespace-uri local-name qname)) (flush-characters handler) (pop (element-stack handler))) (defmethod sax:characters ((handler dom-builder) data) (with-slots (text-buffer) handler (cond ((null text-buffer) (setf text-buffer data)) (t (unless (array-has-fill-pointer-p text-buffer) (setf text-buffer (make-array (length text-buffer) :element-type 'rune :adjustable t :fill-pointer t :initial-contents text-buffer))) (let ((n (length text-buffer)) (m (length data))) (adjust-vector-exponentially text-buffer (+ n m) t) (move data text-buffer 0 n m)))))) (defun flush-characters (handler) (with-slots (document element-stack text-buffer) handler (let ((data text-buffer)) (when data (when (array-has-fill-pointer-p data) (setf data (make-array (length data) :element-type 'rune :initial-contents data))) (let ((parent (car element-stack))) (if (eq (dom:node-type parent) :cdata-section) (setf (dom:data parent) data) (let ((node (dom:create-text-node document data))) (setf (slot-value node 'parent) parent) (fast-push node (slot-value (car element-stack) 'children))))) (setf text-buffer nil))))) (defmethod sax:start-cdata ((handler dom-builder)) (flush-characters handler) (with-slots (document element-stack) handler (let ((node (dom:create-cdata-section document #"")) (parent (car element-stack))) (setf (slot-value node 'parent) parent) (fast-push node (slot-value parent 'children)) (push node element-stack)))) (defmethod sax:end-cdata ((handler dom-builder)) (flush-characters handler) (let ((node (pop (slot-value handler 'element-stack)))) (assert (eq (dom:node-type node) :cdata-section)))) (defmethod sax:processing-instruction ((handler dom-builder) target data) (flush-characters handler) (with-slots (document element-stack) handler (let ((node (dom:create-processing-instruction document target data)) (parent (car element-stack))) (setf (slot-value node 'parent) parent) (fast-push node (slot-value (car element-stack) 'children))))) (defmethod sax:comment ((handler dom-builder) data) (flush-characters handler) (with-slots (document element-stack) handler (let ((node (dom:create-comment document data)) (parent (car element-stack))) (setf (slot-value node 'parent) parent) (fast-push node (slot-value (car element-stack) 'children))))) (defmethod sax:unparsed-entity-declaration ((handler dom-builder) name public-id system-id notation-name) (set-entity handler name public-id system-id notation-name)) (defmethod sax:external-entity-declaration ((handler dom-builder) kind name public-id system-id) (ecase kind (:general (set-entity handler name public-id system-id nil)) (:parameter))) (defmethod sax:internal-entity-declaration ((handler dom-builder) kind name value) (declare (ignore value)) (ecase kind (:general (set-entity handler name nil nil nil)) (:parameter))) (defun set-entity (handler name pid sid notation) (dom:set-named-item (dom:entities (dom:doctype (document handler))) (make-instance 'entity :owner (document handler) :name name :public-id pid :system-id sid :notation-name notation))) (defmethod sax:notation-declaration ((handler dom-builder) name public-id system-id) (dom:set-named-item (dom:notations (dom:doctype (document handler))) (make-instance 'notation :owner (document handler) :name name :public-id public-id :system-id system-id)))