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} current * @param {string} element * @returns {Record} */ 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 index >= 0 && index < this.length ? this[index] : null; }, toString:function(isHTML,nodeFilter){ for(var buf = [], i = 0;i=0){ var lastIndex = list.length-1 while(i0 || 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(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:''] } } /** * Updates `el.childNodes`, updating the indexed items and it's `length`. * Passing `newChild` means it will be appended. * Otherwise it's assumed that an item has been removed, * and `el.firstNode` and it's `.nextSibling` are used * to walk the current list of child nodes. * * @param {Document} doc * @param {Node} el * @param {Node} [newChild] * @private */ function _onUpdateChild (doc, el, newChild) { if(doc && doc._inc){ doc._inc++; //update childNodes var cs = el.childNodes; if (newChild) { cs[cs.length++] = newChild; } else { var child = el.firstChild; var i = 0; while (child) { cs[i++] = child; child = child.nextSibling; } cs.length = i; delete cs[cs.length]; } } } /** * Removes the connections between `parentNode` and `child` * and any existing `child.previousSibling` or `child.nextSibling`. * * @see https://github.com/xmldom/xmldom/issues/135 * @see https://github.com/xmldom/xmldom/issues/145 * * @param {Node} parentNode * @param {Node} child * @returns {Node} the child that was removed. * @private */ 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; } /** * Appends `newChild` to `parentNode`. * If `newChild` is already connected to a `parentNode` it is first removed from it. * * @see https://github.com/xmldom/xmldom/issues/135 * @see https://github.com/xmldom/xmldom/issues/145 * @param {Node} parentNode * @param {Node} newChild * @returns {Node} * @private */ 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.nodeName = 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/xml11/#CleanAttrVals * @see https://www.w3.org/TR/xml11/#NT-AttValue * * Literal whitespace other than space that appear in attribute values * are serialized as their entity references, so they will be preserved. * (In contrast to whitespace literals in the input which are normalized to spaces) * @see https://www.w3.org/TR/xml11/#AVNormalize * @see https://w3c.github.io/DOM-Parsing/#serializing-an-element-s-attributes */ function addSerializedAttribute(buf, qualifiedName, value) { buf.push(' ', qualifiedName, '="', value.replace(/[<>&"\t\n\r]/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'); //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(''); }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 * @see https://w3c.github.io/DOM-Parsing/#xml-serializing-a-text-node */ return buf.push(node.data .replace(/[<&>]/g,_xmlEncoder) ); case CDATA_SECTION_NODE: return buf.push( ''); case COMMENT_NODE: return buf.push( ""); case DOCUMENT_TYPE_NODE: var pubid = node.publicId; var sysid = node.systemId; 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( ""); 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