/* minidom.js -- Minimum DOM in JavaScript * by M.Hiyama (hiyama{at}chimaira{dot}org) 2005 * URL: http://www.chimaira.org/ */ /* -------------------------------------------------- * debugging aid */ /* Caution: * Web browser's 'print' function invokes Print Dialog. */ if (this.window && this.window.navigator) { this._print = alert; } else { this._print = print; // for Rhino } function _Assert(cond, msg_) { if (!msg_) msg_ = ""; if (!cond) { var s = "***** Assertion failed! *****"; if (msg_) s += "\n* " + msg_; _print(s); throw Error("Assertion failed! " + msg_); } } function debugOut(msg) { var s = "*********\n* "; s += msg; s += "\n*********"; _print(s); } /* ================================================== * * Minimum DOM in JavaScript by M.Hiyama * * ================================================== */ /* -------------------------------------------------- * XmlUtil */ var XmlUtil = {}; XmlUtil.isXmlName = function(str) { // it's so awful! return (typeof str == 'string'); }; /* -------------------------------------------------- * DOMImplementaion (MiniDOM) @W3C */ var MiniDOM = {}; MiniDOM.version = "0.1"; /* * debugging spport */ MiniDOM._debug = true; MiniDOM.debug = function(flag) { // flag:boolean // return:boolean | void if (typeof flag == 'undefined') return MiniDOM._debug; MiniDOM._debug = flag; }; MiniDOM._ignoreWrongDocument = true; MiniDOM.ignoreWrongDocument = function(flag) { // flag:boolean // return:boolean | void if (typeof flag == 'undefined') return MiniDOM._ignoreWrongDocument; // else if (!MiniDOM.debug() && flag) { throw domException(DOMException.MINIDOM_YET_ANOTHER_ERR, "cannot ignore wrong document."); } MiniDOM._ignoreWrongDocument = flag; }; MiniDOM.debugOut = function(msg) { // msg:string // return:void if (MiniDOM._debug) debugOut(msg); }; /* * stringizer selection support */ MiniDOM._stringizer = {}; MiniDOM.registerStringizer = function(name, nodeClass, fun) { // name:string // nodeClass:string // fun:function // return:void // throw:Error switch(nodeClass) { case "element": case "attr": case "text": case "document": case "documentFragment": break; default: throw domException(DOMException.MINIDOM_YET_ANOTHER_ERR, "Unsupported or illegal node class: " + nodeClass); } if (typeof MiniDOM._stringizer[name] == 'undefined') { MiniDOM._stringizer[name] = {}; } // register MiniDOM._stringizer[name][nodeClass] = fun; }; MiniDOM._useStringizer00 = function(name, nodeClass) { // name:string // nodeClass:string // return:boolean // success or failure var ctor; switch(nodeClass) { case "element": ctor = Element; break; case "attr": ctor = Attr; break; case "text": ctor = Text; break; case "document": ctor = Document; break; case "documentFragment": ctor = DocumentFragment; break; default: throw domException(DOMException.MINIDOM_YET_ANOTHER_ERR, "Unsupported or illegal node class: " + nodeClass); } var fun = null; if (MiniDOM._stringizer[name] && MiniDOM._stringizer[name][nodeClass]) { fun = MiniDOM._stringizer[name][nodeClass]; } if (fun && typeof fun == 'function') { ctor.prototype.toString = fun; return true; } else { return false; } }; MiniDOM.useStringizer = function(name, nodeClass) { // name:string // nodeClass:string | [string*] // return:number if (typeof nodeClass == 'string') { var r = MiniDOM._useStringizer00(name, nodeClass); if (r) { MiniDOM.debugOut("use " + name + "." + nodeClass); return 1; } else { MiniDOM.debugOut("cannot use " + name + "." + nodeClass); return 0; } } // else if (nodeClass !== null && nodeClass.constructor == Array) { var nOk = 0; for (var i = 0; i < nodeClass.length; i++) { if (MiniDOM._useStringizer00(name, nodeClass[i])) { nOk++; MiniDOM.debugOut("use " + name + "." + nodeClass[i]); } } if (nOk === 0) { MiniDOM.debugOut("no stringizer to use"); } return nOk; } // else MiniDOM.debugOut("illegal call"); return 0; }; MiniDOM.listStringizer = function(name_) { var result = []; var stock = MiniDOM._stringizer; if (typeof name_ != 'undefined') { for (var nodeClass in stock[name_]) { result.push(name_ + "." + nodeClass); } } else { for (var name in stock) { for (var nodeClass in stock[name]) { result.push(name + "." + nodeClass); } } } return result; }; /* * overridden methods */ MiniDOM.toString = function () { return "[MiniDOM version=" + MiniDOM.version + "]"; }; /* * DOM API methods of DOMImplementation@W3C */ /* createDocument */ MiniDOM.createDocument = function(nsUri, qname, docType) { if (nsUri !== "" || qname !== "" || docType !== null) { throw domException(DOMException.NOT_SUPPORTED_ERR); } return new Document(); }; /* -------------------------------------------------- * DOMException @W3C */ var DOMException = {}; DOMException.HIERARCHY_REQUEST_ERR = 3; DOMException.WRONG_DOCUMENT_ERR = 4; DOMException.INVALID_CHARACTER_ERR = 5; DOMException.NO_DATA_ALLOWED_ERR = 6; DOMException.NOT_FOUND_ERR = 8; DOMException.NOT_SUPPORTED_ERR = 9; DOMException.MINIDOM_UNKNOWN_ERR = 20; DOMException.MINIDOM_IMPLEMENTATION_ERR = 21; DOMException.MINIDOM_YET_ANOTHER_ERR = 22; DOMException.message = []; DOMException.message[DOMException.HIERARCHY_REQUEST_ERR] = "Any node is inserted somewhere it should not belong."; DOMException.message[DOMException.WRONG_DOCUMENT_ERR] = "A node is used in a different document " + "than the one that created it (that doesn't support it)."; DOMException.message[DOMException.INVALID_CHARACTER_ERR] = "An invalid or illegal character is specified, " + "such as in a name."; DOMException.message[DOMException.NO_DATA_ALLOWED_ERR] = "Data is specified for a node which does not support data."; DOMException.message[DOMException.NOT_FOUND_ERR] = "An attempt is made to reference " + "a node in a context where it does not exist."; DOMException.message[DOMException.NOT_SUPPORTED_ERR] = "The implementation does not support " + "the requested type of object or operation. "; DOMException.message[DOMException.MINIDOM_UNKNOWN_ERR] = "Unexpected situation, the implementation cannot handle it."; DOMException.message[DOMException.MINIDOM_IMPLEMENTATION_ERR] = "The implementation has some bugs " + "and you have encountered one of them."; DOMException.message[DOMException.MINIDOM_YET_ANOTHER_ERR] = "DOM related error :"; function domException(code, info_) { var msg0 = DOMException.message[code]; var msg1 = code + ":" + msg0; var msg; if (typeof info_ == 'undefined') { msg = msg1; } else if (typeof info_ == 'object' && info_ !== null) { if (info_.constructor == Array) { msg = msg1 + ":[" + info_ + "]"; // unnested style } else { msg = msg1 + ":{" + info_ + "}"; } } else { // null, number, boolean, string msg = msg1 + ":" + info_; } MiniDOM.debugOut(msg); var err = new Error(msg); err.domException = true; err.code = code; return err; } /* -------------------------------------------------- * Node @W3C */ Node.ELEMENT_NODE = 1; Node.ATTRIBUTE_NODE = 2; Node.TEXT_NODE = 3; Node.DOCUMENT_NODE = 9; Node.DOCUMENT_FRAGMENT_NODE = 11; function Node(type, doc_) { if (typeof doc_ == 'undefined') doc_ = null; this._root = this; this.ownerDocument = doc_; this.parentNode = null; this.nodeType = type; switch(type) { case Node.ELEMENT_NODE: case Node.ATTRIBUTE_NODE: this.nodeName = null; break; case Node.TEXT_NODE: this.nodeName = "#text"; break; case Node.DOCUMENT_NODE: this.nodeName = "#document"; break; case Node.DOCUMENT_FRAGMENT_NODE: this.nodeName = "#document-fragment"; break; default: var code = DOMException.NOT_SUPPORTED_ERR; throw domException(code, " nodeType=" + type); } } Node.prototype._isAncestorOrSelfOf = function(node) { // node:Node // return:boolean for (var n = node; n !== null; n = n.parentNode) { if (n == this) return true; } return false; }; Node.prototype._insertBefore = function(newNode, refNode) { // newNode:Node{not null} // refNode:Node // my be null // return:Node _Assert(newNode !== null); var type = newNode.nodeType; if (type == Node.DOCUMENT || type == Node.ATTRIBUTE_NODE) { throw domException(DOMException.HIERARCHY_REQUEST_ERR); } if (newNode.ownerDocument != this.ownerDocument) { MiniDOM.debugOut(newNode.ownerDocument + "!=" + this.ownerDocument); if (!MiniDOM.debug() || !MiniDOM.ignoreWrongDocument()) { throw domException(DOMException.WRONG_DOCUMENT_ERR); } } // if the node to insert (newNode) is one of this node's ancestors. if (newNode._isAncestorOrSelfOf(this)) { throw domException(DOMException.HIERARCHY_REQUEST_ERR); } // find the position index var children = this.childNodes; var len = children.length; var i; if (refNode === null) { i = len; } else { for (i = 0; i < len; i++) { if (children[i] == refNode) break; } if (i == len) throw domException(DOMException.NOT_FOUND_ERR); } // remove first if needed if (newNode._root != newNode) { // already in a tree! MiniDOM.debugOut("removeChild will be called within _insertBefore"); newNode.parentNode.removeChild(newNode); } if (newNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE) { this._insertNodesAt(newNode.childNodes, i); } else { this._insertNodesAt([newNode], i); } return newNode; }; Node.prototype._insertNodesAt = function(newNodes, pos) { // newNodes: [Node*] // pos:number var size = newNodes.length; if (size <= 0) return; var children = this.childNodes; var len = children.length; // splice them for (var i = 0; i < size; i++) { children.splice(pos + i, 0, newNodes[i]); } // parentNode, _root for (var j = pos; j < pos + size; j++) { children[j].parentNode = this; children[j]._root = this._root; } // sibling links, firstChild, lastChild var eldest = newNodes[0]; var youngest = newNodes[size - 1]; if (pos === 0) { eldest.previousSibling = null; this.firstChild = children[0]; } else { eldest.previousSibling = children[pos - 1]; children[pos - 1].nextSibling = eldest; } if (pos == len) { youngest.nextSibling = null; this.lastChild = children[pos]; } else { youngest.nextSibling = children[pos + size]; children[pos + size].previousSibling = youngest; } // id if (this._root == this.ownerDocument) { // this node is in a documnt-tree for (j = pos; j < pos + size; j++) { if (children[j].nodeType == Node.ELEMENT_NODE) { this.ownerDocument.registerId(children[j]); } } } }; Node.prototype._doRemoveChild = function(oldNode) { // oldNode:Node // return:void var children = this.childNodes; var len = children.length; for (var i = 0; i < len; i++) { if (children[i] == oldNode) break; } if (i == len) throw domException(DOMException.NOT_FOUND_ERR); children.splice(i, 1); _Assert(0 <= i && i < len, "_doRemoveChild"); if (len == 1) { // Now, children.length == 0 this.firstChild = null; this.lastChild = null; } else { // first/last child, sibling links if (i === 0) { MiniDOM.debugOut("i = " + i + " children[0]= " + children[0]); children[0].previousSibling = null; this.firstChild = children[0]; } else if (i == len -1) { // (induced: len >= 2) children[len - 2].nextSibling = null; this.lastChild = children[len - 2]; } else { // 0 < i && i < len - 1 (indeced: len >= 2) children[i -1].nextSibling = children[i]; children[i].previousSibling = children[i -1]; } } // ID map if (this._root == this.ownerDocument && oldNode.nodeType == Node.ELEMENT_NODE) { this.ownerDocument.unregisterId(oldNode); } // let the node be free! oldNode._root = oldNode; oldNode.parentNode = null; oldNode.previousSibling = null; oldNode.nextSibling = null; }; /* Node.prototype._getElementsByTagNameRec = function(name, result) { if (this.tagName == name) { result.push(this); } var children = this.childNodes; if (!children) return result; for (var i = 0; i < children.length; i++) { if (children[i].nodeType == Node.ELEMENT_NODE) { children[i]._getElementByTagNameRec(name, result); } } return result; } */ /* -------------------------------------------------- * Element @W3C */ function Element(name, doc_) { // call super Node.apply(this, [Node.ELEMENT_NODE, doc_]); this.nodeName = name; this.tagName = name; this.attributes = []; this.childNodes = []; this.firstChild = null; this.lastChild = null; this.previousSibling = null; this.nextSibling = null; } Element.prototype.__proto__ = Node.prototype; /* * overridden methods */ Element.prototype.toString = function() { var str = "<" + this.tagName; var attrs = this.attributes; for (var i = 0; i < attrs.length; i++) { str += (" " + attrs[i].toString()); } str += "\n>"; var children = this.childNodes; for (var i = 0; i < children.length; i++) { str += children[i].toString(); } str += ""; return str; }; /* * DOM API methods of Element@W3C */ /* insertBefore */ Element.prototype.insertBefore = function(newNode, refNode) { return this._insertBefore(newNode, refNode); }; /* appendChild */ Element.prototype.appendChild = function(newNode) { return this._insertBefore(newNode, null); }; /* removeChild */ Element.prototype.removeChild = function(oldNode) { this._doRemoveChild(oldNode); return oldNode; }; /* hasChildNodes */ Element.prototype.hasChildNodes = function() { return this.childNodes.length !== 0; }; /* hasAttributes */ Element.prototype.hasAttributes = function() { return this.attributes.length !== 0; }; /* getAttribute */ Element.prototype.getAttribute = function(name) { var attr = this.attributes[name]; if (!attr) { return ""; } else { return attr.value; } }; /* setAttribute */ Element.prototype.setAttribute = function(name, value) { // name:string // value:string // return:void if (!XmlUtil.isXmlName(name)) { throw domException(DOMException.INVALID_CHARACTER_ERR); } var attrs = this.attributes; var attr = null; for (var i = 0; i < attrs.length; i++) { if (attrs[i].name == name) { attr = attrs[i]; break; } } if (!attr) { attr = new Attr(name, this.ownerDocument); attr.ownerElement = this; attrs.push(attr); attrs[name] = attr; } attr.nodeValue = value; attr.value = value; if (this._root == this.ownerDocument) { this.ownerDocument.registerId(this); } }; /* removeAttribute */ Element.prototype.removeAttribute = function(name) { // return:void var attrs = this.attributes; var attr = null; for (var i = 0; i < attrs.length; i++) { if (attrs[i].name == name) { attr = attrs[i]; break; } } if (attr) { attrs.splice(i, 1); attrs[name] = (void 0); delete attrs[name]; // might be ignored } }; /* hasAttribute */ Element.prototype.hasAttribute = function(name) { // return:boolean return (this.attributes[name] ? true : false); }; /* -------------------------------------------------- * Text @W3C */ function Text(data, doc_) { // call super Node.apply(this, [Node.TEXT_NODE, doc_]); this.data = data; this.previousSibling = null; this.nextSibling = null; } Text.prototype.__proto__ = Node.prototype; Text.prototype.toString = function() { var s = this.data; s = s.replace(/&/g, "&"); s = s.replace(/