You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1802 lines
55 KiB
1802 lines
55 KiB
var conventions = require("./conventions"); |
|
|
|
var find = conventions.find; |
|
var NAMESPACE = conventions.NAMESPACE; |
|
|
|
/** |
|
* A prerequisite for `[].filter`, to drop elements that are empty |
|
* @param {string} input |
|
* @returns {boolean} |
|
*/ |
|
function notEmptyString (input) { |
|
return input !== '' |
|
} |
|
/** |
|
* @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace |
|
* @see https://infra.spec.whatwg.org/#ascii-whitespace |
|
* |
|
* @param {string} input |
|
* @returns {string[]} (can be empty) |
|
*/ |
|
function splitOnASCIIWhitespace(input) { |
|
// U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE |
|
return input ? input.split(/[\t\n\f\r ]+/).filter(notEmptyString) : [] |
|
} |
|
|
|
/** |
|
* Adds element as a key to current if it is not already present. |
|
* |
|
* @param {Record<string, boolean | undefined>} current |
|
* @param {string} element |
|
* @returns {Record<string, boolean | undefined>} |
|
*/ |
|
function orderedSetReducer (current, element) { |
|
if (!current.hasOwnProperty(element)) { |
|
current[element] = true; |
|
} |
|
return current; |
|
} |
|
|
|
/** |
|
* @see https://infra.spec.whatwg.org/#ordered-set |
|
* @param {string} input |
|
* @returns {string[]} |
|
*/ |
|
function toOrderedSet(input) { |
|
if (!input) return []; |
|
var list = splitOnASCIIWhitespace(input); |
|
return Object.keys(list.reduce(orderedSetReducer, {})) |
|
} |
|
|
|
/** |
|
* Uses `list.indexOf` to implement something like `Array.prototype.includes`, |
|
* which we can not rely on being available. |
|
* |
|
* @param {any[]} list |
|
* @returns {function(any): boolean} |
|
*/ |
|
function arrayIncludes (list) { |
|
return function(element) { |
|
return list && list.indexOf(element) !== -1; |
|
} |
|
} |
|
|
|
function copy(src,dest){ |
|
for(var p in src){ |
|
if (Object.prototype.hasOwnProperty.call(src, p)) { |
|
dest[p] = src[p]; |
|
} |
|
} |
|
} |
|
|
|
/** |
|
^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));? |
|
^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));? |
|
*/ |
|
function _extends(Class,Super){ |
|
var pt = Class.prototype; |
|
if(!(pt instanceof Super)){ |
|
function t(){}; |
|
t.prototype = Super.prototype; |
|
t = new t(); |
|
copy(pt,t); |
|
Class.prototype = pt = t; |
|
} |
|
if(pt.constructor != Class){ |
|
if(typeof Class != 'function'){ |
|
console.error("unknown Class:"+Class) |
|
} |
|
pt.constructor = Class |
|
} |
|
} |
|
|
|
// Node Types |
|
var NodeType = {} |
|
var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1; |
|
var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2; |
|
var TEXT_NODE = NodeType.TEXT_NODE = 3; |
|
var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4; |
|
var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5; |
|
var ENTITY_NODE = NodeType.ENTITY_NODE = 6; |
|
var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7; |
|
var COMMENT_NODE = NodeType.COMMENT_NODE = 8; |
|
var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9; |
|
var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10; |
|
var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11; |
|
var NOTATION_NODE = NodeType.NOTATION_NODE = 12; |
|
|
|
// ExceptionCode |
|
var ExceptionCode = {} |
|
var ExceptionMessage = {}; |
|
var INDEX_SIZE_ERR = ExceptionCode.INDEX_SIZE_ERR = ((ExceptionMessage[1]="Index size error"),1); |
|
var DOMSTRING_SIZE_ERR = ExceptionCode.DOMSTRING_SIZE_ERR = ((ExceptionMessage[2]="DOMString size error"),2); |
|
var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = ((ExceptionMessage[3]="Hierarchy request error"),3); |
|
var WRONG_DOCUMENT_ERR = ExceptionCode.WRONG_DOCUMENT_ERR = ((ExceptionMessage[4]="Wrong document"),4); |
|
var INVALID_CHARACTER_ERR = ExceptionCode.INVALID_CHARACTER_ERR = ((ExceptionMessage[5]="Invalid character"),5); |
|
var NO_DATA_ALLOWED_ERR = ExceptionCode.NO_DATA_ALLOWED_ERR = ((ExceptionMessage[6]="No data allowed"),6); |
|
var NO_MODIFICATION_ALLOWED_ERR = ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = ((ExceptionMessage[7]="No modification allowed"),7); |
|
var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = ((ExceptionMessage[8]="Not found"),8); |
|
var NOT_SUPPORTED_ERR = ExceptionCode.NOT_SUPPORTED_ERR = ((ExceptionMessage[9]="Not supported"),9); |
|
var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = ((ExceptionMessage[10]="Attribute in use"),10); |
|
//level2 |
|
var INVALID_STATE_ERR = ExceptionCode.INVALID_STATE_ERR = ((ExceptionMessage[11]="Invalid state"),11); |
|
var SYNTAX_ERR = ExceptionCode.SYNTAX_ERR = ((ExceptionMessage[12]="Syntax error"),12); |
|
var INVALID_MODIFICATION_ERR = ExceptionCode.INVALID_MODIFICATION_ERR = ((ExceptionMessage[13]="Invalid modification"),13); |
|
var NAMESPACE_ERR = ExceptionCode.NAMESPACE_ERR = ((ExceptionMessage[14]="Invalid namespace"),14); |
|
var INVALID_ACCESS_ERR = ExceptionCode.INVALID_ACCESS_ERR = ((ExceptionMessage[15]="Invalid access"),15); |
|
|
|
/** |
|
* DOM Level 2 |
|
* Object DOMException |
|
* @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html |
|
* @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html |
|
*/ |
|
function DOMException(code, message) { |
|
if(message instanceof Error){ |
|
var error = message; |
|
}else{ |
|
error = this; |
|
Error.call(this, ExceptionMessage[code]); |
|
this.message = ExceptionMessage[code]; |
|
if(Error.captureStackTrace) Error.captureStackTrace(this, DOMException); |
|
} |
|
error.code = code; |
|
if(message) this.message = this.message + ": " + message; |
|
return error; |
|
}; |
|
DOMException.prototype = Error.prototype; |
|
copy(ExceptionCode,DOMException) |
|
|
|
/** |
|
* @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177 |
|
* The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeList objects in the DOM are live. |
|
* The items in the NodeList are accessible via an integral index, starting from 0. |
|
*/ |
|
function NodeList() { |
|
}; |
|
NodeList.prototype = { |
|
/** |
|
* The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive. |
|
* @standard level1 |
|
*/ |
|
length:0, |
|
/** |
|
* Returns the indexth item in the collection. If index is greater than or equal to the number of nodes in the list, this returns null. |
|
* @standard level1 |
|
* @param index unsigned long |
|
* Index into the collection. |
|
* @return Node |
|
* The node at the indexth position in the NodeList, or null if that is not a valid index. |
|
*/ |
|
item: function(index) { |
|
return this[index] || null; |
|
}, |
|
toString:function(isHTML,nodeFilter){ |
|
for(var buf = [], i = 0;i<this.length;i++){ |
|
serializeToString(this[i],buf,isHTML,nodeFilter); |
|
} |
|
return buf.join(''); |
|
}, |
|
/** |
|
* @private |
|
* @param {function (Node):boolean} predicate |
|
* @returns {Node[]} |
|
*/ |
|
filter: function (predicate) { |
|
return Array.prototype.filter.call(this, predicate); |
|
}, |
|
/** |
|
* @private |
|
* @param {Node} item |
|
* @returns {number} |
|
*/ |
|
indexOf: function (item) { |
|
return Array.prototype.indexOf.call(this, item); |
|
}, |
|
}; |
|
|
|
function LiveNodeList(node,refresh){ |
|
this._node = node; |
|
this._refresh = refresh |
|
_updateLiveList(this); |
|
} |
|
function _updateLiveList(list){ |
|
var inc = list._node._inc || list._node.ownerDocument._inc; |
|
if(list._inc != inc){ |
|
var ls = list._refresh(list._node); |
|
//console.log(ls.length) |
|
__set__(list,'length',ls.length); |
|
copy(ls,list); |
|
list._inc = inc; |
|
} |
|
} |
|
LiveNodeList.prototype.item = function(i){ |
|
_updateLiveList(this); |
|
return this[i]; |
|
} |
|
|
|
_extends(LiveNodeList,NodeList); |
|
|
|
/** |
|
* Objects implementing the NamedNodeMap interface are used |
|
* to represent collections of nodes that can be accessed by name. |
|
* Note that NamedNodeMap does not inherit from NodeList; |
|
* NamedNodeMaps are not maintained in any particular order. |
|
* Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index, |
|
* but this is simply to allow convenient enumeration of the contents of a NamedNodeMap, |
|
* and does not imply that the DOM specifies an order to these Nodes. |
|
* NamedNodeMap objects in the DOM are live. |
|
* used for attributes or DocumentType entities |
|
*/ |
|
function NamedNodeMap() { |
|
}; |
|
|
|
function _findNodeIndex(list,node){ |
|
var i = list.length; |
|
while(i--){ |
|
if(list[i] === node){return i} |
|
} |
|
} |
|
|
|
function _addNamedNode(el,list,newAttr,oldAttr){ |
|
if(oldAttr){ |
|
list[_findNodeIndex(list,oldAttr)] = newAttr; |
|
}else{ |
|
list[list.length++] = newAttr; |
|
} |
|
if(el){ |
|
newAttr.ownerElement = el; |
|
var doc = el.ownerDocument; |
|
if(doc){ |
|
oldAttr && _onRemoveAttribute(doc,el,oldAttr); |
|
_onAddAttribute(doc,el,newAttr); |
|
} |
|
} |
|
} |
|
function _removeNamedNode(el,list,attr){ |
|
//console.log('remove attr:'+attr) |
|
var i = _findNodeIndex(list,attr); |
|
if(i>=0){ |
|
var lastIndex = list.length-1 |
|
while(i<lastIndex){ |
|
list[i] = list[++i] |
|
} |
|
list.length = lastIndex; |
|
if(el){ |
|
var doc = el.ownerDocument; |
|
if(doc){ |
|
_onRemoveAttribute(doc,el,attr); |
|
attr.ownerElement = null; |
|
} |
|
} |
|
}else{ |
|
throw new DOMException(NOT_FOUND_ERR,new Error(el.tagName+'@'+attr)) |
|
} |
|
} |
|
NamedNodeMap.prototype = { |
|
length:0, |
|
item:NodeList.prototype.item, |
|
getNamedItem: function(key) { |
|
// if(key.indexOf(':')>0 || key == 'xmlns'){ |
|
// return null; |
|
// } |
|
//console.log() |
|
var i = this.length; |
|
while(i--){ |
|
var attr = this[i]; |
|
//console.log(attr.nodeName,key) |
|
if(attr.nodeName == key){ |
|
return attr; |
|
} |
|
} |
|
}, |
|
setNamedItem: function(attr) { |
|
var el = attr.ownerElement; |
|
if(el && el!=this._ownerElement){ |
|
throw new DOMException(INUSE_ATTRIBUTE_ERR); |
|
} |
|
var oldAttr = this.getNamedItem(attr.nodeName); |
|
_addNamedNode(this._ownerElement,this,attr,oldAttr); |
|
return oldAttr; |
|
}, |
|
/* returns Node */ |
|
setNamedItemNS: function(attr) {// raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR |
|
var el = attr.ownerElement, oldAttr; |
|
if(el && el!=this._ownerElement){ |
|
throw new DOMException(INUSE_ATTRIBUTE_ERR); |
|
} |
|
oldAttr = this.getNamedItemNS(attr.namespaceURI,attr.localName); |
|
_addNamedNode(this._ownerElement,this,attr,oldAttr); |
|
return oldAttr; |
|
}, |
|
|
|
/* returns Node */ |
|
removeNamedItem: function(key) { |
|
var attr = this.getNamedItem(key); |
|
_removeNamedNode(this._ownerElement,this,attr); |
|
return attr; |
|
|
|
|
|
},// raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR |
|
|
|
//for level2 |
|
removeNamedItemNS:function(namespaceURI,localName){ |
|
var attr = this.getNamedItemNS(namespaceURI,localName); |
|
_removeNamedNode(this._ownerElement,this,attr); |
|
return attr; |
|
}, |
|
getNamedItemNS: function(namespaceURI, localName) { |
|
var i = this.length; |
|
while(i--){ |
|
var node = this[i]; |
|
if(node.localName == localName && node.namespaceURI == namespaceURI){ |
|
return node; |
|
} |
|
} |
|
return null; |
|
} |
|
}; |
|
|
|
/** |
|
* The DOMImplementation interface represents an object providing methods |
|
* which are not dependent on any particular document. |
|
* Such an object is returned by the `Document.implementation` property. |
|
* |
|
* __The individual methods describe the differences compared to the specs.__ |
|
* |
|
* @constructor |
|
* |
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation MDN |
|
* @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 DOM Level 1 Core (Initial) |
|
* @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-102161490 DOM Level 2 Core |
|
* @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-102161490 DOM Level 3 Core |
|
* @see https://dom.spec.whatwg.org/#domimplementation DOM Living Standard |
|
*/ |
|
function DOMImplementation() { |
|
} |
|
|
|
DOMImplementation.prototype = { |
|
/** |
|
* The DOMImplementation.hasFeature() method returns a Boolean flag indicating if a given feature is supported. |
|
* The different implementations fairly diverged in what kind of features were reported. |
|
* The latest version of the spec settled to force this method to always return true, where the functionality was accurate and in use. |
|
* |
|
* @deprecated It is deprecated and modern browsers return true in all cases. |
|
* |
|
* @param {string} feature |
|
* @param {string} [version] |
|
* @returns {boolean} always true |
|
* |
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature MDN |
|
* @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-5CED94D7 DOM Level 1 Core |
|
* @see https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature DOM Living Standard |
|
*/ |
|
hasFeature: function(feature, version) { |
|
return true; |
|
}, |
|
/** |
|
* Creates an XML Document object of the specified type with its document element. |
|
* |
|
* __It behaves slightly different from the description in the living standard__: |
|
* - There is no interface/class `XMLDocument`, it returns a `Document` instance. |
|
* - `contentType`, `encoding`, `mode`, `origin`, `url` fields are currently not declared. |
|
* - this implementation is not validating names or qualified names |
|
* (when parsing XML strings, the SAX parser takes care of that) |
|
* |
|
* @param {string|null} namespaceURI |
|
* @param {string} qualifiedName |
|
* @param {DocumentType=null} doctype |
|
* @returns {Document} |
|
* |
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN |
|
* @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM Level 2 Core (initial) |
|
* @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument DOM Level 2 Core |
|
* |
|
* @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract |
|
* @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names |
|
* @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names |
|
*/ |
|
createDocument: function(namespaceURI, qualifiedName, doctype){ |
|
var doc = new Document(); |
|
doc.implementation = this; |
|
doc.childNodes = new NodeList(); |
|
doc.doctype = doctype || null; |
|
if (doctype){ |
|
doc.appendChild(doctype); |
|
} |
|
if (qualifiedName){ |
|
var root = doc.createElementNS(namespaceURI, qualifiedName); |
|
doc.appendChild(root); |
|
} |
|
return doc; |
|
}, |
|
/** |
|
* Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`. |
|
* |
|
* __This behavior is slightly different from the in the specs__: |
|
* - this implementation is not validating names or qualified names |
|
* (when parsing XML strings, the SAX parser takes care of that) |
|
* |
|
* @param {string} qualifiedName |
|
* @param {string} [publicId] |
|
* @param {string} [systemId] |
|
* @returns {DocumentType} which can either be used with `DOMImplementation.createDocument` upon document creation |
|
* or can be put into the document via methods like `Node.insertBefore()` or `Node.replaceChild()` |
|
* |
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType MDN |
|
* @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM Level 2 Core |
|
* @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living Standard |
|
* |
|
* @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract |
|
* @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names |
|
* @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names |
|
*/ |
|
createDocumentType: function(qualifiedName, publicId, systemId){ |
|
var node = new DocumentType(); |
|
node.name = qualifiedName; |
|
node.nodeName = qualifiedName; |
|
node.publicId = publicId || ''; |
|
node.systemId = systemId || ''; |
|
|
|
return node; |
|
} |
|
}; |
|
|
|
|
|
/** |
|
* @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 |
|
*/ |
|
|
|
function Node() { |
|
}; |
|
|
|
Node.prototype = { |
|
firstChild : null, |
|
lastChild : null, |
|
previousSibling : null, |
|
nextSibling : null, |
|
attributes : null, |
|
parentNode : null, |
|
childNodes : null, |
|
ownerDocument : null, |
|
nodeValue : null, |
|
namespaceURI : null, |
|
prefix : null, |
|
localName : null, |
|
// Modified in DOM Level 2: |
|
insertBefore:function(newChild, refChild){//raises |
|
return _insertBefore(this,newChild,refChild); |
|
}, |
|
replaceChild:function(newChild, oldChild){//raises |
|
_insertBefore(this, newChild,oldChild, assertPreReplacementValidityInDocument); |
|
if(oldChild){ |
|
this.removeChild(oldChild); |
|
} |
|
}, |
|
removeChild:function(oldChild){ |
|
return _removeChild(this,oldChild); |
|
}, |
|
appendChild:function(newChild){ |
|
return this.insertBefore(newChild,null); |
|
}, |
|
hasChildNodes:function(){ |
|
return this.firstChild != null; |
|
}, |
|
cloneNode:function(deep){ |
|
return cloneNode(this.ownerDocument||this,this,deep); |
|
}, |
|
// Modified in DOM Level 2: |
|
normalize:function(){ |
|
var child = this.firstChild; |
|
while(child){ |
|
var next = child.nextSibling; |
|
if(next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE){ |
|
this.removeChild(next); |
|
child.appendData(next.data); |
|
}else{ |
|
child.normalize(); |
|
child = next; |
|
} |
|
} |
|
}, |
|
// Introduced in DOM Level 2: |
|
isSupported:function(feature, version){ |
|
return this.ownerDocument.implementation.hasFeature(feature,version); |
|
}, |
|
// Introduced in DOM Level 2: |
|
hasAttributes:function(){ |
|
return this.attributes.length>0; |
|
}, |
|
/** |
|
* Look up the prefix associated to the given namespace URI, starting from this node. |
|
* **The default namespace declarations are ignored by this method.** |
|
* See Namespace Prefix Lookup for details on the algorithm used by this method. |
|
* |
|
* _Note: The implementation seems to be incomplete when compared to the algorithm described in the specs._ |
|
* |
|
* @param {string | null} namespaceURI |
|
* @returns {string | null} |
|
* @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespacePrefix |
|
* @see https://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespacePrefixAlgo |
|
* @see https://dom.spec.whatwg.org/#dom-node-lookupprefix |
|
* @see https://github.com/xmldom/xmldom/issues/322 |
|
*/ |
|
lookupPrefix:function(namespaceURI){ |
|
var el = this; |
|
while(el){ |
|
var map = el._nsMap; |
|
//console.dir(map) |
|
if(map){ |
|
for(var n in map){ |
|
if (Object.prototype.hasOwnProperty.call(map, n) && map[n] === namespaceURI) { |
|
return n; |
|
} |
|
} |
|
} |
|
el = el.nodeType == ATTRIBUTE_NODE?el.ownerDocument : el.parentNode; |
|
} |
|
return null; |
|
}, |
|
// Introduced in DOM Level 3: |
|
lookupNamespaceURI:function(prefix){ |
|
var el = this; |
|
while(el){ |
|
var map = el._nsMap; |
|
//console.dir(map) |
|
if(map){ |
|
if(prefix in map){ |
|
if(Object.prototype.hasOwnProperty.call(map, prefix)){ |
|
return map[prefix] ; |
|
} |
|
} |
|
} |
|
el = el.nodeType == ATTRIBUTE_NODE?el.ownerDocument : el.parentNode; |
|
} |
|
return null; |
|
}, |
|
// Introduced in DOM Level 3: |
|
isDefaultNamespace:function(namespaceURI){ |
|
var prefix = this.lookupPrefix(namespaceURI); |
|
return prefix == null; |
|
} |
|
}; |
|
|
|
|
|
function _xmlEncoder(c){ |
|
return c == '<' && '<' || |
|
c == '>' && '>' || |
|
c == '&' && '&' || |
|
c == '"' && '"' || |
|
'&#'+c.charCodeAt()+';' |
|
} |
|
|
|
|
|
copy(NodeType,Node); |
|
copy(NodeType,Node.prototype); |
|
|
|
/** |
|
* @param callback return true for continue,false for break |
|
* @return boolean true: break visit; |
|
*/ |
|
function _visitNode(node,callback){ |
|
if(callback(node)){ |
|
return true; |
|
} |
|
if(node = node.firstChild){ |
|
do{ |
|
if(_visitNode(node,callback)){return true} |
|
}while(node=node.nextSibling) |
|
} |
|
} |
|
|
|
|
|
|
|
function Document(){ |
|
this.ownerDocument = this; |
|
} |
|
|
|
function _onAddAttribute(doc,el,newAttr){ |
|
doc && doc._inc++; |
|
var ns = newAttr.namespaceURI ; |
|
if(ns === NAMESPACE.XMLNS){ |
|
//update namespace |
|
el._nsMap[newAttr.prefix?newAttr.localName:''] = newAttr.value |
|
} |
|
} |
|
|
|
function _onRemoveAttribute(doc,el,newAttr,remove){ |
|
doc && doc._inc++; |
|
var ns = newAttr.namespaceURI ; |
|
if(ns === NAMESPACE.XMLNS){ |
|
//update namespace |
|
delete el._nsMap[newAttr.prefix?newAttr.localName:''] |
|
} |
|
} |
|
|
|
function _onUpdateChild(doc,el,newChild){ |
|
if(doc && doc._inc){ |
|
doc._inc++; |
|
//update childNodes |
|
var cs = el.childNodes; |
|
if(newChild){ |
|
cs[cs.length++] = newChild; |
|
}else{ |
|
//console.log(1) |
|
var child = el.firstChild; |
|
var i = 0; |
|
while(child){ |
|
cs[i++] = child; |
|
child =child.nextSibling; |
|
} |
|
cs.length = i; |
|
delete cs[cs.length]; |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* attributes; |
|
* children; |
|
* |
|
* writeable properties: |
|
* nodeValue,Attr:value,CharacterData:data |
|
* prefix |
|
*/ |
|
function _removeChild(parentNode,child){ |
|
var previous = child.previousSibling; |
|
var next = child.nextSibling; |
|
if(previous){ |
|
previous.nextSibling = next; |
|
}else{ |
|
parentNode.firstChild = next |
|
} |
|
if(next){ |
|
next.previousSibling = previous; |
|
}else{ |
|
parentNode.lastChild = previous; |
|
} |
|
child.parentNode = null; |
|
child.previousSibling = null; |
|
child.nextSibling = null; |
|
_onUpdateChild(parentNode.ownerDocument,parentNode); |
|
return child; |
|
} |
|
|
|
/** |
|
* Returns `true` if `node` can be a parent for insertion. |
|
* @param {Node} node |
|
* @returns {boolean} |
|
*/ |
|
function hasValidParentNodeType(node) { |
|
return ( |
|
node && |
|
(node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.ELEMENT_NODE) |
|
); |
|
} |
|
|
|
/** |
|
* Returns `true` if `node` can be inserted according to it's `nodeType`. |
|
* @param {Node} node |
|
* @returns {boolean} |
|
*/ |
|
function hasInsertableNodeType(node) { |
|
return ( |
|
node && |
|
(isElementNode(node) || |
|
isTextNode(node) || |
|
isDocTypeNode(node) || |
|
node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || |
|
node.nodeType === Node.COMMENT_NODE || |
|
node.nodeType === Node.PROCESSING_INSTRUCTION_NODE) |
|
); |
|
} |
|
|
|
/** |
|
* Returns true if `node` is a DOCTYPE node |
|
* @param {Node} node |
|
* @returns {boolean} |
|
*/ |
|
function isDocTypeNode(node) { |
|
return node && node.nodeType === Node.DOCUMENT_TYPE_NODE; |
|
} |
|
|
|
/** |
|
* Returns true if the node is an element |
|
* @param {Node} node |
|
* @returns {boolean} |
|
*/ |
|
function isElementNode(node) { |
|
return node && node.nodeType === Node.ELEMENT_NODE; |
|
} |
|
/** |
|
* Returns true if `node` is a text node |
|
* @param {Node} node |
|
* @returns {boolean} |
|
*/ |
|
function isTextNode(node) { |
|
return node && node.nodeType === Node.TEXT_NODE; |
|
} |
|
|
|
/** |
|
* Check if en element node can be inserted before `child`, or at the end if child is falsy, |
|
* according to the presence and position of a doctype node on the same level. |
|
* |
|
* @param {Document} doc The document node |
|
* @param {Node} child the node that would become the nextSibling if the element would be inserted |
|
* @returns {boolean} `true` if an element can be inserted before child |
|
* @private |
|
* https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity |
|
*/ |
|
function isElementInsertionPossible(doc, child) { |
|
var parentChildNodes = doc.childNodes || []; |
|
if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) { |
|
return false; |
|
} |
|
var docTypeNode = find(parentChildNodes, isDocTypeNode); |
|
return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child)); |
|
} |
|
|
|
/** |
|
* Check if en element node can be inserted before `child`, or at the end if child is falsy, |
|
* according to the presence and position of a doctype node on the same level. |
|
* |
|
* @param {Node} doc The document node |
|
* @param {Node} child the node that would become the nextSibling if the element would be inserted |
|
* @returns {boolean} `true` if an element can be inserted before child |
|
* @private |
|
* https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity |
|
*/ |
|
function isElementReplacementPossible(doc, child) { |
|
var parentChildNodes = doc.childNodes || []; |
|
|
|
function hasElementChildThatIsNotChild(node) { |
|
return isElementNode(node) && node !== child; |
|
} |
|
|
|
if (find(parentChildNodes, hasElementChildThatIsNotChild)) { |
|
return false; |
|
} |
|
var docTypeNode = find(parentChildNodes, isDocTypeNode); |
|
return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child)); |
|
} |
|
|
|
/** |
|
* @private |
|
* Steps 1-5 of the checks before inserting and before replacing a child are the same. |
|
* |
|
* @param {Node} parent the parent node to insert `node` into |
|
* @param {Node} node the node to insert |
|
* @param {Node=} child the node that should become the `nextSibling` of `node` |
|
* @returns {Node} |
|
* @throws DOMException for several node combinations that would create a DOM that is not well-formed. |
|
* @throws DOMException if `child` is provided but is not a child of `parent`. |
|
* @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity |
|
* @see https://dom.spec.whatwg.org/#concept-node-replace |
|
*/ |
|
function assertPreInsertionValidity1to5(parent, node, child) { |
|
// 1. If `parent` is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException. |
|
if (!hasValidParentNodeType(parent)) { |
|
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType); |
|
} |
|
// 2. If `node` is a host-including inclusive ancestor of `parent`, then throw a "HierarchyRequestError" DOMException. |
|
// not implemented! |
|
// 3. If `child` is non-null and its parent is not `parent`, then throw a "NotFoundError" DOMException. |
|
if (child && child.parentNode !== parent) { |
|
throw new DOMException(NOT_FOUND_ERR, 'child not in parent'); |
|
} |
|
if ( |
|
// 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException. |
|
!hasInsertableNodeType(node) || |
|
// 5. If either `node` is a Text node and `parent` is a document, |
|
// the sax parser currently adds top level text nodes, this will be fixed in 0.9.0 |
|
// || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE) |
|
// or `node` is a doctype and `parent` is not a document, then throw a "HierarchyRequestError" DOMException. |
|
(isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE) |
|
) { |
|
throw new DOMException( |
|
HIERARCHY_REQUEST_ERR, |
|
'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType |
|
); |
|
} |
|
} |
|
|
|
/** |
|
* @private |
|
* Step 6 of the checks before inserting and before replacing a child are different. |
|
* |
|
* @param {Document} parent the parent node to insert `node` into |
|
* @param {Node} node the node to insert |
|
* @param {Node | undefined} child the node that should become the `nextSibling` of `node` |
|
* @returns {Node} |
|
* @throws DOMException for several node combinations that would create a DOM that is not well-formed. |
|
* @throws DOMException if `child` is provided but is not a child of `parent`. |
|
* @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity |
|
* @see https://dom.spec.whatwg.org/#concept-node-replace |
|
*/ |
|
function assertPreInsertionValidityInDocument(parent, node, child) { |
|
var parentChildNodes = parent.childNodes || []; |
|
var nodeChildNodes = node.childNodes || []; |
|
|
|
// DocumentFragment |
|
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
|
var nodeChildElements = nodeChildNodes.filter(isElementNode); |
|
// If node has more than one element child or has a Text node child. |
|
if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) { |
|
throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment'); |
|
} |
|
// Otherwise, if `node` has one element child and either `parent` has an element child, |
|
// `child` is a doctype, or `child` is non-null and a doctype is following `child`. |
|
if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) { |
|
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype'); |
|
} |
|
} |
|
// Element |
|
if (isElementNode(node)) { |
|
// `parent` has an element child, `child` is a doctype, |
|
// or `child` is non-null and a doctype is following `child`. |
|
if (!isElementInsertionPossible(parent, child)) { |
|
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype'); |
|
} |
|
} |
|
// DocumentType |
|
if (isDocTypeNode(node)) { |
|
// `parent` has a doctype child, |
|
if (find(parentChildNodes, isDocTypeNode)) { |
|
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed'); |
|
} |
|
var parentElementChild = find(parentChildNodes, isElementNode); |
|
// `child` is non-null and an element is preceding `child`, |
|
if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) { |
|
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element'); |
|
} |
|
// or `child` is null and `parent` has an element child. |
|
if (!child && parentElementChild) { |
|
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present'); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* @private |
|
* Step 6 of the checks before inserting and before replacing a child are different. |
|
* |
|
* @param {Document} parent the parent node to insert `node` into |
|
* @param {Node} node the node to insert |
|
* @param {Node | undefined} child the node that should become the `nextSibling` of `node` |
|
* @returns {Node} |
|
* @throws DOMException for several node combinations that would create a DOM that is not well-formed. |
|
* @throws DOMException if `child` is provided but is not a child of `parent`. |
|
* @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity |
|
* @see https://dom.spec.whatwg.org/#concept-node-replace |
|
*/ |
|
function assertPreReplacementValidityInDocument(parent, node, child) { |
|
var parentChildNodes = parent.childNodes || []; |
|
var nodeChildNodes = node.childNodes || []; |
|
|
|
// DocumentFragment |
|
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
|
var nodeChildElements = nodeChildNodes.filter(isElementNode); |
|
// If `node` has more than one element child or has a Text node child. |
|
if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) { |
|
throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment'); |
|
} |
|
// Otherwise, if `node` has one element child and either `parent` has an element child that is not `child` or a doctype is following `child`. |
|
if (nodeChildElements.length === 1 && !isElementReplacementPossible(parent, child)) { |
|
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype'); |
|
} |
|
} |
|
// Element |
|
if (isElementNode(node)) { |
|
// `parent` has an element child that is not `child` or a doctype is following `child`. |
|
if (!isElementReplacementPossible(parent, child)) { |
|
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype'); |
|
} |
|
} |
|
// DocumentType |
|
if (isDocTypeNode(node)) { |
|
function hasDoctypeChildThatIsNotChild(node) { |
|
return isDocTypeNode(node) && node !== child; |
|
} |
|
|
|
// `parent` has a doctype child that is not `child`, |
|
if (find(parentChildNodes, hasDoctypeChildThatIsNotChild)) { |
|
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed'); |
|
} |
|
var parentElementChild = find(parentChildNodes, isElementNode); |
|
// or an element is preceding `child`. |
|
if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) { |
|
throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element'); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* @private |
|
* @param {Node} parent the parent node to insert `node` into |
|
* @param {Node} node the node to insert |
|
* @param {Node=} child the node that should become the `nextSibling` of `node` |
|
* @returns {Node} |
|
* @throws DOMException for several node combinations that would create a DOM that is not well-formed. |
|
* @throws DOMException if `child` is provided but is not a child of `parent`. |
|
* @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity |
|
*/ |
|
function _insertBefore(parent, node, child, _inDocumentAssertion) { |
|
// To ensure pre-insertion validity of a node into a parent before a child, run these steps: |
|
assertPreInsertionValidity1to5(parent, node, child); |
|
|
|
// If parent is a document, and any of the statements below, switched on the interface node implements, |
|
// are true, then throw a "HierarchyRequestError" DOMException. |
|
if (parent.nodeType === Node.DOCUMENT_NODE) { |
|
(_inDocumentAssertion || assertPreInsertionValidityInDocument)(parent, node, child); |
|
} |
|
|
|
var cp = node.parentNode; |
|
if(cp){ |
|
cp.removeChild(node);//remove and update |
|
} |
|
if(node.nodeType === DOCUMENT_FRAGMENT_NODE){ |
|
var newFirst = node.firstChild; |
|
if (newFirst == null) { |
|
return node; |
|
} |
|
var newLast = node.lastChild; |
|
}else{ |
|
newFirst = newLast = node; |
|
} |
|
var pre = child ? child.previousSibling : parent.lastChild; |
|
|
|
newFirst.previousSibling = pre; |
|
newLast.nextSibling = child; |
|
|
|
|
|
if(pre){ |
|
pre.nextSibling = newFirst; |
|
}else{ |
|
parent.firstChild = newFirst; |
|
} |
|
if(child == null){ |
|
parent.lastChild = newLast; |
|
}else{ |
|
child.previousSibling = newLast; |
|
} |
|
do{ |
|
newFirst.parentNode = parent; |
|
}while(newFirst !== newLast && (newFirst= newFirst.nextSibling)) |
|
_onUpdateChild(parent.ownerDocument||parent, parent); |
|
//console.log(parent.lastChild.nextSibling == null) |
|
if (node.nodeType == DOCUMENT_FRAGMENT_NODE) { |
|
node.firstChild = node.lastChild = null; |
|
} |
|
return node; |
|
} |
|
function _appendSingleChild(parentNode,newChild){ |
|
if (newChild.parentNode) { |
|
newChild.parentNode.removeChild(newChild); |
|
} |
|
newChild.parentNode = parentNode; |
|
newChild.previousSibling = parentNode.lastChild; |
|
newChild.nextSibling = null; |
|
if (newChild.previousSibling) { |
|
newChild.previousSibling.nextSibling = newChild; |
|
}else{ |
|
parentNode.firstChild = newChild; |
|
} |
|
parentNode.lastChild = newChild; |
|
_onUpdateChild(parentNode.ownerDocument,parentNode,newChild); |
|
return newChild; |
|
} |
|
|
|
Document.prototype = { |
|
//implementation : null, |
|
nodeName : '#document', |
|
nodeType : DOCUMENT_NODE, |
|
/** |
|
* The DocumentType node of the document. |
|
* |
|
* @readonly |
|
* @type DocumentType |
|
*/ |
|
doctype : null, |
|
documentElement : null, |
|
_inc : 1, |
|
|
|
insertBefore : function(newChild, refChild){//raises |
|
if(newChild.nodeType == DOCUMENT_FRAGMENT_NODE){ |
|
var child = newChild.firstChild; |
|
while(child){ |
|
var next = child.nextSibling; |
|
this.insertBefore(child,refChild); |
|
child = next; |
|
} |
|
return newChild; |
|
} |
|
_insertBefore(this, newChild, refChild); |
|
newChild.ownerDocument = this; |
|
if (this.documentElement === null && newChild.nodeType === ELEMENT_NODE) { |
|
this.documentElement = newChild; |
|
} |
|
|
|
return newChild; |
|
}, |
|
removeChild : function(oldChild){ |
|
if(this.documentElement == oldChild){ |
|
this.documentElement = null; |
|
} |
|
return _removeChild(this,oldChild); |
|
}, |
|
replaceChild: function (newChild, oldChild) { |
|
//raises |
|
_insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument); |
|
newChild.ownerDocument = this; |
|
if (oldChild) { |
|
this.removeChild(oldChild); |
|
} |
|
if (isElementNode(newChild)) { |
|
this.documentElement = newChild; |
|
} |
|
}, |
|
// Introduced in DOM Level 2: |
|
importNode : function(importedNode,deep){ |
|
return importNode(this,importedNode,deep); |
|
}, |
|
// Introduced in DOM Level 2: |
|
getElementById : function(id){ |
|
var rtv = null; |
|
_visitNode(this.documentElement,function(node){ |
|
if(node.nodeType == ELEMENT_NODE){ |
|
if(node.getAttribute('id') == id){ |
|
rtv = node; |
|
return true; |
|
} |
|
} |
|
}) |
|
return rtv; |
|
}, |
|
|
|
/** |
|
* The `getElementsByClassName` method of `Document` interface returns an array-like object |
|
* of all child elements which have **all** of the given class name(s). |
|
* |
|
* Returns an empty list if `classeNames` is an empty string or only contains HTML white space characters. |
|
* |
|
* |
|
* Warning: This is a live LiveNodeList. |
|
* Changes in the DOM will reflect in the array as the changes occur. |
|
* If an element selected by this array no longer qualifies for the selector, |
|
* it will automatically be removed. Be aware of this for iteration purposes. |
|
* |
|
* @param {string} classNames is a string representing the class name(s) to match; multiple class names are separated by (ASCII-)whitespace |
|
* |
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName |
|
* @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname |
|
*/ |
|
getElementsByClassName: function(classNames) { |
|
var classNamesSet = toOrderedSet(classNames) |
|
return new LiveNodeList(this, function(base) { |
|
var ls = []; |
|
if (classNamesSet.length > 0) { |
|
_visitNode(base.documentElement, function(node) { |
|
if(node !== base && node.nodeType === ELEMENT_NODE) { |
|
var nodeClassNames = node.getAttribute('class') |
|
// can be null if the attribute does not exist |
|
if (nodeClassNames) { |
|
// before splitting and iterating just compare them for the most common case |
|
var matches = classNames === nodeClassNames; |
|
if (!matches) { |
|
var nodeClassNamesSet = toOrderedSet(nodeClassNames) |
|
matches = classNamesSet.every(arrayIncludes(nodeClassNamesSet)) |
|
} |
|
if(matches) { |
|
ls.push(node); |
|
} |
|
} |
|
} |
|
}); |
|
} |
|
return ls; |
|
}); |
|
}, |
|
|
|
//document factory method: |
|
createElement : function(tagName){ |
|
var node = new Element(); |
|
node.ownerDocument = this; |
|
node.nodeName = tagName; |
|
node.tagName = tagName; |
|
node.localName = tagName; |
|
node.childNodes = new NodeList(); |
|
var attrs = node.attributes = new NamedNodeMap(); |
|
attrs._ownerElement = node; |
|
return node; |
|
}, |
|
createDocumentFragment : function(){ |
|
var node = new DocumentFragment(); |
|
node.ownerDocument = this; |
|
node.childNodes = new NodeList(); |
|
return node; |
|
}, |
|
createTextNode : function(data){ |
|
var node = new Text(); |
|
node.ownerDocument = this; |
|
node.appendData(data) |
|
return node; |
|
}, |
|
createComment : function(data){ |
|
var node = new Comment(); |
|
node.ownerDocument = this; |
|
node.appendData(data) |
|
return node; |
|
}, |
|
createCDATASection : function(data){ |
|
var node = new CDATASection(); |
|
node.ownerDocument = this; |
|
node.appendData(data) |
|
return node; |
|
}, |
|
createProcessingInstruction : function(target,data){ |
|
var node = new ProcessingInstruction(); |
|
node.ownerDocument = this; |
|
node.tagName = node.target = target; |
|
node.nodeValue= node.data = data; |
|
return node; |
|
}, |
|
createAttribute : function(name){ |
|
var node = new Attr(); |
|
node.ownerDocument = this; |
|
node.name = name; |
|
node.nodeName = name; |
|
node.localName = name; |
|
node.specified = true; |
|
return node; |
|
}, |
|
createEntityReference : function(name){ |
|
var node = new EntityReference(); |
|
node.ownerDocument = this; |
|
node.nodeName = name; |
|
return node; |
|
}, |
|
// Introduced in DOM Level 2: |
|
createElementNS : function(namespaceURI,qualifiedName){ |
|
var node = new Element(); |
|
var pl = qualifiedName.split(':'); |
|
var attrs = node.attributes = new NamedNodeMap(); |
|
node.childNodes = new NodeList(); |
|
node.ownerDocument = this; |
|
node.nodeName = qualifiedName; |
|
node.tagName = qualifiedName; |
|
node.namespaceURI = namespaceURI; |
|
if(pl.length == 2){ |
|
node.prefix = pl[0]; |
|
node.localName = pl[1]; |
|
}else{ |
|
//el.prefix = null; |
|
node.localName = qualifiedName; |
|
} |
|
attrs._ownerElement = node; |
|
return node; |
|
}, |
|
// Introduced in DOM Level 2: |
|
createAttributeNS : function(namespaceURI,qualifiedName){ |
|
var node = new Attr(); |
|
var pl = qualifiedName.split(':'); |
|
node.ownerDocument = this; |
|
node.nodeName = qualifiedName; |
|
node.name = qualifiedName; |
|
node.namespaceURI = namespaceURI; |
|
node.specified = true; |
|
if(pl.length == 2){ |
|
node.prefix = pl[0]; |
|
node.localName = pl[1]; |
|
}else{ |
|
//el.prefix = null; |
|
node.localName = qualifiedName; |
|
} |
|
return node; |
|
} |
|
}; |
|
_extends(Document,Node); |
|
|
|
|
|
function Element() { |
|
this._nsMap = {}; |
|
}; |
|
Element.prototype = { |
|
nodeType : ELEMENT_NODE, |
|
hasAttribute : function(name){ |
|
return this.getAttributeNode(name)!=null; |
|
}, |
|
getAttribute : function(name){ |
|
var attr = this.getAttributeNode(name); |
|
return attr && attr.value || ''; |
|
}, |
|
getAttributeNode : function(name){ |
|
return this.attributes.getNamedItem(name); |
|
}, |
|
setAttribute : function(name, value){ |
|
var attr = this.ownerDocument.createAttribute(name); |
|
attr.value = attr.nodeValue = "" + value; |
|
this.setAttributeNode(attr) |
|
}, |
|
removeAttribute : function(name){ |
|
var attr = this.getAttributeNode(name) |
|
attr && this.removeAttributeNode(attr); |
|
}, |
|
|
|
//four real opeartion method |
|
appendChild:function(newChild){ |
|
if(newChild.nodeType === DOCUMENT_FRAGMENT_NODE){ |
|
return this.insertBefore(newChild,null); |
|
}else{ |
|
return _appendSingleChild(this,newChild); |
|
} |
|
}, |
|
setAttributeNode : function(newAttr){ |
|
return this.attributes.setNamedItem(newAttr); |
|
}, |
|
setAttributeNodeNS : function(newAttr){ |
|
return this.attributes.setNamedItemNS(newAttr); |
|
}, |
|
removeAttributeNode : function(oldAttr){ |
|
//console.log(this == oldAttr.ownerElement) |
|
return this.attributes.removeNamedItem(oldAttr.nodeName); |
|
}, |
|
//get real attribute name,and remove it by removeAttributeNode |
|
removeAttributeNS : function(namespaceURI, localName){ |
|
var old = this.getAttributeNodeNS(namespaceURI, localName); |
|
old && this.removeAttributeNode(old); |
|
}, |
|
|
|
hasAttributeNS : function(namespaceURI, localName){ |
|
return this.getAttributeNodeNS(namespaceURI, localName)!=null; |
|
}, |
|
getAttributeNS : function(namespaceURI, localName){ |
|
var attr = this.getAttributeNodeNS(namespaceURI, localName); |
|
return attr && attr.value || ''; |
|
}, |
|
setAttributeNS : function(namespaceURI, qualifiedName, value){ |
|
var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName); |
|
attr.value = attr.nodeValue = "" + value; |
|
this.setAttributeNode(attr) |
|
}, |
|
getAttributeNodeNS : function(namespaceURI, localName){ |
|
return this.attributes.getNamedItemNS(namespaceURI, localName); |
|
}, |
|
|
|
getElementsByTagName : function(tagName){ |
|
return new LiveNodeList(this,function(base){ |
|
var ls = []; |
|
_visitNode(base,function(node){ |
|
if(node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)){ |
|
ls.push(node); |
|
} |
|
}); |
|
return ls; |
|
}); |
|
}, |
|
getElementsByTagNameNS : function(namespaceURI, localName){ |
|
return new LiveNodeList(this,function(base){ |
|
var ls = []; |
|
_visitNode(base,function(node){ |
|
if(node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)){ |
|
ls.push(node); |
|
} |
|
}); |
|
return ls; |
|
|
|
}); |
|
} |
|
}; |
|
Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName; |
|
Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS; |
|
|
|
|
|
_extends(Element,Node); |
|
function Attr() { |
|
}; |
|
Attr.prototype.nodeType = ATTRIBUTE_NODE; |
|
_extends(Attr,Node); |
|
|
|
|
|
function CharacterData() { |
|
}; |
|
CharacterData.prototype = { |
|
data : '', |
|
substringData : function(offset, count) { |
|
return this.data.substring(offset, offset+count); |
|
}, |
|
appendData: function(text) { |
|
text = this.data+text; |
|
this.nodeValue = this.data = text; |
|
this.length = text.length; |
|
}, |
|
insertData: function(offset,text) { |
|
this.replaceData(offset,0,text); |
|
|
|
}, |
|
appendChild:function(newChild){ |
|
throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR]) |
|
}, |
|
deleteData: function(offset, count) { |
|
this.replaceData(offset,count,""); |
|
}, |
|
replaceData: function(offset, count, text) { |
|
var start = this.data.substring(0,offset); |
|
var end = this.data.substring(offset+count); |
|
text = start + text + end; |
|
this.nodeValue = this.data = text; |
|
this.length = text.length; |
|
} |
|
} |
|
_extends(CharacterData,Node); |
|
function Text() { |
|
}; |
|
Text.prototype = { |
|
nodeName : "#text", |
|
nodeType : TEXT_NODE, |
|
splitText : function(offset) { |
|
var text = this.data; |
|
var newText = text.substring(offset); |
|
text = text.substring(0, offset); |
|
this.data = this.nodeValue = text; |
|
this.length = text.length; |
|
var newNode = this.ownerDocument.createTextNode(newText); |
|
if(this.parentNode){ |
|
this.parentNode.insertBefore(newNode, this.nextSibling); |
|
} |
|
return newNode; |
|
} |
|
} |
|
_extends(Text,CharacterData); |
|
function Comment() { |
|
}; |
|
Comment.prototype = { |
|
nodeName : "#comment", |
|
nodeType : COMMENT_NODE |
|
} |
|
_extends(Comment,CharacterData); |
|
|
|
function CDATASection() { |
|
}; |
|
CDATASection.prototype = { |
|
nodeName : "#cdata-section", |
|
nodeType : CDATA_SECTION_NODE |
|
} |
|
_extends(CDATASection,CharacterData); |
|
|
|
|
|
function DocumentType() { |
|
}; |
|
DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE; |
|
_extends(DocumentType,Node); |
|
|
|
function Notation() { |
|
}; |
|
Notation.prototype.nodeType = NOTATION_NODE; |
|
_extends(Notation,Node); |
|
|
|
function Entity() { |
|
}; |
|
Entity.prototype.nodeType = ENTITY_NODE; |
|
_extends(Entity,Node); |
|
|
|
function EntityReference() { |
|
}; |
|
EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE; |
|
_extends(EntityReference,Node); |
|
|
|
function DocumentFragment() { |
|
}; |
|
DocumentFragment.prototype.nodeName = "#document-fragment"; |
|
DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE; |
|
_extends(DocumentFragment,Node); |
|
|
|
|
|
function ProcessingInstruction() { |
|
} |
|
ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE; |
|
_extends(ProcessingInstruction,Node); |
|
function XMLSerializer(){} |
|
XMLSerializer.prototype.serializeToString = function(node,isHtml,nodeFilter){ |
|
return nodeSerializeToString.call(node,isHtml,nodeFilter); |
|
} |
|
Node.prototype.toString = nodeSerializeToString; |
|
function nodeSerializeToString(isHtml,nodeFilter){ |
|
var buf = []; |
|
var refNode = this.nodeType == 9 && this.documentElement || this; |
|
var prefix = refNode.prefix; |
|
var uri = refNode.namespaceURI; |
|
|
|
if(uri && prefix == null){ |
|
//console.log(prefix) |
|
var prefix = refNode.lookupPrefix(uri); |
|
if(prefix == null){ |
|
//isHTML = true; |
|
var visibleNamespaces=[ |
|
{namespace:uri,prefix:null} |
|
//{namespace:uri,prefix:''} |
|
] |
|
} |
|
} |
|
serializeToString(this,buf,isHtml,nodeFilter,visibleNamespaces); |
|
//console.log('###',this.nodeType,uri,prefix,buf.join('')) |
|
return buf.join(''); |
|
} |
|
|
|
function needNamespaceDefine(node, isHTML, visibleNamespaces) { |
|
var prefix = node.prefix || ''; |
|
var uri = node.namespaceURI; |
|
// According to [Namespaces in XML 1.0](https://www.w3.org/TR/REC-xml-names/#ns-using) , |
|
// and more specifically https://www.w3.org/TR/REC-xml-names/#nsc-NoPrefixUndecl : |
|
// > In a namespace declaration for a prefix [...], the attribute value MUST NOT be empty. |
|
// in a similar manner [Namespaces in XML 1.1](https://www.w3.org/TR/xml-names11/#ns-using) |
|
// and more specifically https://www.w3.org/TR/xml-names11/#nsc-NSDeclared : |
|
// > [...] Furthermore, the attribute value [...] must not be an empty string. |
|
// so serializing empty namespace value like xmlns:ds="" would produce an invalid XML document. |
|
if (!uri) { |
|
return false; |
|
} |
|
if (prefix === "xml" && uri === NAMESPACE.XML || uri === NAMESPACE.XMLNS) { |
|
return false; |
|
} |
|
|
|
var i = visibleNamespaces.length |
|
while (i--) { |
|
var ns = visibleNamespaces[i]; |
|
// get namespace prefix |
|
if (ns.prefix === prefix) { |
|
return ns.namespace !== uri; |
|
} |
|
} |
|
return true; |
|
} |
|
/** |
|
* Well-formed constraint: No < in Attribute Values |
|
* The replacement text of any entity referred to directly or indirectly in an attribute value must not contain a <. |
|
* @see https://www.w3.org/TR/xml/#CleanAttrVals |
|
* @see https://www.w3.org/TR/xml/#NT-AttValue |
|
*/ |
|
function addSerializedAttribute(buf, qualifiedName, value) { |
|
buf.push(' ', qualifiedName, '="', value.replace(/[<&"]/g,_xmlEncoder), '"') |
|
} |
|
|
|
function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){ |
|
if (!visibleNamespaces) { |
|
visibleNamespaces = []; |
|
} |
|
|
|
if(nodeFilter){ |
|
node = nodeFilter(node); |
|
if(node){ |
|
if(typeof node == 'string'){ |
|
buf.push(node); |
|
return; |
|
} |
|
}else{ |
|
return; |
|
} |
|
//buf.sort.apply(attrs, attributeSorter); |
|
} |
|
|
|
switch(node.nodeType){ |
|
case ELEMENT_NODE: |
|
var attrs = node.attributes; |
|
var len = attrs.length; |
|
var child = node.firstChild; |
|
var nodeName = node.tagName; |
|
|
|
isHTML = NAMESPACE.isHTML(node.namespaceURI) || isHTML |
|
|
|
var prefixedNodeName = nodeName |
|
if (!isHTML && !node.prefix && node.namespaceURI) { |
|
var defaultNS |
|
// lookup current default ns from `xmlns` attribute |
|
for (var ai = 0; ai < attrs.length; ai++) { |
|
if (attrs.item(ai).name === 'xmlns') { |
|
defaultNS = attrs.item(ai).value |
|
break |
|
} |
|
} |
|
if (!defaultNS) { |
|
// lookup current default ns in visibleNamespaces |
|
for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) { |
|
var namespace = visibleNamespaces[nsi] |
|
if (namespace.prefix === '' && namespace.namespace === node.namespaceURI) { |
|
defaultNS = namespace.namespace |
|
break |
|
} |
|
} |
|
} |
|
if (defaultNS !== node.namespaceURI) { |
|
for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) { |
|
var namespace = visibleNamespaces[nsi] |
|
if (namespace.namespace === node.namespaceURI) { |
|
if (namespace.prefix) { |
|
prefixedNodeName = namespace.prefix + ':' + nodeName |
|
} |
|
break |
|
} |
|
} |
|
} |
|
} |
|
|
|
buf.push('<', prefixedNodeName); |
|
|
|
for(var i=0;i<len;i++){ |
|
// add namespaces for attributes |
|
var attr = attrs.item(i); |
|
if (attr.prefix == 'xmlns') { |
|
visibleNamespaces.push({ prefix: attr.localName, namespace: attr.value }); |
|
}else if(attr.nodeName == 'xmlns'){ |
|
visibleNamespaces.push({ prefix: '', namespace: attr.value }); |
|
} |
|
} |
|
|
|
for(var i=0;i<len;i++){ |
|
var attr = attrs.item(i); |
|
if (needNamespaceDefine(attr,isHTML, visibleNamespaces)) { |
|
var prefix = attr.prefix||''; |
|
var uri = attr.namespaceURI; |
|
addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri); |
|
visibleNamespaces.push({ prefix: prefix, namespace:uri }); |
|
} |
|
serializeToString(attr,buf,isHTML,nodeFilter,visibleNamespaces); |
|
} |
|
|
|
// add namespace for current node |
|
if (nodeName === prefixedNodeName && needNamespaceDefine(node, isHTML, visibleNamespaces)) { |
|
var prefix = node.prefix||''; |
|
var uri = node.namespaceURI; |
|
addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri); |
|
visibleNamespaces.push({ prefix: prefix, namespace:uri }); |
|
} |
|
|
|
if(child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)){ |
|
buf.push('>'); |
|
//if is cdata child node |
|
if(isHTML && /^script$/i.test(nodeName)){ |
|
while(child){ |
|
if(child.data){ |
|
buf.push(child.data); |
|
}else{ |
|
serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice()); |
|
} |
|
child = child.nextSibling; |
|
} |
|
}else |
|
{ |
|
while(child){ |
|
serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice()); |
|
child = child.nextSibling; |
|
} |
|
} |
|
buf.push('</',prefixedNodeName,'>'); |
|
}else{ |
|
buf.push('/>'); |
|
} |
|
// remove added visible namespaces |
|
//visibleNamespaces.length = startVisibleNamespaces; |
|
return; |
|
case DOCUMENT_NODE: |
|
case DOCUMENT_FRAGMENT_NODE: |
|
var child = node.firstChild; |
|
while(child){ |
|
serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice()); |
|
child = child.nextSibling; |
|
} |
|
return; |
|
case ATTRIBUTE_NODE: |
|
return addSerializedAttribute(buf, node.name, node.value); |
|
case TEXT_NODE: |
|
/** |
|
* The ampersand character (&) and the left angle bracket (<) must not appear in their literal form, |
|
* except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section. |
|
* If they are needed elsewhere, they must be escaped using either numeric character references or the strings |
|
* `&` and `<` respectively. |
|
* The right angle bracket (>) may be represented using the string " > ", and must, for compatibility, |
|
* be escaped using either `>` or a character reference when it appears in the string `]]>` in content, |
|
* when that string is not marking the end of a CDATA section. |
|
* |
|
* In the content of elements, character data is any string of characters |
|
* which does not contain the start-delimiter of any markup |
|
* and does not include the CDATA-section-close delimiter, `]]>`. |
|
* |
|
* @see https://www.w3.org/TR/xml/#NT-CharData |
|
*/ |
|
return buf.push(node.data |
|
.replace(/[<&]/g,_xmlEncoder) |
|
.replace(/]]>/g, ']]>') |
|
); |
|
case CDATA_SECTION_NODE: |
|
return buf.push( '<![CDATA[',node.data,']]>'); |
|
case COMMENT_NODE: |
|
return buf.push( "<!--",node.data,"-->"); |
|
case DOCUMENT_TYPE_NODE: |
|
var pubid = node.publicId; |
|
var sysid = node.systemId; |
|
buf.push('<!DOCTYPE ',node.name); |
|
if(pubid){ |
|
buf.push(' PUBLIC ', pubid); |
|
if (sysid && sysid!='.') { |
|
buf.push(' ', sysid); |
|
} |
|
buf.push('>'); |
|
}else if(sysid && sysid!='.'){ |
|
buf.push(' SYSTEM ', sysid, '>'); |
|
}else{ |
|
var sub = node.internalSubset; |
|
if(sub){ |
|
buf.push(" [",sub,"]"); |
|
} |
|
buf.push(">"); |
|
} |
|
return; |
|
case PROCESSING_INSTRUCTION_NODE: |
|
return buf.push( "<?",node.target," ",node.data,"?>"); |
|
case ENTITY_REFERENCE_NODE: |
|
return buf.push( '&',node.nodeName,';'); |
|
//case ENTITY_NODE: |
|
//case NOTATION_NODE: |
|
default: |
|
buf.push('??',node.nodeName); |
|
} |
|
} |
|
function importNode(doc,node,deep){ |
|
var node2; |
|
switch (node.nodeType) { |
|
case ELEMENT_NODE: |
|
node2 = node.cloneNode(false); |
|
node2.ownerDocument = doc; |
|
//var attrs = node2.attributes; |
|
//var len = attrs.length; |
|
//for(var i=0;i<len;i++){ |
|
//node2.setAttributeNodeNS(importNode(doc,attrs.item(i),deep)); |
|
//} |
|
case DOCUMENT_FRAGMENT_NODE: |
|
break; |
|
case ATTRIBUTE_NODE: |
|
deep = true; |
|
break; |
|
//case ENTITY_REFERENCE_NODE: |
|
//case PROCESSING_INSTRUCTION_NODE: |
|
////case TEXT_NODE: |
|
//case CDATA_SECTION_NODE: |
|
//case COMMENT_NODE: |
|
// deep = false; |
|
// break; |
|
//case DOCUMENT_NODE: |
|
//case DOCUMENT_TYPE_NODE: |
|
//cannot be imported. |
|
//case ENTITY_NODE: |
|
//case NOTATION_NODE: |
|
//can not hit in level3 |
|
//default:throw e; |
|
} |
|
if(!node2){ |
|
node2 = node.cloneNode(false);//false |
|
} |
|
node2.ownerDocument = doc; |
|
node2.parentNode = null; |
|
if(deep){ |
|
var child = node.firstChild; |
|
while(child){ |
|
node2.appendChild(importNode(doc,child,deep)); |
|
child = child.nextSibling; |
|
} |
|
} |
|
return node2; |
|
} |
|
// |
|
//var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1, |
|
// attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,}; |
|
function cloneNode(doc,node,deep){ |
|
var node2 = new node.constructor(); |
|
for (var n in node) { |
|
if (Object.prototype.hasOwnProperty.call(node, n)) { |
|
var v = node[n]; |
|
if (typeof v != "object") { |
|
if (v != node2[n]) { |
|
node2[n] = v; |
|
} |
|
} |
|
} |
|
} |
|
if(node.childNodes){ |
|
node2.childNodes = new NodeList(); |
|
} |
|
node2.ownerDocument = doc; |
|
switch (node2.nodeType) { |
|
case ELEMENT_NODE: |
|
var attrs = node.attributes; |
|
var attrs2 = node2.attributes = new NamedNodeMap(); |
|
var len = attrs.length |
|
attrs2._ownerElement = node2; |
|
for(var i=0;i<len;i++){ |
|
node2.setAttributeNode(cloneNode(doc,attrs.item(i),true)); |
|
} |
|
break;; |
|
case ATTRIBUTE_NODE: |
|
deep = true; |
|
} |
|
if(deep){ |
|
var child = node.firstChild; |
|
while(child){ |
|
node2.appendChild(cloneNode(doc,child,deep)); |
|
child = child.nextSibling; |
|
} |
|
} |
|
return node2; |
|
} |
|
|
|
function __set__(object,key,value){ |
|
object[key] = value |
|
} |
|
//do dynamic |
|
try{ |
|
if(Object.defineProperty){ |
|
Object.defineProperty(LiveNodeList.prototype,'length',{ |
|
get:function(){ |
|
_updateLiveList(this); |
|
return this.$$length; |
|
} |
|
}); |
|
|
|
Object.defineProperty(Node.prototype,'textContent',{ |
|
get:function(){ |
|
return getTextContent(this); |
|
}, |
|
|
|
set:function(data){ |
|
switch(this.nodeType){ |
|
case ELEMENT_NODE: |
|
case DOCUMENT_FRAGMENT_NODE: |
|
while(this.firstChild){ |
|
this.removeChild(this.firstChild); |
|
} |
|
if(data || String(data)){ |
|
this.appendChild(this.ownerDocument.createTextNode(data)); |
|
} |
|
break; |
|
|
|
default: |
|
this.data = data; |
|
this.value = data; |
|
this.nodeValue = data; |
|
} |
|
} |
|
}) |
|
|
|
function getTextContent(node){ |
|
switch(node.nodeType){ |
|
case ELEMENT_NODE: |
|
case DOCUMENT_FRAGMENT_NODE: |
|
var buf = []; |
|
node = node.firstChild; |
|
while(node){ |
|
if(node.nodeType!==7 && node.nodeType !==8){ |
|
buf.push(getTextContent(node)); |
|
} |
|
node = node.nextSibling; |
|
} |
|
return buf.join(''); |
|
default: |
|
return node.nodeValue; |
|
} |
|
} |
|
|
|
__set__ = function(object,key,value){ |
|
//console.log(value) |
|
object['$$'+key] = value |
|
} |
|
} |
|
}catch(e){//ie8 |
|
} |
|
|
|
//if(typeof require == 'function'){ |
|
exports.DocumentType = DocumentType; |
|
exports.DOMException = DOMException; |
|
exports.DOMImplementation = DOMImplementation; |
|
exports.Element = Element; |
|
exports.Node = Node; |
|
exports.NodeList = NodeList; |
|
exports.XMLSerializer = XMLSerializer; |
|
//}
|
|
|