* dom/dom-builder.lisp (DOM-BUILDER): New slot `text-buffer'. (SAX:START-ELEMENT, SAX:END-ELEMENT, SAX:START-CDATA, SAX:END-CDATA, SAX:PROCESSING-INSTRUCTION, SAX:COMMENT): Call flush-characters. (SAX:CHARACTERS): Rewritten. (FLUSH-CHARACTERS): New, based on the old sax:characters. * dom/dom-impl.lisp ((initialize-instance :after entity-reference)): Call flush-characters.
1480 lines
51 KiB
Common Lisp
1480 lines
51 KiB
Common Lisp
;;;; dom-impl.lisp -- Implementation of DOM 1 Core -*- package: rune-dom -*-
|
|
;;;;
|
|
;;;; This file is part of the CXML parser, released under Lisp-LGPL.
|
|
;;;; See file COPYING for details.
|
|
;;;;
|
|
;;;; Author: Gilbert Baumann <unk6@rz.uni-karlsruhe.de>
|
|
;;;; Author: David Lichteblau <david@lichteblau.com>
|
|
;;;; Author: knowledgeTools Int. GmbH
|
|
|
|
#-cxml-system::utf8dom-file
|
|
(defpackage :rune-dom
|
|
(:use :cl :runes)
|
|
#+rune-is-character (:nicknames :cxml-dom)
|
|
(:export #:implementation #:make-dom-builder #:create-document))
|
|
|
|
#+cxml-system::utf8dom-file
|
|
(defpackage :utf8-dom
|
|
(:use :cl :utf8-runes)
|
|
(:nicknames :cxml-dom)
|
|
(:export #:implementation #:make-dom-builder #:create-document))
|
|
|
|
#-cxml-system::utf8dom-file
|
|
(in-package :rune-dom)
|
|
|
|
#+cxml-system::utf8dom-file
|
|
(in-package :utf8-dom)
|
|
|
|
|
|
;; Classes
|
|
|
|
(define-condition dom-exception (error)
|
|
((key :initarg :key :reader dom-exception-key)
|
|
(string :initarg :string :reader dom-exception-string)
|
|
(arguments :initarg :arguments :reader dom-exception-arguments))
|
|
(:report
|
|
(lambda (c s)
|
|
(format s "~A (~D):~%~?"
|
|
(dom-exception-key c)
|
|
(dom:code c)
|
|
(dom-exception-string c)
|
|
(dom-exception-arguments c)))))
|
|
|
|
(defclass node (dom:node)
|
|
((parent :initarg :parent :initform nil)
|
|
(children :initarg :children :initform (make-node-list))
|
|
(owner :initarg :owner :initform nil)
|
|
(read-only-p :initform nil :reader read-only-p)
|
|
(map :initform nil)))
|
|
|
|
(defmethod dom:prefix ((node node)) nil)
|
|
(defmethod dom:local-name ((node node)) nil)
|
|
(defmethod dom:namespace-uri ((node node)) nil)
|
|
|
|
(defclass namespace-mixin ()
|
|
((prefix :initarg :prefix :reader dom:prefix)
|
|
(local-name :initarg :local-name :reader dom:local-name)
|
|
(namespace-uri :initarg :namespace-uri :reader dom:namespace-uri)))
|
|
|
|
(defmethod (setf dom:prefix) (newval (node namespace-mixin))
|
|
(assert-writeable node)
|
|
(when newval
|
|
(safe-split-qname (concatenate 'rod newval #":foo")
|
|
(dom:namespace-uri node)))
|
|
(setf (slot-value node 'prefix) newval))
|
|
|
|
(defclass document (node dom:document)
|
|
((doc-type :initarg :doc-type :reader dom:doctype)
|
|
(dtd :initform nil :reader dtd)
|
|
(entity-resolver :initform nil)))
|
|
|
|
(defclass document-fragment (node dom:document-fragment)
|
|
())
|
|
|
|
(defclass character-data (node dom:character-data)
|
|
((value :initarg :data :reader dom:data)))
|
|
|
|
(defclass attribute (namespace-mixin node dom:attr)
|
|
((name :initarg :name :reader dom:name)
|
|
(owner-element :initarg :owner-element :reader dom:owner-element)
|
|
(specified-p :initarg :specified-p :reader dom:specified)))
|
|
|
|
(defmethod (setf dom:prefix) :before (newval (node attribute))
|
|
(when (rod= (dom:node-name node) #"xmlns")
|
|
(dom-error :NAMESPACE_ERR "must not change xmlns attribute prefix")))
|
|
|
|
(defmethod (setf dom:prefix) :after (newval (node attribute))
|
|
(setf (slot-value node 'name)
|
|
(concatenate 'rod newval #":" (dom:local-name node))))
|
|
|
|
(defmethod print-object ((object attribute) stream)
|
|
(print-unreadable-object (object stream :type t :identity t)
|
|
(format stream "~A=~S"
|
|
(rod-string (dom:name object))
|
|
(rod-string (dom:value object)))))
|
|
|
|
(defclass element (namespace-mixin node dom:element)
|
|
((tag-name :initarg :tag-name :reader dom:tag-name)
|
|
(attributes :initarg :attributes :reader dom:attributes)))
|
|
|
|
(defmethod (setf dom:prefix) :after (newval (node element))
|
|
(setf (slot-value node 'tag-name)
|
|
(concatenate 'rod newval #":" (dom:local-name node))))
|
|
|
|
(defmethod print-object ((object element) stream)
|
|
(print-unreadable-object (object stream :type t :identity t)
|
|
(princ (rod-string (dom:tag-name object)) stream)))
|
|
|
|
(defclass text (character-data dom:text)
|
|
())
|
|
|
|
(defclass comment (character-data dom:comment)
|
|
())
|
|
|
|
(defclass cdata-section (text dom:cdata-section)
|
|
())
|
|
|
|
(defclass document-type (node dom:document-type)
|
|
((name :initarg :name :reader dom:name)
|
|
(public-id :initarg :public-id :reader dom:public-id)
|
|
(system-id :initarg :system-id :reader dom:system-id)
|
|
(entities :initarg :entities :reader dom:entities)
|
|
(notations :initarg :notations :reader dom:notations)
|
|
(dom::%internal-subset :accessor dom::%internal-subset)))
|
|
|
|
(defclass notation (node dom:notation)
|
|
((name :initarg :name :reader dom:name)
|
|
(public-id :initarg :public-id :reader dom:public-id)
|
|
(system-id :initarg :system-id :reader dom:system-id)))
|
|
|
|
(defclass entity (node dom:entity)
|
|
((name :initarg :name :reader dom:name)
|
|
(public-id :initarg :public-id :reader dom:public-id)
|
|
(system-id :initarg :system-id :reader dom:system-id)
|
|
(notation-name :initarg :notation-name :reader dom:notation-name)))
|
|
|
|
(defclass entity-reference (node dom:entity-reference)
|
|
((name :initarg :name :reader dom:name)))
|
|
|
|
(defclass processing-instruction (node dom:processing-instruction)
|
|
((target :initarg :target :reader dom:target)
|
|
(data :initarg :data :reader dom:data)))
|
|
|
|
(defclass named-node-map (dom:named-node-map)
|
|
((items :initarg :items :reader dom:items
|
|
:initform nil)
|
|
(owner :initarg :owner :reader dom:owner-document)
|
|
(read-only-p :initform nil :reader read-only-p)
|
|
(element-type :initarg :element-type)))
|
|
|
|
(defclass attribute-node-map (named-node-map)
|
|
((element :initarg :element)))
|
|
|
|
|
|
;;; Implementation
|
|
|
|
(defun %rod (x)
|
|
(etypecase x
|
|
(null x)
|
|
(rod x)
|
|
#+cxml-system::utf8dom-file (runes::rod (cxml::rod-to-utf8-string x))
|
|
(string (string-rod x))
|
|
(vector x)))
|
|
|
|
#-cxml-system::utf8dom-file
|
|
(defun real-rod (x)
|
|
(%rod x))
|
|
|
|
#+cxml-system::utf8dom-file
|
|
(defun real-rod (x)
|
|
(etypecase x
|
|
(null x)
|
|
(runes::rod x)
|
|
(string (cxml::utf8-string-to-rod x))))
|
|
|
|
(defun valid-name-p (x)
|
|
(cxml::valid-name-p (real-rod x)))
|
|
|
|
(defun assert-writeable (node)
|
|
(when (read-only-p node)
|
|
(dom-error :NO_MODIFICATION_ALLOWED_ERR "~S is marked read-only." node)))
|
|
|
|
(defun dom:map-node-list (fn nodelist)
|
|
(dotimes (i (dom:length nodelist))
|
|
(funcall fn (dom:item nodelist i))))
|
|
|
|
(defmacro dom:do-node-list ((var nodelist &optional resultform) &body body)
|
|
`(block nil
|
|
(dom:map-node-list (lambda (,var) ,@body) ,nodelist)
|
|
,resultform))
|
|
|
|
(defun dom:map-node-map (fn node-map)
|
|
(with-slots (items) node-map
|
|
(mapc fn items)))
|
|
|
|
(defmacro dom:do-node-map ((var node-map &optional resultform) &body body)
|
|
`(block nil
|
|
(dom:map-node-map (lambda (,var) ,@body) ,node-map)
|
|
,resultform))
|
|
|
|
(defmacro dovector ((var vector &optional resultform) &body body)
|
|
`(loop
|
|
for ,var across ,vector do (progn ,@body)
|
|
,@(when resultform `(finally (return ,resultform)))))
|
|
|
|
(defun move (from to from-start to-start length)
|
|
;; like (setf (subseq to to-start (+ to-start length))
|
|
;; (subseq from from-start (+ from-start length)))
|
|
;; but without creating the garbage.
|
|
;; Also, this is using AREF not ELT so that fill-pointers are ignored.
|
|
(if (< to-start from-start)
|
|
(loop
|
|
repeat length
|
|
for i from from-start
|
|
for j from to-start
|
|
do (setf (aref to j) (aref from i)))
|
|
(loop
|
|
repeat length
|
|
for i downfrom (+ from-start length -1)
|
|
for j downfrom (+ to-start length -1)
|
|
do (setf (aref to j) (aref from i)))))
|
|
|
|
(defun adjust-vector-exponentially (vector new-dimension set-fill-pointer-p)
|
|
(let ((d (array-dimension vector 0)))
|
|
(when (< d new-dimension)
|
|
(loop
|
|
do (setf d (* 2 d))
|
|
while (< d new-dimension))
|
|
(adjust-array vector d))
|
|
(when set-fill-pointer-p
|
|
(setf (fill-pointer vector) new-dimension))))
|
|
|
|
(defun make-space (vector &optional (n 1))
|
|
(adjust-vector-exponentially vector (+ (length vector) n) nil))
|
|
|
|
(defun extension (vector)
|
|
(max (array-dimension vector 0) 1))
|
|
|
|
;; dom-exception
|
|
|
|
(defun dom-error (key fmt &rest args)
|
|
(error 'dom-exception :key key :string fmt :arguments args))
|
|
|
|
(defmethod dom:code ((self dom-exception))
|
|
(ecase (dom-exception-key self)
|
|
(:INDEX_SIZE_ERR 1)
|
|
(:DOMSTRING_SIZE_ERR 2)
|
|
(:HIERARCHY_REQUEST_ERR 3)
|
|
(:WRONG_DOCUMENT_ERR 4)
|
|
(:INVALID_CHARACTER_ERR 5)
|
|
(:NO_DATA_ALLOWED_ERR 6)
|
|
(:NO_MODIFICATION_ALLOWED_ERR 7)
|
|
(:NOT_FOUND_ERR 8)
|
|
(:NOT_SUPPORTED_ERR 9)
|
|
(:INUSE_ATTRIBUTE_ERR 10)
|
|
(:INVALID_STATE_ERR 11)
|
|
(:SYNTAX_ERR 12)
|
|
(:INVALID_MODIFICATION_ERR 13)
|
|
(:NAMESPACE_ERR 14)
|
|
(:INVALID_ACCESS_ERR 15)))
|
|
|
|
;; dom-implementation protocol
|
|
|
|
(defmethod dom:has-feature ((factory (eql 'implementation)) feature version)
|
|
(and (or (string-equal (rod-string feature) "xml")
|
|
(string-equal (rod-string feature) "core"))
|
|
(or (zerop (length version))
|
|
(string-equal (rod-string version) "1.0")
|
|
(string-equal (rod-string version) "2.0"))))
|
|
|
|
(defun %create-document-type (name publicid systemid)
|
|
(make-instance 'document-type
|
|
:name name
|
|
:notations (make-instance 'named-node-map
|
|
:element-type :notation
|
|
:owner nil)
|
|
:entities (make-instance 'named-node-map
|
|
:element-type :entity
|
|
:owner nil)
|
|
:public-id publicid
|
|
:system-id systemid))
|
|
|
|
(defmethod dom:create-document-type
|
|
((factory (eql 'implementation)) name publicid systemid)
|
|
(safe-split-qname name #"")
|
|
(let ((result (%create-document-type name publicid systemid)))
|
|
(setf (slot-value (dom:entities result) 'read-only-p) t)
|
|
(setf (slot-value (dom:notations result) 'read-only-p) t)
|
|
result))
|
|
|
|
(defmethod dom:create-document
|
|
((factory (eql 'implementation)) uri qname doctype)
|
|
(let ((document (make-instance 'document)))
|
|
(setf (slot-value document 'owner) nil
|
|
(slot-value document 'doc-type) doctype)
|
|
(when doctype
|
|
(unless (typep doctype 'document-type)
|
|
(dom-error :WRONG_DOCUMENT_ERR
|
|
"doctype was created by a different dom implementation"))
|
|
(when (dom:owner-document doctype)
|
|
(dom-error :WRONG_DOCUMENT_ERR "doctype already in use"))
|
|
(setf (slot-value doctype 'owner) document
|
|
(slot-value (dom:notations doctype) 'owner) document
|
|
(slot-value (dom:entities doctype) 'owner) document))
|
|
(when (or uri qname)
|
|
(dom:append-child document (dom:create-element-ns document uri qname)))
|
|
document))
|
|
|
|
;; document-fragment protocol
|
|
;; document protocol
|
|
|
|
(defmethod dom:implementation ((document document))
|
|
'implementation)
|
|
|
|
(defmethod dom:document-element ((document document))
|
|
(dovector (k (dom:child-nodes document))
|
|
(cond ((typep k 'element)
|
|
(return k)))))
|
|
|
|
(defmethod dom:create-element ((document document) tag-name)
|
|
(setf tag-name (%rod tag-name))
|
|
(unless (valid-name-p tag-name)
|
|
(dom-error :INVALID_CHARACTER_ERR "not a name: ~A" (rod-string tag-name)))
|
|
(let ((result (make-instance 'element
|
|
:tag-name tag-name
|
|
:namespace-uri nil
|
|
:local-name nil
|
|
:prefix nil
|
|
:owner document)))
|
|
(setf (slot-value result 'attributes)
|
|
(make-instance 'attribute-node-map
|
|
:element-type :attribute
|
|
:owner document
|
|
:element result))
|
|
(add-default-attributes result)
|
|
result))
|
|
|
|
(defun safe-split-qname (qname uri)
|
|
(unless (valid-name-p qname)
|
|
(dom-error :INVALID_CHARACTER_ERR "not a name: ~A" (rod-string qname)))
|
|
(multiple-value-bind (prefix local-name)
|
|
(handler-case
|
|
(cxml::split-qname (real-rod qname))
|
|
(cxml:well-formedness-violation (c)
|
|
(dom-error :NAMESPACE_ERR "~A" c)))
|
|
(setf local-name (%rod local-name))
|
|
(when prefix
|
|
(setf prefix (%rod prefix))
|
|
(unless uri
|
|
(dom-error :NAMESPACE_ERR "prefix specified but no namespace URI"))
|
|
(when (and (rod= prefix #"xml")
|
|
(not (rod= uri #"http://www.w3.org/XML/1998/namespace")))
|
|
(dom-error :NAMESPACE_ERR "invalid uri for prefix `xml'"))
|
|
(when (and (rod= prefix #"xmlns")
|
|
(not (rod= uri #"http://www.w3.org/2000/xmlns/")))
|
|
(dom-error :NAMESPACE_ERR "invalid uri for prefix `xmlns'")))
|
|
(values prefix local-name)))
|
|
|
|
(defmethod dom:create-element-ns ((document document) uri qname)
|
|
(setf qname (%rod qname))
|
|
(multiple-value-bind (prefix local-name)
|
|
(safe-split-qname qname uri)
|
|
(let ((result (make-instance 'element
|
|
:tag-name qname
|
|
:namespace-uri uri
|
|
:local-name local-name
|
|
:prefix prefix
|
|
:owner document)))
|
|
(setf (slot-value result 'attributes)
|
|
(make-instance 'attribute-node-map
|
|
:element-type :attribute
|
|
:owner document
|
|
:element result))
|
|
(add-default-attributes result)
|
|
result)))
|
|
|
|
(defmethod dom:create-document-fragment ((document document))
|
|
(make-instance 'document-fragment
|
|
:owner document))
|
|
|
|
(defmethod dom:create-text-node ((document document) data)
|
|
(setf data (%rod data))
|
|
(make-instance 'text
|
|
:data data
|
|
:owner document))
|
|
|
|
(defmethod dom:create-comment ((document document) data)
|
|
(setf data (%rod data))
|
|
(make-instance 'comment
|
|
:data data
|
|
:owner document))
|
|
|
|
(defmethod dom:create-cdata-section ((document document) data)
|
|
(setf data (%rod data))
|
|
(make-instance 'cdata-section
|
|
:data data
|
|
:owner document))
|
|
|
|
(defmethod dom:create-processing-instruction ((document document) target data)
|
|
(setf target (%rod target))
|
|
(setf data (%rod data))
|
|
(unless (valid-name-p target)
|
|
(dom-error :INVALID_CHARACTER_ERR "not a name: ~A" (rod-string target)))
|
|
(make-instance 'processing-instruction
|
|
:owner document
|
|
:target target
|
|
:data data))
|
|
|
|
(defmethod dom:create-attribute ((document document) name)
|
|
(setf name (%rod name))
|
|
(unless (valid-name-p name)
|
|
(dom-error :INVALID_CHARACTER_ERR "not a name: ~A" (rod-string name)))
|
|
(make-instance 'attribute
|
|
:name name
|
|
:local-name nil
|
|
:prefix nil
|
|
:namespace-uri nil
|
|
:specified-p t
|
|
:owner-element nil
|
|
:owner document))
|
|
|
|
(defmethod dom:create-attribute-ns ((document document) uri qname)
|
|
(setf uri (%rod uri))
|
|
(setf qname (%rod qname))
|
|
(when (and (rod= qname #"xmlns")
|
|
(not (rod= uri #"http://www.w3.org/2000/xmlns/")))
|
|
(dom-error :NAMESPACE_ERR "invalid uri for qname `xmlns'"))
|
|
(multiple-value-bind (prefix local-name)
|
|
(safe-split-qname qname uri)
|
|
(make-instance 'attribute
|
|
:name qname
|
|
:namespace-uri uri
|
|
:local-name local-name
|
|
:prefix prefix
|
|
:specified-p t
|
|
:owner-element nil
|
|
:owner document)))
|
|
|
|
(defmethod dom:create-entity-reference ((document document) name)
|
|
(setf name (%rod name))
|
|
(unless (valid-name-p name)
|
|
(dom-error :INVALID_CHARACTER_ERR "not a name: ~A" (rod-string name)))
|
|
(make-instance 'entity-reference
|
|
:name name
|
|
:owner document))
|
|
|
|
(defmethod get-elements-by-tag-name-internal (node tag-name)
|
|
(setf tag-name (%rod tag-name))
|
|
(let ((result (make-node-list))
|
|
(wild-p (rod= tag-name #"*")))
|
|
(labels ((walk (n)
|
|
(dovector (c (dom:child-nodes n))
|
|
(when (dom:element-p c)
|
|
(when (or wild-p (rod= tag-name (dom:node-name c)))
|
|
(vector-push-extend c result (extension result)))
|
|
(walk c)))))
|
|
(walk node))
|
|
result))
|
|
|
|
(defmethod get-elements-by-tag-name-internal-ns (node uri lname)
|
|
(setf uri (%rod uri))
|
|
(setf lname (%rod lname))
|
|
(let ((result (make-node-list))
|
|
(wild-uri-p (rod= uri #"*"))
|
|
(wild-lname-p (rod= lname #"*")))
|
|
(labels ((walk (n)
|
|
(dovector (c (dom:child-nodes n))
|
|
(when (dom:element-p c)
|
|
(when (and (or wild-lname-p (rod= lname (dom:local-name c)))
|
|
(or wild-uri-p (rod= uri (dom:namespace-uri c))))
|
|
(vector-push-extend c result (extension result)))
|
|
(walk c)))))
|
|
(walk node))
|
|
result))
|
|
|
|
(defmethod dom:get-elements-by-tag-name ((document document) tag-name)
|
|
(get-elements-by-tag-name-internal document tag-name))
|
|
|
|
(defmethod dom:get-elements-by-tag-name-ns ((document document) uri lname)
|
|
(get-elements-by-tag-name-internal-ns document uri lname))
|
|
|
|
(defmethod dom:get-element-by-id ((document document) id)
|
|
(block t
|
|
(unless (dtd document)
|
|
(return-from t nil))
|
|
(setf id (%rod id))
|
|
(labels ((walk (n)
|
|
(dovector (c (dom:child-nodes n))
|
|
(when (dom:element-p c)
|
|
(let ((e (cxml::find-element
|
|
(real-rod (dom:tag-name c))
|
|
(dtd document))))
|
|
(when e
|
|
(dolist (a (cxml::elmdef-attributes e))
|
|
(when (eq :ID (cxml::attdef-type a))
|
|
(let* ((name (%rod (cxml::attdef-name a)))
|
|
(value (dom:get-attribute c name)))
|
|
(when (and value (rod= value id))
|
|
(return-from t c)))))))
|
|
(walk c)))))
|
|
(walk document))))
|
|
|
|
|
|
;;; Node
|
|
|
|
(defmethod dom:has-attributes ((element node))
|
|
nil)
|
|
|
|
(defmethod dom:is-supported ((node node) feature version)
|
|
(dom:has-feature 'implementation feature version))
|
|
|
|
(defmethod dom:parent-node ((node node))
|
|
(slot-value node 'parent))
|
|
|
|
(defmethod dom:child-nodes ((node node))
|
|
(slot-value node 'children))
|
|
|
|
(defmethod dom:first-child ((node node))
|
|
(dom:item (slot-value node 'children) 0))
|
|
|
|
(defmethod dom:last-child ((node node))
|
|
(with-slots (children) node
|
|
(if (plusp (length children))
|
|
(elt children (1- (length children)))
|
|
nil)))
|
|
|
|
(defmethod dom:previous-sibling ((node node))
|
|
(with-slots (parent) node
|
|
(when parent
|
|
(with-slots (children) parent
|
|
(let ((index (1- (position node children))))
|
|
(if (eql index -1)
|
|
nil
|
|
(elt children index)))))))
|
|
|
|
(defmethod dom:next-sibling ((node node))
|
|
(with-slots (parent) node
|
|
(when parent
|
|
(with-slots (children) parent
|
|
(let ((index (1+ (position node children))))
|
|
(if (eql index (length children))
|
|
nil
|
|
(elt children index)))))))
|
|
|
|
(defmethod dom:owner-document ((node node))
|
|
(slot-value node 'owner))
|
|
|
|
(defun ensure-valid-insertion-request (node new-child)
|
|
(assert-writeable node)
|
|
(unless (can-adopt-p node new-child)
|
|
(dom-error :HIERARCHY_REQUEST_ERR "~S cannot adopt ~S." node new-child))
|
|
#+(or) ;XXX needs to be moved elsewhere
|
|
(when (eq (dom:node-type node) :document)
|
|
(let ((child-type (dom:node-type new-child)))
|
|
(when (and (member child-type '(:element :document-type))
|
|
(find child-type (dom:child-nodes node) :key #'dom:node-type))
|
|
(dom-error :HIERARCHY_REQUEST_ERR
|
|
"~S cannot adopt a second child of type ~S."
|
|
node child-type))))
|
|
(unless (eq (if (eq (dom:node-type node) :document)
|
|
node
|
|
(dom:owner-document node))
|
|
(dom:owner-document new-child))
|
|
(dom-error :WRONG_DOCUMENT_ERR
|
|
"~S cannot adopt ~S, since it was created by a different document."
|
|
node new-child))
|
|
(do ((n node (dom:parent-node n)))
|
|
((null n))
|
|
(when (eq n new-child)
|
|
(dom-error :HIERARCHY_REQUEST_ERR
|
|
"~S cannot adopt ~S, since that would create a cycle"
|
|
node new-child)))
|
|
(unless (null (slot-value new-child 'parent))
|
|
(dom:remove-child (slot-value new-child 'parent) new-child)))
|
|
|
|
(defmethod dom:insert-before ((node node) (new-child node) ref-child)
|
|
(ensure-valid-insertion-request node new-child)
|
|
(with-slots (children) node
|
|
(if ref-child
|
|
(let ((i (position ref-child children)))
|
|
(unless i
|
|
(dom-error :NOT_FOUND_ERR "~S is no child of ~S." ref-child node))
|
|
(make-space children 1)
|
|
(move children children i (1+ i) (- (length children) i))
|
|
(incf (fill-pointer children))
|
|
(setf (elt children i) new-child))
|
|
(vector-push-extend new-child children (extension children)))
|
|
(setf (slot-value new-child 'parent) node)
|
|
new-child))
|
|
|
|
(defmethod dom:insert-before
|
|
((node node) (fragment document-fragment) ref-child)
|
|
(let ((children (dom:child-nodes fragment)))
|
|
(cxml::while (plusp (length children))
|
|
(dom:insert-before node (elt children 0) ref-child)))
|
|
fragment)
|
|
|
|
(defmethod dom:replace-child ((node node) (new-child node) (old-child node))
|
|
(ensure-valid-insertion-request node new-child)
|
|
(with-slots (children) node
|
|
(let ((i (position old-child children)))
|
|
(unless i
|
|
(dom-error :NOT_FOUND_ERR "~S is no child of ~S." old-child node))
|
|
(setf (elt children i) new-child))
|
|
(setf (slot-value new-child 'parent) node)
|
|
(setf (slot-value old-child 'parent) nil)
|
|
old-child))
|
|
|
|
(defmethod dom:replace-child
|
|
((node node) (new-child document-fragment) (old-child node))
|
|
(dom:insert-before node new-child old-child)
|
|
(dom:remove-child node old-child))
|
|
|
|
(defmethod dom:remove-child ((node node) (old-child node))
|
|
(assert-writeable node)
|
|
(with-slots (children) node
|
|
(let ((i (position old-child children)))
|
|
(unless i
|
|
(dom-error :NOT_FOUND_ERR "~A not found in ~A" old-child node))
|
|
(move children children (1+ i) i (- (length children) i 1))
|
|
(decf (fill-pointer children)))
|
|
(setf (slot-value old-child 'parent) nil)
|
|
old-child))
|
|
|
|
(defmethod dom:append-child ((node node) (new-child node))
|
|
(ensure-valid-insertion-request node new-child)
|
|
(with-slots (children) node
|
|
(vector-push-extend new-child children (extension children))
|
|
(setf (slot-value new-child 'parent) node)
|
|
new-child))
|
|
|
|
(defmethod dom:has-child-nodes ((node node))
|
|
(plusp (length (slot-value node 'children))))
|
|
|
|
(defmethod dom:append-child ((node node) (new-child document-fragment))
|
|
(assert-writeable node)
|
|
(let ((children (dom:child-nodes new-child)))
|
|
(cxml::while (plusp (length children))
|
|
(dom:append-child node (elt children 0))))
|
|
new-child)
|
|
|
|
;; was auf node noch implemetiert werden muss:
|
|
;; - node-type
|
|
;; - can-adopt-p
|
|
;; - ggf attributes
|
|
;; - node-name
|
|
;; - node-value
|
|
|
|
;; node-name
|
|
|
|
(defmethod dom:node-name ((self document))
|
|
#"#document")
|
|
|
|
(defmethod dom:node-name ((self document-fragment))
|
|
#"#document-fragment")
|
|
|
|
(defmethod dom:node-name ((self text))
|
|
#"#text")
|
|
|
|
(defmethod dom:node-name ((self cdata-section))
|
|
#"#cdata-section")
|
|
|
|
(defmethod dom:node-name ((self comment))
|
|
#"#comment")
|
|
|
|
(defmethod dom:node-name ((self attribute))
|
|
(dom:name self))
|
|
|
|
(defmethod dom:node-name ((self element))
|
|
(dom:tag-name self))
|
|
|
|
(defmethod dom:node-name ((self document-type))
|
|
(dom:name self))
|
|
|
|
(defmethod dom:node-name ((self notation))
|
|
(dom:name self))
|
|
|
|
(defmethod dom:node-name ((self entity))
|
|
(dom:name self))
|
|
|
|
(defmethod dom:node-name ((self entity-reference))
|
|
(dom:name self))
|
|
|
|
(defmethod dom:node-name ((self processing-instruction))
|
|
(dom:target self))
|
|
|
|
;; node-type
|
|
|
|
(defmethod dom:node-type ((self document)) :document)
|
|
(defmethod dom:node-type ((self document-fragment)) :document-fragment)
|
|
(defmethod dom:node-type ((self text)) :text)
|
|
(defmethod dom:node-type ((self comment)) :comment)
|
|
(defmethod dom:node-type ((self cdata-section)) :cdata-section)
|
|
(defmethod dom:node-type ((self attribute)) :attribute)
|
|
(defmethod dom:node-type ((self element)) :element)
|
|
(defmethod dom:node-type ((self document-type)) :document-type)
|
|
(defmethod dom:node-type ((self notation)) :notation)
|
|
(defmethod dom:node-type ((self entity)) :entity)
|
|
(defmethod dom:node-type ((self entity-reference)) :entity-reference)
|
|
(defmethod dom:node-type ((self processing-instruction)) :processing-instruction)
|
|
|
|
;; node-value
|
|
|
|
(defmethod dom:node-value ((self document)) nil)
|
|
(defmethod dom:node-value ((self document-fragment)) nil)
|
|
(defmethod dom:node-value ((self character-data)) (dom:data self))
|
|
(defmethod dom:node-value ((self attribute)) (dom:value self))
|
|
(defmethod dom:node-value ((self element)) nil)
|
|
(defmethod dom:node-value ((self document-type)) nil)
|
|
(defmethod dom:node-value ((self notation)) nil)
|
|
(defmethod dom:node-value ((self entity)) nil)
|
|
(defmethod dom:node-value ((self entity-reference)) nil)
|
|
(defmethod dom:node-value ((self processing-instruction)) (dom:data self))
|
|
|
|
;; (setf node-value), first the meaningful cases...
|
|
|
|
(defmethod (setf dom:node-value) (newval (self character-data))
|
|
(assert-writeable self)
|
|
(setf (dom:data self) newval))
|
|
|
|
(defmethod (setf dom:node-value) (newval (self attribute))
|
|
(assert-writeable self)
|
|
(setf (dom:value self) newval))
|
|
|
|
(defmethod (setf dom:node-value) (newval (self processing-instruction))
|
|
(assert-writeable self)
|
|
(setf (dom:data self) newval))
|
|
|
|
;; ... and (setf node-value), part II. The DOM Level 1 spec fails to explain
|
|
;; this case, but it is covered by the (Level 1) test suite and clarified
|
|
;; in Level 2:
|
|
;; nodeValue of type DOMString
|
|
;; The value of this node, depending on its type; see the
|
|
;; table above. When it is defined to be null, setting
|
|
;; it has no effect.
|
|
|
|
(defmethod (setf dom:node-value) (newval (self element))
|
|
(declare (ignore newval)))
|
|
|
|
(defmethod (setf dom:node-value) (newval (self entity-reference))
|
|
(declare (ignore newval)))
|
|
|
|
(defmethod (setf dom:node-value) (newval (self entity))
|
|
(declare (ignore newval)))
|
|
|
|
(defmethod (setf dom:node-value) (newval (self document))
|
|
(declare (ignore newval)))
|
|
|
|
(defmethod (setf dom:node-value) (newval (self document-type))
|
|
(declare (ignore newval)))
|
|
|
|
(defmethod (setf dom:node-value) (newval (self document-fragment))
|
|
(declare (ignore newval)))
|
|
|
|
(defmethod (setf dom:node-value) (newval (self notation))
|
|
(declare (ignore newval)))
|
|
|
|
;; attributes
|
|
|
|
;; (gibt es nur auf element)
|
|
|
|
(defmethod dom:attributes ((self node))
|
|
nil)
|
|
|
|
;; dann fehlt noch can-adopt und attribute conventions fuer adoption
|
|
|
|
;;; NodeList
|
|
|
|
(defun make-node-list (&optional initial-contents)
|
|
(make-array (length initial-contents)
|
|
:adjustable t
|
|
:fill-pointer (length initial-contents)
|
|
:initial-contents initial-contents))
|
|
|
|
(defmethod dom:item ((self vector) index)
|
|
(if (< index (length self))
|
|
(elt self index)
|
|
nil))
|
|
|
|
(defmethod dom:length ((self vector))
|
|
(length self))
|
|
|
|
;;; NAMED-NODE-MAP
|
|
|
|
(defmethod dom:get-named-item ((self named-node-map) name)
|
|
(setf name (%rod name))
|
|
(with-slots (items) self
|
|
(dolist (k items nil)
|
|
(when (rod= name (dom:node-name k))
|
|
(return k)))))
|
|
|
|
(defmethod dom:get-named-item-ns ((self named-node-map) uri lname)
|
|
(setf uri (%rod uri))
|
|
(setf lname (%rod lname))
|
|
(with-slots (items) self
|
|
(dolist (k items nil)
|
|
(when (and (rod= uri (dom:namespace-uri k))
|
|
(rod= lname (dom:local-name k)))
|
|
(return k)))))
|
|
|
|
(defun %set-named-item (map arg test)
|
|
(assert-writeable map)
|
|
(unless (eq (dom:node-type arg) (slot-value map 'element-type))
|
|
(dom-error :HIERARCHY_REQUEST_ERR
|
|
"~S cannot adopt ~S, since it is not of type ~S."
|
|
map arg (slot-value map 'element-type)))
|
|
(unless (eq (dom:owner-document map) (dom:owner-document arg))
|
|
(dom-error :WRONG_DOCUMENT_ERR
|
|
"~S cannot adopt ~S, since it was created by a different document."
|
|
map arg))
|
|
(let ((old-map (slot-value arg 'map)))
|
|
(when (and old-map (not (eq old-map map)))
|
|
(dom-error :INUSE_ATTRIBUTE_ERR "Attribute node already mapped" arg)))
|
|
(setf (slot-value arg 'map) map)
|
|
(with-slots (items) map
|
|
(dolist (k items (progn (setf items (cons arg items)) nil))
|
|
(when (funcall test k)
|
|
(setf items (cons arg (delete k items)))
|
|
(return k)))))
|
|
|
|
(defmethod dom:set-named-item ((self named-node-map) arg)
|
|
(let ((name (dom:node-name arg)))
|
|
(%set-named-item self arg (lambda (k) (rod= name (dom:node-name k))))))
|
|
|
|
(defmethod dom:set-named-item-ns ((self named-node-map) arg)
|
|
(let ((uri (dom:namespace-uri arg))
|
|
(lname (dom:local-name arg)))
|
|
(%set-named-item self
|
|
arg
|
|
(lambda (k)
|
|
(and (rod= lname (dom:local-name k))
|
|
(rod= uri (dom:namespace-uri k)))))))
|
|
|
|
(defmethod dom:remove-named-item ((self named-node-map) name)
|
|
(assert-writeable self)
|
|
(setf name (%rod name))
|
|
(with-slots (items) self
|
|
(dolist (k items (dom-error :NOT_FOUND_ERR "~A not found in ~A" name self))
|
|
(cond ((rod= name (dom:node-name k))
|
|
(setf items (delete k items))
|
|
(return k))))))
|
|
|
|
(defmethod dom:remove-named-item-ns ((self named-node-map) uri lname)
|
|
(assert-writeable self)
|
|
(setf uri (%rod uri))
|
|
(setf lname (%rod lname))
|
|
(with-slots (items) self
|
|
(dolist (k items
|
|
(dom-error :NOT_FOUND_ERR "~A not found in ~A" lname self))
|
|
(when (and (rod= lname (dom:local-name k))
|
|
(rod= uri (dom:namespace-uri k)))
|
|
(setf items (delete k items))
|
|
(return k)))))
|
|
|
|
(defmethod dom:length ((self named-node-map))
|
|
(with-slots (items) self
|
|
(length items)))
|
|
|
|
(defmethod dom:item ((self named-node-map) index)
|
|
(with-slots (items) self
|
|
(do ((nthcdr items (cdr nthcdr))
|
|
(i index (1- i)))
|
|
((zerop i) (car nthcdr)))))
|
|
|
|
;;; CHARACTER-DATA
|
|
|
|
(defmethod (setf dom:data) (newval (self character-data))
|
|
(assert-writeable self)
|
|
(setf newval (%rod newval))
|
|
(setf (slot-value self 'value) newval))
|
|
|
|
(defmethod dom:length ((node character-data))
|
|
(length (slot-value node 'value)))
|
|
|
|
(defmethod dom:substring-data ((node character-data) offset count)
|
|
(with-slots (value) node
|
|
(unless (<= 0 offset (length value))
|
|
(dom-error :INDEX_SIZE_ERR "offset is invalid"))
|
|
(let ((end (min (length value) (+ offset count))))
|
|
(subseq value offset end))))
|
|
|
|
(defmethod dom:append-data ((node character-data) arg)
|
|
(assert-writeable node)
|
|
(setq arg (%rod arg))
|
|
(with-slots (value) node
|
|
(setf value (concatenate 'rod value arg)))
|
|
(values))
|
|
|
|
(defmethod dom:delete-data ((node character-data) offset count)
|
|
(assert-writeable node)
|
|
(with-slots (value) node
|
|
(unless (<= 0 offset (length value))
|
|
(dom-error :INDEX_SIZE_ERR "offset is invalid"))
|
|
(when (minusp count)
|
|
(dom-error :INDEX_SIZE_ERR "count is negative"))
|
|
(setf count (min count (- (length value) offset)))
|
|
(let ((new (make-array (- (length value) count)
|
|
:element-type (array-element-type value))))
|
|
(replace new value
|
|
:start1 0 :end1 offset
|
|
:start2 0 :end2 offset)
|
|
(replace new value
|
|
:start1 offset :end1 (length new)
|
|
:start2 (+ offset count) :end2 (length value))
|
|
(setf value new)))
|
|
(values))
|
|
|
|
(defmethod dom:replace-data ((node character-data) offset count arg)
|
|
;; Although we could implement this by calling DELETE-DATA, then INSERT-DATA,
|
|
;; we implement this function directly to avoid creating temporary garbage.
|
|
(assert-writeable node)
|
|
(setf arg (%rod arg))
|
|
(with-slots (value) node
|
|
(unless (<= 0 offset (length value))
|
|
(dom-error :INDEX_SIZE_ERR "offset is invalid"))
|
|
(when (minusp count)
|
|
(dom-error :INDEX_SIZE_ERR "count is negative"))
|
|
(setf count (min count (- (length value) offset)))
|
|
(if (= count (length arg))
|
|
(replace value arg
|
|
:start1 offset :end1 (+ offset count)
|
|
:start2 0 :end2 count)
|
|
(let ((new (make-array (+ (length value) (length arg) (- count))
|
|
:element-type (array-element-type value))))
|
|
(replace new value :end1 offset)
|
|
(replace new arg :start1 offset)
|
|
(replace new value
|
|
:start1 (+ offset (length arg))
|
|
:start2 (+ offset count))
|
|
(setf value new))))
|
|
(values))
|
|
|
|
(defmethod dom:insert-data ((node character-data) offset arg)
|
|
(assert-writeable node)
|
|
(setf arg (%rod arg))
|
|
(with-slots (value) node
|
|
(unless (<= 0 offset (length value))
|
|
(dom-error :INDEX_SIZE_ERR "offset is invalid"))
|
|
(let ((new (make-array (+ (length value) (length arg))
|
|
:element-type (array-element-type value)))
|
|
(arglen (length arg)))
|
|
(replace new value :end1 offset)
|
|
(replace new arg :start1 offset)
|
|
(replace new value :start1 (+ offset arglen) :start2 offset)
|
|
(setf value new)))
|
|
(values))
|
|
|
|
;;; ATTR
|
|
;;;
|
|
;;; An attribute value can be read and set as a string using DOM:VALUE
|
|
;;; or frobbed by changing the attribute's children!
|
|
;;;
|
|
;;; We store the value in a TEXT node and read this node's DATA slot
|
|
;;; when asked for our VALUE -- until the user changes the child nodes,
|
|
;;; in which case we have to compute VALUE by traversing the children.
|
|
|
|
(defmethod dom:value ((node attribute))
|
|
(with-slots (children) node
|
|
(cond
|
|
((zerop (length children))
|
|
#.(rod-string ""))
|
|
((and (eql (length children) 1)
|
|
(eq (dom:node-type (elt children 0)) :text))
|
|
;; we have as single TEXT-NODE child, just return its DATA
|
|
(dom:data (elt children 0)))
|
|
(t
|
|
;; traverse children to compute value
|
|
(attribute-to-string node)))))
|
|
|
|
(defmethod (setf dom:value) (new-value (node attribute))
|
|
(assert-writeable node)
|
|
(let ((rod (%rod new-value)))
|
|
(with-slots (children owner) node
|
|
;; remove children, add new TEXT-NODE child
|
|
;; (alas, we must not reuse an old TEXT-NODE)
|
|
(cxml::while (plusp (length children))
|
|
(dom:remove-child node (dom:last-child node)))
|
|
(dom:append-child node (dom:create-text-node owner rod))))
|
|
new-value)
|
|
|
|
(defun attribute-to-string (attribute)
|
|
(let ((stream (make-rod-stream)))
|
|
(flet ((doit ()
|
|
(dovector (child (dom:child-nodes attribute))
|
|
(write-attribute-child child stream))))
|
|
(doit)
|
|
(initialize-rod-stream stream)
|
|
(doit))
|
|
(rod-stream-buf stream)))
|
|
|
|
(defmethod write-attribute-child ((node node) stream)
|
|
(put-rod (dom:node-value node) stream))
|
|
|
|
(defmethod write-attribute-child ((node entity-reference) stream)
|
|
(dovector (child (dom:child-nodes node))
|
|
(write-attribute-child child stream)))
|
|
|
|
;;; ROD-STREAM als Ersatz fuer MAKE-STRING-OUTPUT-STREAM zu verwenden,
|
|
;;; nur dass der Buffer statische Groesse hat. Solange er NIL ist,
|
|
;;; zaehlt der Stream nur die Runen. Dann ruft man INITIALIZE-ROD-STREAM
|
|
;;; auf, um den Buffer zu erzeugen und die Position zurueckzusetzen, und
|
|
;;; schreibt alles abermals. Dann ist der Buffer gefuellt.
|
|
(defstruct rod-stream
|
|
(buf nil)
|
|
(position 0))
|
|
|
|
(defun put-rod (rod rod-stream)
|
|
(let ((buf (rod-stream-buf rod-stream)))
|
|
(when buf
|
|
(move rod buf 0 (rod-stream-position rod-stream) (length rod)))
|
|
(incf (rod-stream-position rod-stream) (length rod)))
|
|
rod)
|
|
|
|
(defun initialize-rod-stream (stream)
|
|
(setf (rod-stream-buf stream) (make-rod (rod-stream-position stream)))
|
|
(setf (rod-stream-position stream) 0)
|
|
stream)
|
|
|
|
;;; ELEMENT
|
|
|
|
(defmethod dom:has-attributes ((element element))
|
|
(plusp (length (dom:items (dom:attributes element)))))
|
|
|
|
(defmethod dom:has-attribute ((element element) name)
|
|
(and (dom:get-named-item (dom:attributes element) name) t))
|
|
|
|
(defmethod dom:has-attribute-ns ((element element) uri lname)
|
|
(and (dom:get-named-item-ns (dom:attributes element) uri lname) t))
|
|
|
|
(defmethod dom:get-attribute-node ((element element) name)
|
|
(dom:get-named-item (dom:attributes element) name))
|
|
|
|
(defmethod dom:set-attribute-node ((element element) (new-attr attribute))
|
|
(assert-writeable element)
|
|
(dom:set-named-item (dom:attributes element) new-attr))
|
|
|
|
(defmethod dom:get-attribute-node-ns ((element element) uri lname)
|
|
(dom:get-named-item-ns (dom:attributes element) uri lname))
|
|
|
|
(defmethod dom:set-attribute-node-ns ((element element) (new-attr attribute))
|
|
(assert-writeable element)
|
|
(dom:set-named-item-ns (dom:attributes element) new-attr))
|
|
|
|
(defmethod dom:get-attribute ((element element) name)
|
|
(let ((a (dom:get-attribute-node element name)))
|
|
(if a
|
|
(dom:value a)
|
|
#"")))
|
|
|
|
(defmethod dom:get-attribute-ns ((element element) uri lname)
|
|
(let ((a (dom:get-attribute-node-ns element uri lname)))
|
|
(if a
|
|
(dom:value a)
|
|
#"")))
|
|
|
|
(defmethod dom:set-attribute ((element element) name value)
|
|
(assert-writeable element)
|
|
(with-slots (owner) element
|
|
(let ((attr (dom:create-attribute owner name)))
|
|
(setf (slot-value attr 'owner-element) element)
|
|
(setf (dom:value attr) value)
|
|
(dom:set-attribute-node element attr))
|
|
(values)))
|
|
|
|
(defmethod dom:set-attribute-ns ((element element) uri lname value)
|
|
(assert-writeable element)
|
|
(with-slots (owner) element
|
|
(let ((attr (dom:create-attribute-ns owner uri lname)))
|
|
(setf (slot-value attr 'owner-element) element)
|
|
(setf (dom:value attr) value)
|
|
(dom:set-attribute-node-ns element attr))
|
|
(values)))
|
|
|
|
(defmethod dom:remove-attribute ((element element) name)
|
|
(assert-writeable element)
|
|
(dom:remove-attribute-node element (dom:get-attribute-node element name)))
|
|
|
|
(defmethod dom:remove-attribute-ns ((elt element) uri lname)
|
|
(assert-writeable elt)
|
|
(dom:remove-attribute-node elt (dom:get-attribute-node-ns elt uri lname)))
|
|
|
|
(defmethod dom:remove-attribute-node ((element element) (old-attr attribute))
|
|
(assert-writeable element)
|
|
(with-slots (items) (dom:attributes element)
|
|
(unless (find old-attr items)
|
|
(dom-error :NOT_FOUND_ERR "Attribute not found."))
|
|
(setf items (remove old-attr items))
|
|
(maybe-add-default-attribute element old-attr)
|
|
old-attr))
|
|
|
|
;; eek, defaulting:
|
|
|
|
(defun maybe-add-default-attribute (element old-attr)
|
|
(let* ((qname (dom:name old-attr))
|
|
(dtd (dtd (slot-value element 'owner)))
|
|
(e (when dtd (cxml::find-element
|
|
(real-rod (dom:tag-name element))
|
|
dtd)))
|
|
(a (when e (cxml::find-attribute e (real-rod qname)))))
|
|
(when (and a (listp (cxml::attdef-default a)))
|
|
(let ((new (add-default-attribute element a)))
|
|
(setf (slot-value new 'namespace-uri) (dom:namespace-uri old-attr))
|
|
(setf (slot-value new 'prefix) (dom:prefix old-attr))
|
|
(setf (slot-value new 'local-name) (dom:local-name old-attr))))))
|
|
|
|
(defun add-default-attributes (element)
|
|
(let* ((dtd (dtd (slot-value element 'owner)))
|
|
(e (when dtd (cxml::find-element
|
|
(real-rod (dom:tag-name element))
|
|
dtd))))
|
|
(when e
|
|
(dolist (a (cxml::elmdef-attributes e))
|
|
(when (and a
|
|
(listp (cxml::attdef-default a))
|
|
(not (dom:get-attribute-node
|
|
element
|
|
(%rod (cxml::attdef-name a)))))
|
|
(let ((anode (add-default-attribute element a)))
|
|
(multiple-value-bind (prefix local-name)
|
|
(handler-case
|
|
(cxml::split-qname (cxml::attdef-name a))
|
|
(cxml:well-formedness-violation (c)
|
|
(dom-error :NAMESPACE_ERR "~A" c)))
|
|
(when prefix (setf prefix (%rod prefix)))
|
|
(setf local-name (%rod local-name))
|
|
;; das ist fuer importnode07.
|
|
;; so richtig ueberzeugend finde ich das ja nicht.
|
|
(setf (slot-value anode 'prefix) prefix)
|
|
(setf (slot-value anode 'local-name) local-name))))))))
|
|
|
|
(defun add-default-attribute (element adef)
|
|
(let* ((value (second (cxml::attdef-default adef)))
|
|
(owner (slot-value element 'owner))
|
|
(anode (dom:create-attribute owner (cxml::attdef-name adef)))
|
|
(text (dom:create-text-node owner value)))
|
|
(setf (slot-value anode 'specified-p) nil)
|
|
(setf (slot-value anode 'owner-element) element)
|
|
(dom:append-child anode text)
|
|
(push anode (slot-value (dom:attributes element) 'items))
|
|
anode))
|
|
|
|
(defmethod dom:remove-named-item ((self attribute-node-map) name)
|
|
name
|
|
(let ((k (call-next-method)))
|
|
(maybe-add-default-attribute (slot-value self 'element) k)
|
|
k))
|
|
|
|
(defmethod dom:remove-named-item-ns ((self attribute-node-map) uri lname)
|
|
uri lname
|
|
(let ((k (call-next-method)))
|
|
(maybe-add-default-attribute (slot-value self 'element) k)
|
|
k))
|
|
|
|
(defmethod dom:get-elements-by-tag-name ((element element) name)
|
|
(assert-writeable element)
|
|
(get-elements-by-tag-name-internal element name))
|
|
|
|
(defmethod dom:get-elements-by-tag-name-ns ((element element) uri lname)
|
|
(assert-writeable element)
|
|
(get-elements-by-tag-name-internal-ns element uri lname))
|
|
|
|
(defmethod dom:set-named-item :after ((self attribute-node-map) arg)
|
|
(setf (slot-value arg 'owner-element)
|
|
(slot-value self 'element)))
|
|
|
|
(defmethod dom:set-named-item-ns :after ((self attribute-node-map) arg)
|
|
(setf (slot-value arg 'owner-element)
|
|
(slot-value self 'element)))
|
|
|
|
(defmethod dom:normalize ((node node))
|
|
(assert-writeable node)
|
|
(labels ((walk (n)
|
|
(when (eq (dom:node-type n) :element)
|
|
(map nil #'walk (dom:items (dom:attributes n))))
|
|
(let ((children (dom:child-nodes n))
|
|
(i 0)
|
|
(previous nil))
|
|
;; careful here, we're modifying the array we are iterating over
|
|
(cxml::while (< i (length children))
|
|
(let ((child (elt children i)))
|
|
(cond
|
|
((not (eq (dom:node-type child) :text))
|
|
(setf previous nil)
|
|
(incf i))
|
|
((and previous (eq (dom:node-type previous) :text))
|
|
(setf (slot-value previous 'value)
|
|
(concatenate 'rod
|
|
(dom:data previous)
|
|
(dom:data child)))
|
|
(dom:remove-child n child)
|
|
;; not (incf i)
|
|
)
|
|
((zerop (length (dom:data child)))
|
|
(dom:remove-child n child)
|
|
;; not (incf i)
|
|
)
|
|
(t
|
|
(setf previous child)
|
|
(incf i))))))
|
|
(map nil #'walk (dom:child-nodes n))))
|
|
(walk node))
|
|
(values))
|
|
|
|
;;; TEXT
|
|
|
|
(defmethod dom:split-text ((text text) offset)
|
|
(assert-writeable text)
|
|
(with-slots (owner parent value) text
|
|
(unless (<= 0 offset (length value))
|
|
(dom-error :INDEX_SIZE_ERR "offset is invalid"))
|
|
(prog1
|
|
(dom:insert-before parent
|
|
(dom:create-text-node owner (subseq value offset))
|
|
(dom:next-sibling text))
|
|
(setf value (subseq value 0 offset)))))
|
|
|
|
;;; COMMENT -- nix
|
|
;;; CDATA-SECTION -- nix
|
|
|
|
;;; DOCUMENT-TYPE
|
|
|
|
(defmethod dom:internal-subset ((node document-type))
|
|
;; FIXME: encoding ist falsch, anderen sink nehmen!
|
|
(if (and (slot-boundp node 'dom::%internal-subset)
|
|
;; die damen und herren von der test suite sind wohl der meinung,
|
|
;; dass ein leeres internal subset nicht vorhanden ist und
|
|
;; wir daher nil liefern sollen. bittesehr!
|
|
(dom::%internal-subset node))
|
|
(let ((sink
|
|
#+rune-is-character (cxml:make-string-sink)
|
|
#-rune-is-character (cxml:make-string-sink/utf8)))
|
|
(dolist (def (dom::%internal-subset node))
|
|
(apply (car def) sink (cdr def)))
|
|
(sax:end-document sink))
|
|
nil))
|
|
|
|
;;; NOTATION -- nix
|
|
;;; ENTITY -- nix
|
|
|
|
;;; ENTITY-REFERENCE
|
|
|
|
(defmethod initialize-instance :after ((instance entity-reference) &key)
|
|
(let* ((owner (dom:owner-document instance))
|
|
(handler (make-dom-builder))
|
|
(resolver (slot-value owner 'entity-resolver)))
|
|
(when resolver
|
|
(setf (document handler) owner)
|
|
(push instance (element-stack handler))
|
|
#+cxml-system::utf8dom-file
|
|
(setf handler (cxml:make-recoder handler #'cxml:rod-to-utf8-string))
|
|
(funcall resolver (real-rod (dom:name instance)) handler)
|
|
(flush-characters handler)))
|
|
(labels ((walk (n)
|
|
(setf (slot-value n 'read-only-p) t)
|
|
(when (dom:element-p n)
|
|
(setf (slot-value (dom:attributes n) 'read-only-p) t)
|
|
(map nil #'walk (dom:items (dom:attributes n))))
|
|
(map nil #'walk (dom:child-nodes n))))
|
|
(walk instance)))
|
|
|
|
;;; PROCESSING-INSTRUCTION
|
|
|
|
(defmethod (setf dom:data) (newval (self processing-instruction))
|
|
(assert-writeable self)
|
|
(setf newval (%rod newval))
|
|
(setf (slot-value self 'data) newval))
|
|
|
|
;; das koennte man auch mit einer GF machen
|
|
(defun can-adopt-p (parent child)
|
|
(member (dom:node-type child)
|
|
(let ((default '(:element :processing-instruction :comment :text
|
|
:cdata-section :entity-reference)))
|
|
(etypecase parent
|
|
(document
|
|
'(:element :processing-instruction :comment :document-type))
|
|
(document-fragment default)
|
|
(document-type nil)
|
|
(entity-reference default)
|
|
(element default)
|
|
(attribute '(:text :entity-reference))
|
|
(processing-instruction nil)
|
|
(comment nil)
|
|
(text nil)
|
|
(cdata-section nil)
|
|
(entity default)
|
|
(notation nil)))))
|
|
|
|
|
|
;;; predicates
|
|
|
|
(defmethod dom:node-p ((object node)) t)
|
|
(defmethod dom:node-p ((object t)) nil)
|
|
|
|
(defmethod dom:document-p ((object document)) t)
|
|
(defmethod dom:document-p ((object t)) nil)
|
|
|
|
(defmethod dom:document-fragment-p ((object document-fragment)) t)
|
|
(defmethod dom:document-fragment-p ((object t)) nil)
|
|
|
|
(defmethod dom:character-data-p ((object character-data)) t)
|
|
(defmethod dom:character-data-p ((object t)) nil)
|
|
|
|
(defmethod dom:attribute-p ((object attribute)) t)
|
|
(defmethod dom:attribute-p ((object t)) nil)
|
|
|
|
(defmethod dom:element-p ((object element)) t)
|
|
(defmethod dom:element-p ((object t)) nil)
|
|
|
|
(defmethod dom:text-node-p ((object text)) t)
|
|
(defmethod dom:text-node-p ((object t)) nil)
|
|
|
|
(defmethod dom:comment-p ((object comment)) t)
|
|
(defmethod dom:comment-p ((object t)) nil)
|
|
|
|
(defmethod dom:cdata-section-p ((object cdata-section)) t)
|
|
(defmethod dom:cdata-section-p ((object t)) nil)
|
|
|
|
(defmethod dom:document-type-p ((object document-type)) t)
|
|
(defmethod dom:document-type-p ((object t)) nil)
|
|
|
|
(defmethod dom:notation-p ((object notation)) t)
|
|
(defmethod dom:notation-p ((object t)) nil)
|
|
|
|
(defmethod dom:entity-p ((object entity)) t)
|
|
(defmethod dom:entity-p ((object t)) nil)
|
|
|
|
(defmethod dom:entity-reference-p ((object entity-reference)) t)
|
|
(defmethod dom:entity-reference-p ((object t)) nil)
|
|
|
|
(defmethod dom:processing-instruction-p ((object processing-instruction)) t)
|
|
(defmethod dom:processing-instruction-p ((object t)) nil)
|
|
|
|
(defmethod dom:named-node-map-p ((object named-node-map)) t)
|
|
(defmethod dom:named-node-map-p ((object t)) nil)
|
|
|
|
|
|
;;; IMPORT-NODE
|
|
|
|
(defvar *clone-not-import* nil) ;not beautiful, I know. See below.
|
|
|
|
(defmethod import-node-internal (class document node deep &rest initargs)
|
|
(let ((result (apply #'make-instance class :owner document initargs)))
|
|
(when deep
|
|
(dovector (child (dom:child-nodes node))
|
|
(dom:append-child result (dom:import-node document child t))))
|
|
result))
|
|
|
|
(defmethod dom:import-node ((document document) (node t) deep)
|
|
(declare (ignore deep))
|
|
(dom-error :NOT_SUPPORTED_ERR "not implemented"))
|
|
|
|
(defmethod dom:import-node ((document document) (node attribute) deep)
|
|
(declare (ignore deep))
|
|
(import-node-internal 'attribute
|
|
document node
|
|
t
|
|
:specified-p (dom:specified node)
|
|
:name (dom:name node)
|
|
:namespace-uri (dom:namespace-uri node)
|
|
:local-name (dom:local-name node)
|
|
:prefix (dom:prefix node)
|
|
:owner-element nil))
|
|
|
|
(defmethod dom:import-node ((document document) (node document-fragment) deep)
|
|
(import-node-internal 'document-fragment document node deep))
|
|
|
|
(defmethod dom:import-node ((document document) (node element) deep)
|
|
(let* ((attributes (make-instance 'attribute-node-map
|
|
:element-type :attribute
|
|
:owner document))
|
|
(result (import-node-internal 'element document node deep
|
|
:attributes attributes
|
|
:namespace-uri (dom:namespace-uri node)
|
|
:local-name (dom:local-name node)
|
|
:prefix (dom:prefix node)
|
|
:tag-name (dom:tag-name node))))
|
|
(setf (slot-value attributes 'element) result)
|
|
(dolist (attribute (dom:items (dom:attributes node)))
|
|
(when (or (dom:specified attribute) *clone-not-import*)
|
|
(let ((attr (dom:import-node document attribute t)))
|
|
(if (dom:namespace-uri attribute)
|
|
(dom:set-attribute-node-ns result attr)
|
|
(dom:set-attribute-node result attr)))))
|
|
(add-default-attributes result)
|
|
result))
|
|
|
|
(defmethod dom:import-node ((document document) (node entity) deep)
|
|
(import-node-internal 'entity document node deep
|
|
:name (dom:name node)
|
|
:public-id (dom:public-id node)
|
|
:system-id (dom:system-id node)
|
|
:notation-name (dom:notation-name node)))
|
|
|
|
(defmethod dom:import-node ((document document) (node entity-reference) deep)
|
|
(declare (ignore deep))
|
|
(import-node-internal 'entity-reference document node nil
|
|
:name (dom:name node)))
|
|
|
|
(defmethod dom:import-node ((document document) (node notation) deep)
|
|
(import-node-internal 'notation document node deep
|
|
:name (dom:name node)
|
|
:public-id (dom:public-id node)
|
|
:system-id (dom:system-id node)))
|
|
|
|
(defmethod dom:import-node
|
|
((document document) (node processing-instruction) deep)
|
|
(import-node-internal 'processing-instruction document node deep
|
|
:target (dom:target node)
|
|
:data (dom:data node)))
|
|
|
|
;; TEXT_NODE, CDATA_SECTION_NODE, COMMENT_NODE
|
|
(defmethod dom:import-node
|
|
((document document) (node character-data) deep)
|
|
(import-node-internal (class-of node) document node deep
|
|
:data (copy-seq (dom:data node))))
|
|
|
|
;;; CLONE-NODE
|
|
;;;
|
|
;;; As far as I can tell, cloneNode is the same as importNode, except
|
|
;;; for one difference involving element attributes: importNode imports
|
|
;;; only specified attributes, cloneNode copies even default values.
|
|
;;;
|
|
;;; Since I don't want to reimplement all of importNode here, we run
|
|
;;; importNode with a special flag...
|
|
|
|
(defmethod dom:clone-node ((node node) deep)
|
|
(let ((*clone-not-import* t))
|
|
(dom:import-node (dom:owner-document node) node deep)))
|
|
|
|
;; extension:
|
|
(defmethod dom:clone-node ((node document) deep)
|
|
(let* ((document (make-instance 'document))
|
|
(original-doctype (dom:doctype node))
|
|
(doctype
|
|
(when original-doctype
|
|
(make-instance 'document-type
|
|
:owner document
|
|
:name (dom:name original-doctype)
|
|
:public-id (dom:public-id original-doctype)
|
|
:system-id (dom:system-id original-doctype)
|
|
:notations (make-instance 'named-node-map
|
|
:element-type :notation
|
|
:owner document
|
|
:items (dom:items (dom:notations original-doctype)))
|
|
:entities (make-instance 'named-node-map
|
|
:element-type :entity
|
|
:owner document
|
|
:items (dom:items
|
|
(dom:entities original-doctype)))))))
|
|
(setf (slot-value document 'owner) nil)
|
|
(setf (slot-value document 'doc-type) doctype)
|
|
(setf (slot-value document 'dtd) (dtd node))
|
|
(setf (slot-value document 'entity-resolver)
|
|
(slot-value node 'entity-resolver))
|
|
(setf (slot-value (dom:entities doctype) 'read-only-p) t)
|
|
(setf (slot-value (dom:notations doctype) 'read-only-p) t)
|
|
(when (and doctype (slot-boundp doctype 'dom::%internal-subset))
|
|
(setf (dom::%internal-subset doctype)
|
|
(dom::%internal-subset original-doctype)))
|
|
(when (and (dom:document-element node) deep)
|
|
(let* ((*clone-not-import* t)
|
|
(clone (dom:import-node document (dom:document-element node) t)))
|
|
(dom:append-child document clone)))
|
|
document))
|
|
|
|
|
|
;;; Erweiterung
|
|
|
|
(defun create-document (&optional document-element)
|
|
;; Um ein neues Dokumentenobject zu erzeugen, parsen wir einfach ein
|
|
;; Dummydokument.
|
|
(let* ((handler (make-dom-builder))
|
|
(cxml::*ctx* (cxml::make-context :handler handler))
|
|
(result
|
|
(progn
|
|
(sax:start-document handler)
|
|
(sax:end-document handler))))
|
|
(when document-element
|
|
(dom:append-child result (dom:import-node result document-element t)))
|
|
result))
|