3136 lines
101 KiB
JavaScript
3136 lines
101 KiB
JavaScript
'use strict';
|
||
|
||
var conventions = require('./conventions');
|
||
var find = conventions.find;
|
||
var hasDefaultHTMLNamespace = conventions.hasDefaultHTMLNamespace;
|
||
var hasOwn = conventions.hasOwn;
|
||
var isHTMLMimeType = conventions.isHTMLMimeType;
|
||
var isHTMLRawTextElement = conventions.isHTMLRawTextElement;
|
||
var isHTMLVoidElement = conventions.isHTMLVoidElement;
|
||
var MIME_TYPE = conventions.MIME_TYPE;
|
||
var NAMESPACE = conventions.NAMESPACE;
|
||
|
||
/**
|
||
* Private DOM Constructor symbol
|
||
*
|
||
* Internal symbol used for construction of all classes whose constructors should be private.
|
||
* Currently used for checks in `Node`, `Document`, `Element`, `Attr`, `CharacterData`, `Text`, `Comment`,
|
||
* `CDATASection`, `DocumentType`, `Notation`, `Entity`, `EntityReference`, `DocumentFragment`, `ProcessingInstruction`
|
||
* so the constructor can't be used from outside the module.
|
||
*/
|
||
var PDC = Symbol();
|
||
|
||
var errors = require('./errors');
|
||
var DOMException = errors.DOMException;
|
||
var DOMExceptionName = errors.DOMExceptionName;
|
||
|
||
var g = require('./grammar');
|
||
|
||
/**
|
||
* Checks if the given symbol equals the Private DOM Constructor symbol (PDC)
|
||
* and throws an Illegal constructor exception when the symbols don't match.
|
||
* This ensures that the constructor remains private and can't be used outside this module.
|
||
*/
|
||
function checkSymbol(symbol) {
|
||
if (symbol !== PDC) {
|
||
throw new TypeError('Illegal constructor');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* A prerequisite for `[].filter`, to drop elements that are empty.
|
||
*
|
||
* @param {string} input
|
||
* The string to be checked.
|
||
* @returns {boolean}
|
||
* Returns `true` if the input string is not empty, `false` otherwise.
|
||
*/
|
||
function notEmptyString(input) {
|
||
return input !== '';
|
||
}
|
||
/**
|
||
* Splits a string on ASCII whitespace characters (U+0009 TAB, U+000A LF, U+000C FF, U+000D CR,
|
||
* U+0020 SPACE).
|
||
* It follows the definition from the infra specification from WHATWG.
|
||
*
|
||
* @param {string} input
|
||
* The string to be split.
|
||
* @returns {string[]}
|
||
* An array of the split strings. The array can be empty if the input string is empty or only
|
||
* contains whitespace characters.
|
||
* @see {@link https://infra.spec.whatwg.org/#split-on-ascii-whitespace}
|
||
* @see {@link https://infra.spec.whatwg.org/#ascii-whitespace}
|
||
*/
|
||
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
|
||
* The current record object to which the element will be added as a key.
|
||
* The object's keys are string types and values are either boolean or undefined.
|
||
* @param {string} element
|
||
* The string to be added as a key to the current record.
|
||
* @returns {Record<string, boolean | undefined>}
|
||
* The updated record object after the addition of the new element.
|
||
*/
|
||
function orderedSetReducer(current, element) {
|
||
if (!hasOwn(current, element)) {
|
||
current[element] = true;
|
||
}
|
||
return current;
|
||
}
|
||
|
||
/**
|
||
* Converts a string into an ordered set by splitting the input on ASCII whitespace and
|
||
* ensuring uniqueness of elements.
|
||
* This follows the definition of an ordered set from the infra specification by WHATWG.
|
||
*
|
||
* @param {string} input
|
||
* The input string to be transformed into an ordered set.
|
||
* @returns {string[]}
|
||
* An array of unique strings obtained from the input, preserving the original order.
|
||
* The array can be empty if the input string is empty or only contains whitespace characters.
|
||
* @see {@link https://infra.spec.whatwg.org/#ordered-set}
|
||
*/
|
||
function toOrderedSet(input) {
|
||
if (!input) return [];
|
||
var list = splitOnASCIIWhitespace(input);
|
||
return Object.keys(list.reduce(orderedSetReducer, {}));
|
||
}
|
||
|
||
/**
|
||
* Uses `list.indexOf` to implement a function that behaves like `Array.prototype.includes`.
|
||
* This function is used in environments where `Array.prototype.includes` may not be available.
|
||
*
|
||
* @param {any[]} list
|
||
* The array in which to search for the element.
|
||
* @returns {function(any): boolean}
|
||
* A function that accepts an element and returns a boolean indicating whether the element is
|
||
* included in the provided list.
|
||
*/
|
||
function arrayIncludes(list) {
|
||
return function (element) {
|
||
return list && list.indexOf(element) !== -1;
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Validates a qualified name based on the criteria provided in the DOM specification by
|
||
* WHATWG.
|
||
*
|
||
* @param {string} qualifiedName
|
||
* The qualified name to be validated.
|
||
* @throws {DOMException}
|
||
* With code {@link DOMException.INVALID_CHARACTER_ERR} if the qualified name contains an
|
||
* invalid character.
|
||
* @see {@link https://dom.spec.whatwg.org/#validate}
|
||
*/
|
||
function validateQualifiedName(qualifiedName) {
|
||
if (!g.QName_exact.test(qualifiedName)) {
|
||
throw new DOMException(DOMException.INVALID_CHARACTER_ERR, 'invalid character in qualified name "' + qualifiedName + '"');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Validates a qualified name and the namespace associated with it,
|
||
* based on the criteria provided in the DOM specification by WHATWG.
|
||
*
|
||
* @param {string | null} namespace
|
||
* The namespace to be validated. It can be a string or null.
|
||
* @param {string} qualifiedName
|
||
* The qualified name to be validated.
|
||
* @returns {[namespace: string | null, prefix: string | null, localName: string]}
|
||
* Returns a tuple with the namespace,
|
||
* prefix and local name of the qualified name.
|
||
* @throws {DOMException}
|
||
* Throws a DOMException if the qualified name or the namespace is not valid.
|
||
* @see {@link https://dom.spec.whatwg.org/#validate-and-extract}
|
||
*/
|
||
function validateAndExtract(namespace, qualifiedName) {
|
||
validateQualifiedName(qualifiedName);
|
||
namespace = namespace || null;
|
||
/**
|
||
* @type {string | null}
|
||
*/
|
||
var prefix = null;
|
||
var localName = qualifiedName;
|
||
if (qualifiedName.indexOf(':') >= 0) {
|
||
var splitResult = qualifiedName.split(':');
|
||
prefix = splitResult[0];
|
||
localName = splitResult[1];
|
||
}
|
||
if (prefix !== null && namespace === null) {
|
||
throw new DOMException(DOMException.NAMESPACE_ERR, 'prefix is non-null and namespace is null');
|
||
}
|
||
if (prefix === 'xml' && namespace !== conventions.NAMESPACE.XML) {
|
||
throw new DOMException(DOMException.NAMESPACE_ERR, 'prefix is "xml" and namespace is not the XML namespace');
|
||
}
|
||
if ((prefix === 'xmlns' || qualifiedName === 'xmlns') && namespace !== conventions.NAMESPACE.XMLNS) {
|
||
throw new DOMException(
|
||
DOMException.NAMESPACE_ERR,
|
||
'either qualifiedName or prefix is "xmlns" and namespace is not the XMLNS namespace'
|
||
);
|
||
}
|
||
if (namespace === conventions.NAMESPACE.XMLNS && prefix !== 'xmlns' && qualifiedName !== 'xmlns') {
|
||
throw new DOMException(
|
||
DOMException.NAMESPACE_ERR,
|
||
'namespace is the XMLNS namespace and neither qualifiedName nor prefix is "xmlns"'
|
||
);
|
||
}
|
||
return [namespace, prefix, localName];
|
||
}
|
||
|
||
/**
|
||
* Copies properties from one object to another.
|
||
* It only copies the object's own (not inherited) properties.
|
||
*
|
||
* @param {Object} src
|
||
* The source object from which properties are copied.
|
||
* @param {Object} dest
|
||
* The destination object to which properties are copied.
|
||
*/
|
||
function copy(src, dest) {
|
||
for (var p in src) {
|
||
if (hasOwn(src, p)) {
|
||
dest[p] = src[p];
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Extends a class with the properties and methods of a super class.
|
||
* It uses a form of prototypal inheritance, and establishes the `constructor` property
|
||
* correctly(?).
|
||
*
|
||
* It is not clear to the current maintainers if this implementation is making sense,
|
||
* since it creates an intermediate prototype function,
|
||
* which all properties of `Super` are copied onto using `_copy`.
|
||
*
|
||
* @param {Object} Class
|
||
* The class that is to be extended.
|
||
* @param {Object} Super
|
||
* The super class from which properties and methods are inherited.
|
||
* @private
|
||
*/
|
||
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;
|
||
}
|
||
}
|
||
|
||
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);
|
||
|
||
var DocumentPosition = conventions.freeze({
|
||
DOCUMENT_POSITION_DISCONNECTED: 1,
|
||
DOCUMENT_POSITION_PRECEDING: 2,
|
||
DOCUMENT_POSITION_FOLLOWING: 4,
|
||
DOCUMENT_POSITION_CONTAINS: 8,
|
||
DOCUMENT_POSITION_CONTAINED_BY: 16,
|
||
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 32,
|
||
});
|
||
|
||
//helper functions for compareDocumentPosition
|
||
/**
|
||
* Finds the common ancestor in two parent chains.
|
||
*
|
||
* @param {Node[]} a
|
||
* The first parent chain.
|
||
* @param {Node[]} b
|
||
* The second parent chain.
|
||
* @returns {Node}
|
||
* The common ancestor node if it exists. If there is no common ancestor, the function will
|
||
* return `null`.
|
||
*/
|
||
function commonAncestor(a, b) {
|
||
if (b.length < a.length) return commonAncestor(b, a);
|
||
var c = null;
|
||
for (var n in a) {
|
||
if (a[n] !== b[n]) return c;
|
||
c = a[n];
|
||
}
|
||
return c;
|
||
}
|
||
|
||
/**
|
||
* Assigns a unique identifier to a document to ensure consistency while comparing unrelated
|
||
* nodes.
|
||
*
|
||
* @param {Document} doc
|
||
* The document to which a unique identifier is to be assigned.
|
||
* @returns {string}
|
||
* The unique identifier of the document. If the document already had a unique identifier, the
|
||
* function will return the existing one.
|
||
*/
|
||
function docGUID(doc) {
|
||
if (!doc.guid) doc.guid = Math.random();
|
||
return doc.guid;
|
||
}
|
||
//-- end of helper functions
|
||
|
||
/**
|
||
* 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.
|
||
* You can also access the items of the NodeList with a `for...of` loop.
|
||
*
|
||
* @class NodeList
|
||
* @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177
|
||
* @constructs NodeList
|
||
*/
|
||
function NodeList() {}
|
||
NodeList.prototype = {
|
||
/**
|
||
* The number of nodes in the list. The range of valid child node indices is 0 to length-1
|
||
* inclusive.
|
||
*
|
||
* @type {number}
|
||
*/
|
||
length: 0,
|
||
/**
|
||
* Returns the item at `index`. If index is greater than or equal to the number of nodes in
|
||
* the list, this returns null.
|
||
*
|
||
* @param index
|
||
* Unsigned long Index into the collection.
|
||
* @returns {Node | null}
|
||
* The node at position `index` in the NodeList,
|
||
* or null if that is not a valid index.
|
||
*/
|
||
item: function (index) {
|
||
return index >= 0 && index < this.length ? this[index] : null;
|
||
},
|
||
/**
|
||
* Returns a string representation of the NodeList.
|
||
*
|
||
* @param {unknown} nodeFilter
|
||
* __A filter function? Not implemented according to the spec?__.
|
||
* @returns {string}
|
||
* A string representation of the NodeList.
|
||
*/
|
||
toString: function (nodeFilter) {
|
||
for (var buf = [], i = 0; i < this.length; i++) {
|
||
serializeToString(this[i], buf, nodeFilter);
|
||
}
|
||
return buf.join('');
|
||
},
|
||
/**
|
||
* Filters the NodeList based on a predicate.
|
||
*
|
||
* @param {function(Node): boolean} predicate
|
||
* - A predicate function to filter the NodeList.
|
||
* @returns {Node[]}
|
||
* An array of nodes that satisfy the predicate.
|
||
* @private
|
||
*/
|
||
filter: function (predicate) {
|
||
return Array.prototype.filter.call(this, predicate);
|
||
},
|
||
/**
|
||
* Returns the first index at which a given node can be found in the NodeList, or -1 if it is
|
||
* not present.
|
||
*
|
||
* @param {Node} item
|
||
* - The Node item to locate in the NodeList.
|
||
* @returns {number}
|
||
* The first index of the node in the NodeList; -1 if not found.
|
||
* @private
|
||
*/
|
||
indexOf: function (item) {
|
||
return Array.prototype.indexOf.call(this, item);
|
||
},
|
||
};
|
||
NodeList.prototype[Symbol.iterator] = function () {
|
||
var me = this;
|
||
var index = 0;
|
||
|
||
return {
|
||
next: function () {
|
||
if (index < me.length) {
|
||
return {
|
||
value: me[index++],
|
||
done: false,
|
||
};
|
||
} else {
|
||
return {
|
||
done: true,
|
||
};
|
||
}
|
||
},
|
||
return: function () {
|
||
return {
|
||
done: true,
|
||
};
|
||
},
|
||
};
|
||
};
|
||
|
||
/**
|
||
* Represents a live collection of nodes that is automatically updated when its associated
|
||
* document changes.
|
||
*
|
||
* @class LiveNodeList
|
||
* @param {Node} node
|
||
* The associated node.
|
||
* @param {function} refresh
|
||
* The function to refresh the live node list.
|
||
* @augments NodeList
|
||
* @constructs LiveNodeList
|
||
*/
|
||
function LiveNodeList(node, refresh) {
|
||
this._node = node;
|
||
this._refresh = refresh;
|
||
_updateLiveList(this);
|
||
}
|
||
/**
|
||
* Updates the live node list.
|
||
*
|
||
* @param {LiveNodeList} list
|
||
* The live node list to update.
|
||
* @private
|
||
*/
|
||
function _updateLiveList(list) {
|
||
var inc = list._node._inc || list._node.ownerDocument._inc;
|
||
if (list._inc !== inc) {
|
||
var ls = list._refresh(list._node);
|
||
__set__(list, 'length', ls.length);
|
||
if (!list.$$length || ls.length < list.$$length) {
|
||
for (var i = ls.length; i in list; i++) {
|
||
if (hasOwn(list, i)) {
|
||
delete list[i];
|
||
}
|
||
}
|
||
}
|
||
copy(ls, list);
|
||
list._inc = inc;
|
||
}
|
||
}
|
||
/**
|
||
* Returns the node at position `index` in the LiveNodeList, or null if that is not a valid
|
||
* index.
|
||
*
|
||
* @param {number} i
|
||
* Index into the collection.
|
||
* @returns {Node | null}
|
||
* The node at position `index` in the LiveNodeList, or null if that is not a valid index.
|
||
*/
|
||
LiveNodeList.prototype.item = function (i) {
|
||
_updateLiveList(this);
|
||
return this[i] || null;
|
||
};
|
||
|
||
_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
|
||
*
|
||
* This implementation only supports property indices, but does not support named properties,
|
||
* as specified in the living standard.
|
||
*
|
||
* @class NamedNodeMap
|
||
* @see https://dom.spec.whatwg.org/#interface-namednodemap
|
||
* @see https://webidl.spec.whatwg.org/#dfn-supported-property-names
|
||
* @constructs NamedNodeMap
|
||
*/
|
||
function NamedNodeMap() {}
|
||
/**
|
||
* Returns the index of a node within the list.
|
||
*
|
||
* @param {Array} list
|
||
* The list of nodes.
|
||
* @param {Node} node
|
||
* The node to find.
|
||
* @returns {number}
|
||
* The index of the node within the list, or -1 if not found.
|
||
* @private
|
||
*/
|
||
function _findNodeIndex(list, node) {
|
||
var i = 0;
|
||
while (i < list.length) {
|
||
if (list[i] === node) {
|
||
return i;
|
||
}
|
||
i++;
|
||
}
|
||
}
|
||
/**
|
||
* Adds a new attribute to the list and updates the owner element of the attribute.
|
||
*
|
||
* @param {Element} el
|
||
* The element which will become the owner of the new attribute.
|
||
* @param {NamedNodeMap} list
|
||
* The list to which the new attribute will be added.
|
||
* @param {Attr} newAttr
|
||
* The new attribute to be added.
|
||
* @param {Attr} oldAttr
|
||
* The old attribute to be replaced, or null if no attribute is to be replaced.
|
||
* @returns {void}
|
||
* @private
|
||
*/
|
||
function _addNamedNode(el, list, newAttr, oldAttr) {
|
||
if (oldAttr) {
|
||
list[_findNodeIndex(list, oldAttr)] = newAttr;
|
||
} else {
|
||
list[list.length] = newAttr;
|
||
list.length++;
|
||
}
|
||
if (el) {
|
||
newAttr.ownerElement = el;
|
||
var doc = el.ownerDocument;
|
||
if (doc) {
|
||
oldAttr && _onRemoveAttribute(doc, el, oldAttr);
|
||
_onAddAttribute(doc, el, newAttr);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Removes an attribute from the list and updates the owner element of the attribute.
|
||
*
|
||
* @param {Element} el
|
||
* The element which is the current owner of the attribute.
|
||
* @param {NamedNodeMap} list
|
||
* The list from which the attribute will be removed.
|
||
* @param {Attr} attr
|
||
* The attribute to be removed.
|
||
* @returns {void}
|
||
* @private
|
||
*/
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
NamedNodeMap.prototype = {
|
||
length: 0,
|
||
item: NodeList.prototype.item,
|
||
|
||
/**
|
||
* Get an attribute by name. Note: Name is in lower case in case of HTML namespace and
|
||
* document.
|
||
*
|
||
* @param {string} localName
|
||
* The local name of the attribute.
|
||
* @returns {Attr | null}
|
||
* The attribute with the given local name, or null if no such attribute exists.
|
||
* @see https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name
|
||
*/
|
||
getNamedItem: function (localName) {
|
||
if (this._ownerElement && this._ownerElement._isInHTMLDocumentAndNamespace()) {
|
||
localName = localName.toLowerCase();
|
||
}
|
||
var i = 0;
|
||
while (i < this.length) {
|
||
var attr = this[i];
|
||
if (attr.nodeName === localName) {
|
||
return attr;
|
||
}
|
||
i++;
|
||
}
|
||
return null;
|
||
},
|
||
|
||
/**
|
||
* Set an attribute.
|
||
*
|
||
* @param {Attr} attr
|
||
* The attribute to set.
|
||
* @returns {Attr | null}
|
||
* The old attribute with the same local name and namespace URI as the new one, or null if no
|
||
* such attribute exists.
|
||
* @throws {DOMException}
|
||
* With code:
|
||
* - {@link INUSE_ATTRIBUTE_ERR} - If the attribute is already an attribute of another
|
||
* element.
|
||
* @see https://dom.spec.whatwg.org/#concept-element-attributes-set
|
||
*/
|
||
setNamedItem: function (attr) {
|
||
var el = attr.ownerElement;
|
||
if (el && el !== this._ownerElement) {
|
||
throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR);
|
||
}
|
||
var oldAttr = this.getNamedItemNS(attr.namespaceURI, attr.localName);
|
||
if (oldAttr === attr) {
|
||
return attr;
|
||
}
|
||
_addNamedNode(this._ownerElement, this, attr, oldAttr);
|
||
return oldAttr;
|
||
},
|
||
|
||
/**
|
||
* Set an attribute, replacing an existing attribute with the same local name and namespace
|
||
* URI if one exists.
|
||
*
|
||
* @param {Attr} attr
|
||
* The attribute to set.
|
||
* @returns {Attr | null}
|
||
* The old attribute with the same local name and namespace URI as the new one, or null if no
|
||
* such attribute exists.
|
||
* @throws {DOMException}
|
||
* Throws a DOMException with the name "InUseAttributeError" if the attribute is already an
|
||
* attribute of another element.
|
||
* @see https://dom.spec.whatwg.org/#concept-element-attributes-set
|
||
*/
|
||
setNamedItemNS: function (attr) {
|
||
return this.setNamedItem(attr);
|
||
},
|
||
|
||
/**
|
||
* Removes an attribute specified by the local name.
|
||
*
|
||
* @param {string} localName
|
||
* The local name of the attribute to be removed.
|
||
* @returns {Attr}
|
||
* The attribute node that was removed.
|
||
* @throws {DOMException}
|
||
* With code:
|
||
* - {@link DOMException.NOT_FOUND_ERR} if no attribute with the given name is found.
|
||
* @see https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem
|
||
* @see https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-name
|
||
*/
|
||
removeNamedItem: function (localName) {
|
||
var attr = this.getNamedItem(localName);
|
||
if (!attr) {
|
||
throw new DOMException(DOMException.NOT_FOUND_ERR, localName);
|
||
}
|
||
_removeNamedNode(this._ownerElement, this, attr);
|
||
return attr;
|
||
},
|
||
|
||
/**
|
||
* Removes an attribute specified by the namespace and local name.
|
||
*
|
||
* @param {string | null} namespaceURI
|
||
* The namespace URI of the attribute to be removed.
|
||
* @param {string} localName
|
||
* The local name of the attribute to be removed.
|
||
* @returns {Attr}
|
||
* The attribute node that was removed.
|
||
* @throws {DOMException}
|
||
* With code:
|
||
* - {@link DOMException.NOT_FOUND_ERR} if no attribute with the given namespace URI and local
|
||
* name is found.
|
||
* @see https://dom.spec.whatwg.org/#dom-namednodemap-removenameditemns
|
||
* @see https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-namespace
|
||
*/
|
||
removeNamedItemNS: function (namespaceURI, localName) {
|
||
var attr = this.getNamedItemNS(namespaceURI, localName);
|
||
if (!attr) {
|
||
throw new DOMException(DOMException.NOT_FOUND_ERR, namespaceURI ? namespaceURI + ' : ' + localName : localName);
|
||
}
|
||
_removeNamedNode(this._ownerElement, this, attr);
|
||
return attr;
|
||
},
|
||
|
||
/**
|
||
* Get an attribute by namespace and local name.
|
||
*
|
||
* @param {string | null} namespaceURI
|
||
* The namespace URI of the attribute.
|
||
* @param {string} localName
|
||
* The local name of the attribute.
|
||
* @returns {Attr | null}
|
||
* The attribute with the given namespace URI and local name, or null if no such attribute
|
||
* exists.
|
||
* @see https://dom.spec.whatwg.org/#concept-element-attributes-get-by-namespace
|
||
*/
|
||
getNamedItemNS: function (namespaceURI, localName) {
|
||
if (!namespaceURI) {
|
||
namespaceURI = null;
|
||
}
|
||
var i = 0;
|
||
while (i < this.length) {
|
||
var node = this[i];
|
||
if (node.localName === localName && node.namespaceURI === namespaceURI) {
|
||
return node;
|
||
}
|
||
i++;
|
||
}
|
||
return null;
|
||
},
|
||
};
|
||
NamedNodeMap.prototype[Symbol.iterator] = function () {
|
||
var me = this;
|
||
var index = 0;
|
||
|
||
return {
|
||
next: function () {
|
||
if (index < me.length) {
|
||
return {
|
||
value: me[index++],
|
||
done: false,
|
||
};
|
||
} else {
|
||
return {
|
||
done: true,
|
||
};
|
||
}
|
||
},
|
||
return: function () {
|
||
return {
|
||
done: true,
|
||
};
|
||
},
|
||
};
|
||
};
|
||
|
||
/**
|
||
* The DOMImplementation interface provides a number of methods for performing operations that
|
||
* are independent of any particular instance of the document object model.
|
||
*
|
||
* 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**.
|
||
*
|
||
* @class DOMImplementation
|
||
* @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
|
||
* @constructs DOMImplementation
|
||
*/
|
||
function DOMImplementation() {}
|
||
|
||
DOMImplementation.prototype = {
|
||
/**
|
||
* Test if the DOM implementation implements a specific feature and version, as specified in
|
||
* {@link https://www.w3.org/TR/DOM-Level-3-Core/core.html#DOMFeatures DOM Features}.
|
||
*
|
||
* 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.
|
||
* @function DOMImplementation#hasFeature
|
||
* @param {string} feature
|
||
* The name of the feature to test.
|
||
* @param {string} [version]
|
||
* This is the version number of the feature to test.
|
||
* @returns {boolean}
|
||
* Always returns 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
|
||
* @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-5CED94D7 DOM Level 3 Core
|
||
*/
|
||
hasFeature: function (feature, version) {
|
||
return true;
|
||
},
|
||
/**
|
||
* Creates a DOM Document object of the specified type with its document element. Note that
|
||
* based on the {@link DocumentType}
|
||
* given to create the document, the implementation may instantiate specialized
|
||
* {@link Document} objects that support additional features than the "Core", such as "HTML"
|
||
* {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#DOM2HTML DOM Level 2 HTML}.
|
||
* On the other hand, setting the {@link DocumentType} after the document was created makes
|
||
* this very unlikely to happen. Alternatively, specialized {@link Document} creation methods,
|
||
* such as createHTMLDocument
|
||
* {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#DOM2HTML DOM Level 2 HTML},
|
||
* can be used to obtain specific types of {@link Document} objects.
|
||
*
|
||
* __It behaves slightly different from the description in the living standard__:
|
||
* - There is no interface/class `XMLDocument`, it returns a `Document`
|
||
* instance (with it's `type` set to `'xml'`).
|
||
* - `encoding`, `mode`, `origin`, `url` fields are currently not declared.
|
||
*
|
||
* @function DOMImplementation.createDocument
|
||
* @param {string | null} namespaceURI
|
||
* The
|
||
* {@link https://www.w3.org/TR/DOM-Level-3-Core/glossary.html#dt-namespaceURI namespace URI}
|
||
* of the document element to create or null.
|
||
* @param {string | null} qualifiedName
|
||
* The
|
||
* {@link https://www.w3.org/TR/DOM-Level-3-Core/glossary.html#dt-qualifiedname qualified name}
|
||
* of the document element to be created or null.
|
||
* @param {DocumentType | null} [doctype=null]
|
||
* The type of document to be created or null. When doctype is not null, its
|
||
* {@link Node#ownerDocument} attribute is set to the document being created. Default is
|
||
* `null`
|
||
* @returns {Document}
|
||
* A new {@link Document} object with its document element. If the NamespaceURI,
|
||
* qualifiedName, and doctype are null, the returned {@link Document} is empty with no
|
||
* document element.
|
||
* @throws {DOMException}
|
||
* With code:
|
||
*
|
||
* - `INVALID_CHARACTER_ERR`: Raised if the specified qualified name is not an XML name
|
||
* according to {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#XML XML 1.0}.
|
||
* - `NAMESPACE_ERR`: Raised if the qualifiedName is malformed, if the qualifiedName has a
|
||
* prefix and the namespaceURI is null, or if the qualifiedName is null and the namespaceURI
|
||
* is different from null, or if the qualifiedName has a prefix that is "xml" and the
|
||
* namespaceURI is different from "{@link http://www.w3.org/XML/1998/namespace}"
|
||
* {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#Namespaces XML Namespaces},
|
||
* or if the DOM implementation does not support the "XML" feature but a non-null namespace
|
||
* URI was provided, since namespaces were defined by XML.
|
||
* - `WRONG_DOCUMENT_ERR`: Raised if doctype has already been used with a different document
|
||
* or was created from a different implementation.
|
||
* - `NOT_SUPPORTED_ERR`: May be raised if the implementation does not support the feature
|
||
* "XML" and the language exposed through the Document does not support XML Namespaces (such
|
||
* as {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#HTML40 HTML 4.01}).
|
||
* @since DOM Level 2.
|
||
* @see {@link #createHTMLDocument}
|
||
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN
|
||
* @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument DOM Living Standard
|
||
* @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Level-2-Core-DOM-createDocument DOM
|
||
* Level 3 Core
|
||
* @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM
|
||
* Level 2 Core (initial)
|
||
*/
|
||
createDocument: function (namespaceURI, qualifiedName, doctype) {
|
||
var contentType = MIME_TYPE.XML_APPLICATION;
|
||
if (namespaceURI === NAMESPACE.HTML) {
|
||
contentType = MIME_TYPE.XML_XHTML_APPLICATION;
|
||
} else if (namespaceURI === NAMESPACE.SVG) {
|
||
contentType = MIME_TYPE.XML_SVG_IMAGE;
|
||
}
|
||
var doc = new Document(PDC, { contentType: contentType });
|
||
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;
|
||
},
|
||
/**
|
||
* Creates an empty DocumentType node. Entity declarations and notations are not made
|
||
* available. Entity reference expansions and default attribute additions do not occur.
|
||
*
|
||
* **This behavior is slightly different from the one in the specs**:
|
||
* - `encoding`, `mode`, `origin`, `url` fields are currently not declared.
|
||
* - `publicId` and `systemId` contain the raw data including any possible quotes,
|
||
* so they can always be serialized back to the original value
|
||
* - `internalSubset` contains the raw string between `[` and `]` if present,
|
||
* but is not parsed or validated in any form.
|
||
*
|
||
* @function DOMImplementation#createDocumentType
|
||
* @param {string} qualifiedName
|
||
* The {@link https://www.w3.org/TR/DOM-Level-3-Core/glossary.html#dt-qualifiedname qualified
|
||
* name} of the document type to be created.
|
||
* @param {string} [publicId]
|
||
* The external subset public identifier.
|
||
* @param {string} [systemId]
|
||
* The external subset system identifier.
|
||
* @param {string} [internalSubset]
|
||
* the internal subset or an empty string if it is not present
|
||
* @returns {DocumentType}
|
||
* A new {@link DocumentType} node with {@link Node#ownerDocument} set to null.
|
||
* @throws {DOMException}
|
||
* With code:
|
||
*
|
||
* - `INVALID_CHARACTER_ERR`: Raised if the specified qualified name is not an XML name
|
||
* according to {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#XML XML 1.0}.
|
||
* - `NAMESPACE_ERR`: Raised if the qualifiedName is malformed.
|
||
* - `NOT_SUPPORTED_ERR`: May be raised if the implementation does not support the feature
|
||
* "XML" and the language exposed through the Document does not support XML Namespaces (such
|
||
* as {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#HTML40 HTML 4.01}).
|
||
* @since DOM Level 2.
|
||
* @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType
|
||
* MDN
|
||
* @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living
|
||
* Standard
|
||
* @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Level-3-Core-DOM-createDocType DOM
|
||
* Level 3 Core
|
||
* @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM
|
||
* Level 2 Core
|
||
* @see https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md#050
|
||
* @see https://www.w3.org/TR/DOM-Level-2-Core/#core-ID-Core-DocType-internalSubset
|
||
* @prettierignore
|
||
*/
|
||
createDocumentType: function (qualifiedName, publicId, systemId, internalSubset) {
|
||
validateQualifiedName(qualifiedName);
|
||
var node = new DocumentType(PDC);
|
||
node.name = qualifiedName;
|
||
node.nodeName = qualifiedName;
|
||
node.publicId = publicId || '';
|
||
node.systemId = systemId || '';
|
||
node.internalSubset = internalSubset || '';
|
||
node.childNodes = new NodeList();
|
||
|
||
return node;
|
||
},
|
||
/**
|
||
* Returns an HTML document, that might already have a basic DOM structure.
|
||
*
|
||
* __It behaves slightly different from the description in the living standard__:
|
||
* - If the first argument is `false` no initial nodes are added (steps 3-7 in the specs are
|
||
* omitted)
|
||
* - `encoding`, `mode`, `origin`, `url` fields are currently not declared.
|
||
*
|
||
* @param {string | false} [title]
|
||
* A string containing the title to give the new HTML document.
|
||
* @returns {Document}
|
||
* The HTML document.
|
||
* @since WHATWG Living Standard.
|
||
* @see {@link #createDocument}
|
||
* @see https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument
|
||
* @see https://dom.spec.whatwg.org/#html-document
|
||
*/
|
||
createHTMLDocument: function (title) {
|
||
var doc = new Document(PDC, { contentType: MIME_TYPE.HTML });
|
||
doc.implementation = this;
|
||
doc.childNodes = new NodeList();
|
||
if (title !== false) {
|
||
doc.doctype = this.createDocumentType('html');
|
||
doc.doctype.ownerDocument = doc;
|
||
doc.appendChild(doc.doctype);
|
||
var htmlNode = doc.createElement('html');
|
||
doc.appendChild(htmlNode);
|
||
var headNode = doc.createElement('head');
|
||
htmlNode.appendChild(headNode);
|
||
if (typeof title === 'string') {
|
||
var titleNode = doc.createElement('title');
|
||
titleNode.appendChild(doc.createTextNode(title));
|
||
headNode.appendChild(titleNode);
|
||
}
|
||
htmlNode.appendChild(doc.createElement('body'));
|
||
}
|
||
return doc;
|
||
},
|
||
};
|
||
|
||
/**
|
||
* The DOM Node interface is an abstract base class upon which many other DOM API objects are
|
||
* based, thus letting those object types to be used similarly and often interchangeably. As an
|
||
* abstract class, there is no such thing as a plain Node object. All objects that implement
|
||
* Node functionality are based on one of its subclasses. Most notable are Document, Element,
|
||
* and DocumentFragment.
|
||
*
|
||
* In addition, every kind of DOM node is represented by an interface based on Node. These
|
||
* include Attr, CharacterData (which Text, Comment, CDATASection and ProcessingInstruction are
|
||
* all based on), and DocumentType.
|
||
*
|
||
* In some cases, a particular feature of the base Node interface may not apply to one of its
|
||
* child interfaces; in that case, the inheriting node may return null or throw an exception,
|
||
* depending on circumstances. For example, attempting to add children to a node type that
|
||
* cannot have children will throw an exception.
|
||
*
|
||
* **This behavior is slightly different from the in the specs**:
|
||
* - unimplemented interfaces: `EventTarget`
|
||
*
|
||
* @class
|
||
* @abstract
|
||
* @param {Symbol} symbol
|
||
* @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247
|
||
* @see https://dom.spec.whatwg.org/#node
|
||
* @prettierignore
|
||
*/
|
||
function Node(symbol) {
|
||
checkSymbol(symbol);
|
||
}
|
||
|
||
Node.prototype = {
|
||
/**
|
||
* The first child of this node.
|
||
*
|
||
* @type {Node | null}
|
||
*/
|
||
firstChild: null,
|
||
/**
|
||
* The last child of this node.
|
||
*
|
||
* @type {Node | null}
|
||
*/
|
||
lastChild: null,
|
||
/**
|
||
* The previous sibling of this node.
|
||
*
|
||
* @type {Node | null}
|
||
*/
|
||
previousSibling: null,
|
||
/**
|
||
* The next sibling of this node.
|
||
*
|
||
* @type {Node | null}
|
||
*/
|
||
nextSibling: null,
|
||
/**
|
||
* The parent node of this node.
|
||
*
|
||
* @type {Node | null}
|
||
*/
|
||
parentNode: null,
|
||
/**
|
||
* The parent element of this node.
|
||
*
|
||
* @type {Element | null}
|
||
*/
|
||
get parentElement() {
|
||
return this.parentNode && this.parentNode.nodeType === this.ELEMENT_NODE ? this.parentNode : null;
|
||
},
|
||
/**
|
||
* The child nodes of this node.
|
||
*
|
||
* @type {NodeList}
|
||
*/
|
||
childNodes: null,
|
||
/**
|
||
* The document object associated with this node.
|
||
*
|
||
* @type {Document | null}
|
||
*/
|
||
ownerDocument: null,
|
||
/**
|
||
* The value of this node.
|
||
*
|
||
* @type {string | null}
|
||
*/
|
||
nodeValue: null,
|
||
/**
|
||
* The namespace URI of this node.
|
||
*
|
||
* @type {string | null}
|
||
*/
|
||
namespaceURI: null,
|
||
/**
|
||
* The prefix of the namespace for this node.
|
||
*
|
||
* @type {string | null}
|
||
*/
|
||
prefix: null,
|
||
/**
|
||
* The local part of the qualified name of this node.
|
||
*
|
||
* @type {string | null}
|
||
*/
|
||
localName: null,
|
||
/**
|
||
* The baseURI is currently always `about:blank`,
|
||
* since that's what happens when you create a document from scratch.
|
||
*
|
||
* @type {'about:blank'}
|
||
*/
|
||
baseURI: 'about:blank',
|
||
/**
|
||
* Is true if this node is part of a document.
|
||
*
|
||
* @type {boolean}
|
||
*/
|
||
get isConnected() {
|
||
var rootNode = this.getRootNode();
|
||
return rootNode && rootNode.nodeType === rootNode.DOCUMENT_NODE;
|
||
},
|
||
/**
|
||
* Checks whether `other` is an inclusive descendant of this node.
|
||
*
|
||
* @param {Node | null | undefined} other
|
||
* The node to check.
|
||
* @returns {boolean}
|
||
* True if `other` is an inclusive descendant of this node; false otherwise.
|
||
* @see https://dom.spec.whatwg.org/#dom-node-contains
|
||
*/
|
||
contains: function (other) {
|
||
if (!other) return false;
|
||
var parent = other;
|
||
do {
|
||
if (this === parent) return true;
|
||
parent = other.parentNode;
|
||
} while (parent);
|
||
return false;
|
||
},
|
||
/**
|
||
* @typedef GetRootNodeOptions
|
||
* @property {boolean} [composed=false]
|
||
*/
|
||
/**
|
||
* Searches for the root node of this node.
|
||
*
|
||
* **This behavior is slightly different from the in the specs**:
|
||
* - ignores `options.composed`, since `ShadowRoot`s are unsupported, always returns root.
|
||
*
|
||
* @param {GetRootNodeOptions} [options]
|
||
* @returns {Node}
|
||
* Root node.
|
||
* @see https://dom.spec.whatwg.org/#dom-node-getrootnode
|
||
* @see https://dom.spec.whatwg.org/#concept-shadow-including-root
|
||
*/
|
||
getRootNode: function (options) {
|
||
var parent = this;
|
||
do {
|
||
if (!parent.parentNode) {
|
||
return parent;
|
||
}
|
||
parent = parent.parentNode;
|
||
} while (parent);
|
||
},
|
||
/**
|
||
* Checks whether the given node is equal to this node.
|
||
*
|
||
* @param {Node} [otherNode]
|
||
* @see https://dom.spec.whatwg.org/#concept-node-equals
|
||
*/
|
||
isEqualNode: function (otherNode) {
|
||
if (!otherNode) return false;
|
||
|
||
if (this.nodeType !== otherNode.nodeType) return false;
|
||
|
||
switch (this.nodeType) {
|
||
case this.DOCUMENT_TYPE_NODE:
|
||
if (this.name !== otherNode.name) return false;
|
||
if (this.publicId !== otherNode.publicId) return false;
|
||
if (this.systemId !== otherNode.systemId) return false;
|
||
break;
|
||
case this.ELEMENT_NODE:
|
||
if (this.namespaceURI !== otherNode.namespaceURI) return false;
|
||
if (this.prefix !== otherNode.prefix) return false;
|
||
if (this.localName !== otherNode.localName) return false;
|
||
if (this.attributes.length !== otherNode.attributes.length) return false;
|
||
for (var i = 0; i < this.attributes.length; i++) {
|
||
var attr = this.attributes.item(i);
|
||
if (!attr.isEqualNode(otherNode.getAttributeNodeNS(attr.namespaceURI, attr.localName))) {
|
||
return false;
|
||
}
|
||
}
|
||
break;
|
||
case this.ATTRIBUTE_NODE:
|
||
if (this.namespaceURI !== otherNode.namespaceURI) return false;
|
||
if (this.localName !== otherNode.localName) return false;
|
||
if (this.value !== otherNode.value) return false;
|
||
|
||
break;
|
||
case this.PROCESSING_INSTRUCTION_NODE:
|
||
if (this.target !== otherNode.target || this.data !== otherNode.data) {
|
||
return false;
|
||
}
|
||
break;
|
||
case this.TEXT_NODE:
|
||
case this.COMMENT_NODE:
|
||
if (this.data !== otherNode.data) return false;
|
||
break;
|
||
}
|
||
|
||
if (this.childNodes.length !== otherNode.childNodes.length) {
|
||
return false;
|
||
}
|
||
|
||
for (var i = 0; i < this.childNodes.length; i++) {
|
||
if (!this.childNodes[i].isEqualNode(otherNode.childNodes[i])) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
},
|
||
/**
|
||
* Checks whether or not the given node is this node.
|
||
*
|
||
* @param {Node} [otherNode]
|
||
*/
|
||
isSameNode: function (otherNode) {
|
||
return this === otherNode;
|
||
},
|
||
/**
|
||
* Inserts a node before a reference node as a child of this node.
|
||
*
|
||
* @param {Node} newChild
|
||
* The new child node to be inserted.
|
||
* @param {Node | null} refChild
|
||
* The reference node before which newChild will be inserted.
|
||
* @returns {Node}
|
||
* The new child node successfully inserted.
|
||
* @throws {DOMException}
|
||
* Throws a DOMException if inserting the node would result in a DOM tree that is not
|
||
* well-formed, or if `child` is provided but is not a child of `parent`.
|
||
* See {@link _insertBefore} for more details.
|
||
* @since Modified in DOM L2
|
||
*/
|
||
insertBefore: function (newChild, refChild) {
|
||
return _insertBefore(this, newChild, refChild);
|
||
},
|
||
/**
|
||
* Replaces an old child node with a new child node within this node.
|
||
*
|
||
* @param {Node} newChild
|
||
* The new node that is to replace the old node.
|
||
* If it already exists in the DOM, it is removed from its original position.
|
||
* @param {Node} oldChild
|
||
* The existing child node to be replaced.
|
||
* @returns {Node}
|
||
* Returns the replaced child node.
|
||
* @throws {DOMException}
|
||
* Throws a DOMException if replacing the node would result in a DOM tree that is not
|
||
* well-formed, or if `oldChild` is not a child of `this`.
|
||
* This can also occur if the pre-replacement validity assertion fails.
|
||
* See {@link _insertBefore}, {@link Node.removeChild}, and
|
||
* {@link assertPreReplacementValidityInDocument} for more details.
|
||
* @see https://dom.spec.whatwg.org/#concept-node-replace
|
||
*/
|
||
replaceChild: function (newChild, oldChild) {
|
||
_insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument);
|
||
if (oldChild) {
|
||
this.removeChild(oldChild);
|
||
}
|
||
},
|
||
/**
|
||
* Removes an existing child node from this node.
|
||
*
|
||
* @param {Node} oldChild
|
||
* The child node to be removed.
|
||
* @returns {Node}
|
||
* Returns the removed child node.
|
||
* @throws {DOMException}
|
||
* Throws a DOMException if `oldChild` is not a child of `this`.
|
||
* See {@link _removeChild} for more details.
|
||
*/
|
||
removeChild: function (oldChild) {
|
||
return _removeChild(this, oldChild);
|
||
},
|
||
/**
|
||
* Appends a child node to this node.
|
||
*
|
||
* @param {Node} newChild
|
||
* The child node to be appended to this node.
|
||
* If it already exists in the DOM, it is removed from its original position.
|
||
* @returns {Node}
|
||
* Returns the appended child node.
|
||
* @throws {DOMException}
|
||
* Throws a DOMException if appending the node would result in a DOM tree that is not
|
||
* well-formed, or if `newChild` is not a valid Node.
|
||
* See {@link insertBefore} for more details.
|
||
*/
|
||
appendChild: function (newChild) {
|
||
return this.insertBefore(newChild, null);
|
||
},
|
||
/**
|
||
* Determines whether this node has any child nodes.
|
||
*
|
||
* @returns {boolean}
|
||
* Returns true if this node has any child nodes, and false otherwise.
|
||
*/
|
||
hasChildNodes: function () {
|
||
return this.firstChild != null;
|
||
},
|
||
/**
|
||
* Creates a copy of the calling node.
|
||
*
|
||
* @param {boolean} deep
|
||
* If true, the contents of the node are recursively copied.
|
||
* If false, only the node itself (and its attributes, if it is an element) are copied.
|
||
* @returns {Node}
|
||
* Returns the newly created copy of the node.
|
||
* @throws {DOMException}
|
||
* May throw a DOMException if operations within {@link Element#setAttributeNode} or
|
||
* {@link Node#appendChild} (which are potentially invoked in this method) do not meet their
|
||
* specific constraints.
|
||
* @see {@link cloneNode}
|
||
*/
|
||
cloneNode: function (deep) {
|
||
return cloneNode(this.ownerDocument || this, this, deep);
|
||
},
|
||
/**
|
||
* Puts the specified node and all of its subtree into a "normalized" form. In a normalized
|
||
* subtree, no text nodes in the subtree are empty and there are no adjacent text nodes.
|
||
*
|
||
* Specifically, this method merges any adjacent text nodes (i.e., nodes for which `nodeType`
|
||
* is `TEXT_NODE`) into a single node with the combined data. It also removes any empty text
|
||
* nodes.
|
||
*
|
||
* This method operates recursively, so it also normalizes any and all descendent nodes within
|
||
* the subtree.
|
||
*
|
||
* @throws {DOMException}
|
||
* May throw a DOMException if operations within removeChild or appendData (which are
|
||
* potentially invoked in this method) do not meet their specific constraints.
|
||
* @since Modified in DOM Level 2
|
||
* @see {@link Node.removeChild}
|
||
* @see {@link CharacterData.appendData}
|
||
*/
|
||
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;
|
||
}
|
||
}
|
||
},
|
||
/**
|
||
* Checks whether the DOM implementation implements a specific feature and its version.
|
||
*
|
||
* @deprecated
|
||
* Since `DOMImplementation.hasFeature` is deprecated and always returns true.
|
||
* @param {string} feature
|
||
* The package name of the feature to test. This is the same name that can be passed to the
|
||
* method `hasFeature` on `DOMImplementation`.
|
||
* @param {string} version
|
||
* This is the version number of the package name to test.
|
||
* @returns {boolean}
|
||
* Returns true in all cases in the current implementation.
|
||
* @since Introduced in DOM Level 2
|
||
* @see {@link DOMImplementation.hasFeature}
|
||
*/
|
||
isSupported: function (feature, version) {
|
||
return this.ownerDocument.implementation.hasFeature(feature, version);
|
||
},
|
||
/**
|
||
* 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.
|
||
*
|
||
* **This behavior is different from the in the specs**:
|
||
* - no node type specific handling
|
||
* - uses the internal attribute _nsMap for resolving namespaces that is updated when changing attributes
|
||
*
|
||
* @param {string | null} namespaceURI
|
||
* The namespace URI for which to find the associated prefix.
|
||
* @returns {string | null}
|
||
* The associated prefix, if found; otherwise, 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
|
||
* @prettierignore
|
||
*/
|
||
lookupPrefix: function (namespaceURI) {
|
||
var el = this;
|
||
while (el) {
|
||
var map = el._nsMap;
|
||
//console.dir(map)
|
||
if (map) {
|
||
for (var n in map) {
|
||
if (hasOwn(map, n) && map[n] === namespaceURI) {
|
||
return n;
|
||
}
|
||
}
|
||
}
|
||
el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode;
|
||
}
|
||
return null;
|
||
},
|
||
/**
|
||
* This function is used to look up the namespace URI associated with the given prefix,
|
||
* starting from this node.
|
||
*
|
||
* **This behavior is different from the in the specs**:
|
||
* - no node type specific handling
|
||
* - uses the internal attribute _nsMap for resolving namespaces that is updated when changing attributes
|
||
*
|
||
* @param {string | null} prefix
|
||
* The prefix for which to find the associated namespace URI.
|
||
* @returns {string | null}
|
||
* The associated namespace URI, if found; otherwise, null.
|
||
* @since DOM Level 3
|
||
* @see https://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri
|
||
* @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI
|
||
* @prettierignore
|
||
*/
|
||
lookupNamespaceURI: function (prefix) {
|
||
var el = this;
|
||
while (el) {
|
||
var map = el._nsMap;
|
||
//console.dir(map)
|
||
if (map) {
|
||
if (hasOwn(map, prefix)) {
|
||
return map[prefix];
|
||
}
|
||
}
|
||
el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode;
|
||
}
|
||
return null;
|
||
},
|
||
/**
|
||
* Determines whether the given namespace URI is the default namespace.
|
||
*
|
||
* The function works by looking up the prefix associated with the given namespace URI. If no
|
||
* prefix is found (i.e., the namespace URI is not registered in the namespace map of this
|
||
* node or any of its ancestors), it returns `true`, implying the namespace URI is considered
|
||
* the default.
|
||
*
|
||
* **This behavior is different from the in the specs**:
|
||
* - no node type specific handling
|
||
* - uses the internal attribute _nsMap for resolving namespaces that is updated when changing attributes
|
||
*
|
||
* @param {string | null} namespaceURI
|
||
* The namespace URI to be checked.
|
||
* @returns {boolean}
|
||
* Returns true if the given namespace URI is the default namespace, false otherwise.
|
||
* @since DOM Level 3
|
||
* @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-isDefaultNamespace
|
||
* @see https://dom.spec.whatwg.org/#dom-node-isdefaultnamespace
|
||
* @prettierignore
|
||
*/
|
||
isDefaultNamespace: function (namespaceURI) {
|
||
var prefix = this.lookupPrefix(namespaceURI);
|
||
return prefix == null;
|
||
},
|
||
/**
|
||
* Compares the reference node with a node with regard to their position in the document and
|
||
* according to the document order.
|
||
*
|
||
* @param {Node} other
|
||
* The node to compare the reference node to.
|
||
* @returns {number}
|
||
* Returns how the node is positioned relatively to the reference node according to the
|
||
* bitmask. 0 if reference node and given node are the same.
|
||
* @since DOM Level 3
|
||
* @see https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Node3-compare
|
||
* @see https://dom.spec.whatwg.org/#dom-node-comparedocumentposition
|
||
*/
|
||
compareDocumentPosition: function (other) {
|
||
if (this === other) return 0;
|
||
var node1 = other;
|
||
var node2 = this;
|
||
var attr1 = null;
|
||
var attr2 = null;
|
||
if (node1 instanceof Attr) {
|
||
attr1 = node1;
|
||
node1 = attr1.ownerElement;
|
||
}
|
||
if (node2 instanceof Attr) {
|
||
attr2 = node2;
|
||
node2 = attr2.ownerElement;
|
||
if (attr1 && node1 && node2 === node1) {
|
||
for (var i = 0, attr; (attr = node2.attributes[i]); i++) {
|
||
if (attr === attr1)
|
||
return DocumentPosition.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DocumentPosition.DOCUMENT_POSITION_PRECEDING;
|
||
if (attr === attr2)
|
||
return DocumentPosition.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DocumentPosition.DOCUMENT_POSITION_FOLLOWING;
|
||
}
|
||
}
|
||
}
|
||
if (!node1 || !node2 || node2.ownerDocument !== node1.ownerDocument) {
|
||
return (
|
||
DocumentPosition.DOCUMENT_POSITION_DISCONNECTED +
|
||
DocumentPosition.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC +
|
||
(docGUID(node2.ownerDocument) > docGUID(node1.ownerDocument)
|
||
? DocumentPosition.DOCUMENT_POSITION_FOLLOWING
|
||
: DocumentPosition.DOCUMENT_POSITION_PRECEDING)
|
||
);
|
||
}
|
||
if (attr2 && node1 === node2) {
|
||
return DocumentPosition.DOCUMENT_POSITION_CONTAINS + DocumentPosition.DOCUMENT_POSITION_PRECEDING;
|
||
}
|
||
if (attr1 && node1 === node2) {
|
||
return DocumentPosition.DOCUMENT_POSITION_CONTAINED_BY + DocumentPosition.DOCUMENT_POSITION_FOLLOWING;
|
||
}
|
||
|
||
var chain1 = [];
|
||
var ancestor1 = node1.parentNode;
|
||
while (ancestor1) {
|
||
if (!attr2 && ancestor1 === node2) {
|
||
return DocumentPosition.DOCUMENT_POSITION_CONTAINED_BY + DocumentPosition.DOCUMENT_POSITION_FOLLOWING;
|
||
}
|
||
chain1.push(ancestor1);
|
||
ancestor1 = ancestor1.parentNode;
|
||
}
|
||
chain1.reverse();
|
||
|
||
var chain2 = [];
|
||
var ancestor2 = node2.parentNode;
|
||
while (ancestor2) {
|
||
if (!attr1 && ancestor2 === node1) {
|
||
return DocumentPosition.DOCUMENT_POSITION_CONTAINS + DocumentPosition.DOCUMENT_POSITION_PRECEDING;
|
||
}
|
||
chain2.push(ancestor2);
|
||
ancestor2 = ancestor2.parentNode;
|
||
}
|
||
chain2.reverse();
|
||
|
||
var ca = commonAncestor(chain1, chain2);
|
||
for (var n in ca.childNodes) {
|
||
var child = ca.childNodes[n];
|
||
if (child === node2) return DocumentPosition.DOCUMENT_POSITION_FOLLOWING;
|
||
if (child === node1) return DocumentPosition.DOCUMENT_POSITION_PRECEDING;
|
||
if (chain2.indexOf(child) >= 0) return DocumentPosition.DOCUMENT_POSITION_FOLLOWING;
|
||
if (chain1.indexOf(child) >= 0) return DocumentPosition.DOCUMENT_POSITION_PRECEDING;
|
||
}
|
||
return 0;
|
||
},
|
||
};
|
||
|
||
/**
|
||
* Encodes special XML characters to their corresponding entities.
|
||
*
|
||
* @param {string} c
|
||
* The character to be encoded.
|
||
* @returns {string}
|
||
* The encoded character.
|
||
* @private
|
||
*/
|
||
function _xmlEncoder(c) {
|
||
return (
|
||
(c == '<' && '<') || (c == '>' && '>') || (c == '&' && '&') || (c == '"' && '"') || '&#' + c.charCodeAt() + ';'
|
||
);
|
||
}
|
||
|
||
copy(NodeType, Node);
|
||
copy(NodeType, Node.prototype);
|
||
copy(DocumentPosition, Node);
|
||
copy(DocumentPosition, Node.prototype);
|
||
|
||
/**
|
||
* @param callback
|
||
* Return true for continue,false for break.
|
||
* @returns
|
||
* 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));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @typedef DocumentOptions
|
||
* @property {string} [contentType=MIME_TYPE.XML_APPLICATION]
|
||
*/
|
||
/**
|
||
* The Document interface describes the common properties and methods for any kind of document.
|
||
*
|
||
* It should usually be created using `new DOMImplementation().createDocument(...)`
|
||
* or `new DOMImplementation().createHTMLDocument(...)`.
|
||
*
|
||
* The constructor is considered a private API and offers to initially set the `contentType`
|
||
* property via it's options parameter.
|
||
*
|
||
* @class
|
||
* @param {Symbol} symbol
|
||
* @param {DocumentOptions} [options]
|
||
* @augments Node
|
||
* @private
|
||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document
|
||
* @see https://dom.spec.whatwg.org/#interface-document
|
||
*/
|
||
function Document(symbol, options) {
|
||
checkSymbol(symbol);
|
||
|
||
var opt = options || {};
|
||
this.ownerDocument = this;
|
||
/**
|
||
* The mime type of the document is determined at creation time and can not be modified.
|
||
*
|
||
* @type {string}
|
||
* @see https://dom.spec.whatwg.org/#concept-document-content-type
|
||
* @see {@link DOMImplementation}
|
||
* @see {@link MIME_TYPE}
|
||
* @readonly
|
||
*/
|
||
this.contentType = opt.contentType || MIME_TYPE.XML_APPLICATION;
|
||
/**
|
||
* @type {'html' | 'xml'}
|
||
* @see https://dom.spec.whatwg.org/#concept-document-type
|
||
* @see {@link DOMImplementation}
|
||
* @readonly
|
||
*/
|
||
this.type = isHTMLMimeType(this.contentType) ? 'html' : 'xml';
|
||
}
|
||
|
||
/**
|
||
* Updates the namespace mapping of an element when a new attribute is added.
|
||
*
|
||
* @param {Document} doc
|
||
* The document that the element belongs to.
|
||
* @param {Element} el
|
||
* The element to which the attribute is being added.
|
||
* @param {Attr} newAttr
|
||
* The new attribute being added.
|
||
* @private
|
||
*/
|
||
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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Updates the namespace mapping of an element when an attribute is removed.
|
||
*
|
||
* @param {Document} doc
|
||
* The document that the element belongs to.
|
||
* @param {Element} el
|
||
* The element from which the attribute is being removed.
|
||
* @param {Attr} newAttr
|
||
* The attribute being removed.
|
||
* @param {boolean} remove
|
||
* Indicates whether the attribute is to be removed.
|
||
* @private
|
||
*/
|
||
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 `parent.childNodes`, adjusting the indexed items and its `length`.
|
||
* If `newChild` is provided and has no nextSibling, it will be appended.
|
||
* Otherwise, it's assumed that an item has been removed or inserted,
|
||
* and `parent.firstNode` and its `.nextSibling` to re-indexing all child nodes of `parent`.
|
||
*
|
||
* @param {Document} doc
|
||
* The parent document of `el`.
|
||
* @param {Node} parent
|
||
* The parent node whose childNodes list needs to be updated.
|
||
* @param {Node} [newChild]
|
||
* The new child node to be appended. If not provided, the function assumes a node has been
|
||
* removed.
|
||
* @private
|
||
*/
|
||
function _onUpdateChild(doc, parent, newChild) {
|
||
if (doc && doc._inc) {
|
||
doc._inc++;
|
||
var childNodes = parent.childNodes;
|
||
// assumes nextSibling and previousSibling were already configured upfront
|
||
if (newChild && !newChild.nextSibling) {
|
||
// if an item has been appended, we only need to update the last index and the length
|
||
childNodes[childNodes.length++] = newChild;
|
||
} else {
|
||
// otherwise we need to reindex all items,
|
||
// which can take a while when processing nodes with a lot of children
|
||
var child = parent.firstChild;
|
||
var i = 0;
|
||
while (child) {
|
||
childNodes[i++] = child;
|
||
child = child.nextSibling;
|
||
}
|
||
childNodes.length = i;
|
||
delete childNodes[childNodes.length];
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Removes the connections between `parentNode` and `child`
|
||
* and any existing `child.previousSibling` or `child.nextSibling`.
|
||
*
|
||
* @param {Node} parentNode
|
||
* The parent node from which the child node is to be removed.
|
||
* @param {Node} child
|
||
* The child node to be removed from the parentNode.
|
||
* @returns {Node}
|
||
* Returns the child node that was removed.
|
||
* @throws {DOMException}
|
||
* With code:
|
||
* - {@link DOMException.NOT_FOUND_ERR} If the parentNode is not the parent of the child node.
|
||
* @private
|
||
* @see https://github.com/xmldom/xmldom/issues/135
|
||
* @see https://github.com/xmldom/xmldom/issues/145
|
||
*/
|
||
function _removeChild(parentNode, child) {
|
||
if (parentNode !== child.parentNode) {
|
||
throw new DOMException(DOMException.NOT_FOUND_ERR, "child's parent is not parent");
|
||
}
|
||
var oldPreviousSibling = child.previousSibling;
|
||
var oldNextSibling = child.nextSibling;
|
||
if (oldPreviousSibling) {
|
||
oldPreviousSibling.nextSibling = oldNextSibling;
|
||
} else {
|
||
parentNode.firstChild = oldNextSibling;
|
||
}
|
||
if (oldNextSibling) {
|
||
oldNextSibling.previousSibling = oldPreviousSibling;
|
||
} else {
|
||
parentNode.lastChild = oldPreviousSibling;
|
||
}
|
||
_onUpdateChild(parentNode.ownerDocument, parentNode);
|
||
child.parentNode = null;
|
||
child.previousSibling = null;
|
||
child.nextSibling = null;
|
||
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 &&
|
||
(node.nodeType === Node.CDATA_SECTION_NODE ||
|
||
node.nodeType === Node.COMMENT_NODE ||
|
||
node.nodeType === Node.DOCUMENT_FRAGMENT_NODE ||
|
||
node.nodeType === Node.DOCUMENT_TYPE_NODE ||
|
||
node.nodeType === Node.ELEMENT_NODE ||
|
||
node.nodeType === Node.PROCESSING_INSTRUCTION_NODE ||
|
||
node.nodeType === Node.TEXT_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
|
||
*/
|
||
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
|
||
*/
|
||
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));
|
||
}
|
||
|
||
/**
|
||
* Asserts pre-insertion validity of a node into a parent before a child.
|
||
* Throws errors for invalid node combinations that would result in an ill-formed DOM.
|
||
*
|
||
* @param {Node} parent
|
||
* The parent node to insert `node` into.
|
||
* @param {Node} node
|
||
* The node to insert.
|
||
* @param {Node | null} child
|
||
* The node that should become the `nextSibling` of `node`. If null, no sibling is considered.
|
||
* @throws {DOMException}
|
||
* With code:
|
||
* - {@link DOMException.HIERARCHY_REQUEST_ERR} If `parent` is not a Document,
|
||
* DocumentFragment, or Element node.
|
||
* - {@link DOMException.HIERARCHY_REQUEST_ERR} If `node` is a host-including inclusive
|
||
* ancestor of `parent`. (Currently not implemented)
|
||
* - {@link DOMException.NOT_FOUND_ERR} If `child` is non-null and its `parent` is not
|
||
* `parent`.
|
||
* - {@link DOMException.HIERARCHY_REQUEST_ERR} If `node` is not a DocumentFragment,
|
||
* DocumentType, Element, or CharacterData node.
|
||
* - {@link DOMException.HIERARCHY_REQUEST_ERR} If either `node` is a Text node and `parent` is
|
||
* a document, or if `node` is a doctype and `parent` is not a document.
|
||
* @private
|
||
* @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(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(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(
|
||
DOMException.HIERARCHY_REQUEST_ERR,
|
||
'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Asserts pre-insertion validity of a node into a document before a child.
|
||
* Throws errors for invalid node combinations that would result in an ill-formed DOM.
|
||
*
|
||
* @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`. If undefined, no sibling is
|
||
* considered.
|
||
* @returns {Node}
|
||
* @throws {DOMException}
|
||
* With code:
|
||
* - {@link DOMException.HIERARCHY_REQUEST_ERR} If `node` is a DocumentFragment with more than
|
||
* one element child or has a Text node child.
|
||
* - {@link DOMException.HIERARCHY_REQUEST_ERR} If `node` is a DocumentFragment with 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`.
|
||
* - {@link DOMException.HIERARCHY_REQUEST_ERR} If `node` is an Element and `parent` has an
|
||
* element child, `child` is a doctype, or `child` is non-null and a doctype is following
|
||
* `child`.
|
||
* - {@link DOMException.HIERARCHY_REQUEST_ERR} If `node` is a DocumentType and `parent` has a
|
||
* doctype child, `child` is non-null and an element is preceding `child`, or `child` is null
|
||
* and `parent` has an element child.
|
||
* @private
|
||
* @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(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(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(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(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(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(DOMException.HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @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`.
|
||
* @private
|
||
* @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(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(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(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(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(DOMException.HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Inserts a node into a parent node before a child node.
|
||
*
|
||
* @param {Node} parent
|
||
* The parent node to insert the node into.
|
||
* @param {Node} node
|
||
* The node to insert into the parent.
|
||
* @param {Node | null} child
|
||
* The node that should become the next sibling of the node.
|
||
* If null, the function inserts the node at the end of the children of the parent node.
|
||
* @param {Function} [_inDocumentAssertion]
|
||
* An optional function to check pre-insertion validity if parent is a document node.
|
||
* Defaults to {@link assertPreInsertionValidityInDocument}
|
||
* @returns {Node}
|
||
* Returns the inserted node.
|
||
* @throws {DOMException}
|
||
* Throws a DOMException if inserting the node would result in a DOM tree that is not
|
||
* well-formed. See {@link assertPreInsertionValidity1to5},
|
||
* {@link assertPreInsertionValidityInDocument}.
|
||
* @throws {DOMException}
|
||
* Throws a DOMException if child is provided but is not a child of the parent. See
|
||
* {@link Node.removeChild}
|
||
* @private
|
||
* @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, node);
|
||
if (node.nodeType == DOCUMENT_FRAGMENT_NODE) {
|
||
node.firstChild = node.lastChild = null;
|
||
}
|
||
|
||
return node;
|
||
}
|
||
|
||
Document.prototype = {
|
||
/**
|
||
* The implementation that created this document.
|
||
*
|
||
* @type DOMImplementation
|
||
* @readonly
|
||
*/
|
||
implementation: null,
|
||
nodeName: '#document',
|
||
nodeType: DOCUMENT_NODE,
|
||
/**
|
||
* The DocumentType node of the document.
|
||
*
|
||
* @type DocumentType
|
||
* @readonly
|
||
*/
|
||
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) {
|
||
var removed = _removeChild(this, oldChild);
|
||
if (removed === this.documentElement) {
|
||
this.documentElement = null;
|
||
}
|
||
return removed;
|
||
},
|
||
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;
|
||
},
|
||
|
||
/**
|
||
* Creates a new `Element` that is owned by this `Document`.
|
||
* In HTML Documents `localName` is the lower cased `tagName`,
|
||
* otherwise no transformation is being applied.
|
||
* When `contentType` implies the HTML namespace, it will be set as `namespaceURI`.
|
||
*
|
||
* __This implementation differs from the specification:__ - The provided name is not checked
|
||
* against the `Name` production,
|
||
* so no related error will be thrown.
|
||
* - There is no interface `HTMLElement`, it is always an `Element`.
|
||
* - There is no support for a second argument to indicate using custom elements.
|
||
*
|
||
* @param {string} tagName
|
||
* @returns {Element}
|
||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement
|
||
* @see https://dom.spec.whatwg.org/#dom-document-createelement
|
||
* @see https://dom.spec.whatwg.org/#concept-create-element
|
||
*/
|
||
createElement: function (tagName) {
|
||
var node = new Element(PDC);
|
||
node.ownerDocument = this;
|
||
if (this.type === 'html') {
|
||
tagName = tagName.toLowerCase();
|
||
}
|
||
if (hasDefaultHTMLNamespace(this.contentType)) {
|
||
node.namespaceURI = NAMESPACE.HTML;
|
||
}
|
||
node.nodeName = tagName;
|
||
node.tagName = tagName;
|
||
node.localName = tagName;
|
||
node.childNodes = new NodeList();
|
||
var attrs = (node.attributes = new NamedNodeMap());
|
||
attrs._ownerElement = node;
|
||
return node;
|
||
},
|
||
/**
|
||
* @returns {DocumentFragment}
|
||
*/
|
||
createDocumentFragment: function () {
|
||
var node = new DocumentFragment(PDC);
|
||
node.ownerDocument = this;
|
||
node.childNodes = new NodeList();
|
||
return node;
|
||
},
|
||
/**
|
||
* @param {string} data
|
||
* @returns {Text}
|
||
*/
|
||
createTextNode: function (data) {
|
||
var node = new Text(PDC);
|
||
node.ownerDocument = this;
|
||
node.childNodes = new NodeList();
|
||
node.appendData(data);
|
||
return node;
|
||
},
|
||
/**
|
||
* @param {string} data
|
||
* @returns {Comment}
|
||
*/
|
||
createComment: function (data) {
|
||
var node = new Comment(PDC);
|
||
node.ownerDocument = this;
|
||
node.childNodes = new NodeList();
|
||
node.appendData(data);
|
||
return node;
|
||
},
|
||
/**
|
||
* @param {string} data
|
||
* @returns {CDATASection}
|
||
*/
|
||
createCDATASection: function (data) {
|
||
var node = new CDATASection(PDC);
|
||
node.ownerDocument = this;
|
||
node.childNodes = new NodeList();
|
||
node.appendData(data);
|
||
return node;
|
||
},
|
||
/**
|
||
* @param {string} target
|
||
* @param {string} data
|
||
* @returns {ProcessingInstruction}
|
||
*/
|
||
createProcessingInstruction: function (target, data) {
|
||
var node = new ProcessingInstruction(PDC);
|
||
node.ownerDocument = this;
|
||
node.childNodes = new NodeList();
|
||
node.nodeName = node.target = target;
|
||
node.nodeValue = node.data = data;
|
||
return node;
|
||
},
|
||
/**
|
||
* Creates an `Attr` node that is owned by this document.
|
||
* In HTML Documents `localName` is the lower cased `name`,
|
||
* otherwise no transformation is being applied.
|
||
*
|
||
* __This implementation differs from the specification:__ - The provided name is not checked
|
||
* against the `Name` production,
|
||
* so no related error will be thrown.
|
||
*
|
||
* @param {string} name
|
||
* @returns {Attr}
|
||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createAttribute
|
||
* @see https://dom.spec.whatwg.org/#dom-document-createattribute
|
||
*/
|
||
createAttribute: function (name) {
|
||
if (!g.QName_exact.test(name)) {
|
||
throw new DOMException(DOMException.INVALID_CHARACTER_ERR, 'invalid character in name "' + name + '"');
|
||
}
|
||
if (this.type === 'html') {
|
||
name = name.toLowerCase();
|
||
}
|
||
return this._createAttribute(name);
|
||
},
|
||
_createAttribute: function (name) {
|
||
var node = new Attr(PDC);
|
||
node.ownerDocument = this;
|
||
node.childNodes = new NodeList();
|
||
node.name = name;
|
||
node.nodeName = name;
|
||
node.localName = name;
|
||
node.specified = true;
|
||
return node;
|
||
},
|
||
/**
|
||
* Creates an EntityReference object.
|
||
* The current implementation does not fill the `childNodes` with those of the corresponding
|
||
* `Entity`
|
||
*
|
||
* @deprecated
|
||
* In DOM Level 4.
|
||
* @param {string} name
|
||
* The name of the entity to reference. No namespace well-formedness checks are performed.
|
||
* @returns {EntityReference}
|
||
* @throws {DOMException}
|
||
* With code `INVALID_CHARACTER_ERR` when `name` is not valid.
|
||
* @throws {DOMException}
|
||
* with code `NOT_SUPPORTED_ERR` when the document is of type `html`
|
||
* @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-392B75AE
|
||
*/
|
||
createEntityReference: function (name) {
|
||
if (!g.Name.test(name)) {
|
||
throw new DOMException(DOMException.INVALID_CHARACTER_ERR, 'not a valid xml name "' + name + '"');
|
||
}
|
||
if (this.type === 'html') {
|
||
throw new DOMException('document is an html document', DOMExceptionName.NotSupportedError);
|
||
}
|
||
|
||
var node = new EntityReference(PDC);
|
||
node.ownerDocument = this;
|
||
node.childNodes = new NodeList();
|
||
node.nodeName = name;
|
||
return node;
|
||
},
|
||
// Introduced in DOM Level 2:
|
||
/**
|
||
* @param {string} namespaceURI
|
||
* @param {string} qualifiedName
|
||
* @returns {Element}
|
||
*/
|
||
createElementNS: function (namespaceURI, qualifiedName) {
|
||
var validated = validateAndExtract(namespaceURI, qualifiedName);
|
||
var node = new Element(PDC);
|
||
var attrs = (node.attributes = new NamedNodeMap());
|
||
node.childNodes = new NodeList();
|
||
node.ownerDocument = this;
|
||
node.nodeName = qualifiedName;
|
||
node.tagName = qualifiedName;
|
||
node.namespaceURI = validated[0];
|
||
node.prefix = validated[1];
|
||
node.localName = validated[2];
|
||
attrs._ownerElement = node;
|
||
return node;
|
||
},
|
||
// Introduced in DOM Level 2:
|
||
/**
|
||
* @param {string} namespaceURI
|
||
* @param {string} qualifiedName
|
||
* @returns {Attr}
|
||
*/
|
||
createAttributeNS: function (namespaceURI, qualifiedName) {
|
||
var validated = validateAndExtract(namespaceURI, qualifiedName);
|
||
var node = new Attr(PDC);
|
||
node.ownerDocument = this;
|
||
node.childNodes = new NodeList();
|
||
node.nodeName = qualifiedName;
|
||
node.name = qualifiedName;
|
||
node.specified = true;
|
||
node.namespaceURI = validated[0];
|
||
node.prefix = validated[1];
|
||
node.localName = validated[2];
|
||
return node;
|
||
},
|
||
};
|
||
_extends(Document, Node);
|
||
|
||
function Element(symbol) {
|
||
checkSymbol(symbol);
|
||
|
||
this._nsMap = Object.create(null);
|
||
}
|
||
Element.prototype = {
|
||
nodeType: ELEMENT_NODE,
|
||
/**
|
||
* The attributes of this element.
|
||
*
|
||
* @type {NamedNodeMap | null}
|
||
*/
|
||
attributes: null,
|
||
getQualifiedName: function () {
|
||
return this.prefix ? this.prefix + ':' + this.localName : this.localName;
|
||
},
|
||
_isInHTMLDocumentAndNamespace: function () {
|
||
return this.ownerDocument.type === 'html' && this.namespaceURI === NAMESPACE.HTML;
|
||
},
|
||
/**
|
||
* Implementaton of Level2 Core function hasAttributes.
|
||
*
|
||
* @returns {boolean}
|
||
* True if attribute list is not empty.
|
||
* @see https://www.w3.org/TR/DOM-Level-2-Core/#core-ID-NodeHasAttrs
|
||
*/
|
||
hasAttributes: function () {
|
||
return !!(this.attributes && this.attributes.length);
|
||
},
|
||
hasAttribute: function (name) {
|
||
return !!this.getAttributeNode(name);
|
||
},
|
||
/**
|
||
* Returns element’s first attribute whose qualified name is `name`, and `null`
|
||
* if there is no such attribute.
|
||
*
|
||
* @param {string} name
|
||
* @returns {string | null}
|
||
*/
|
||
getAttribute: function (name) {
|
||
var attr = this.getAttributeNode(name);
|
||
return attr ? attr.value : null;
|
||
},
|
||
getAttributeNode: function (name) {
|
||
if (this._isInHTMLDocumentAndNamespace()) {
|
||
name = name.toLowerCase();
|
||
}
|
||
return this.attributes.getNamedItem(name);
|
||
},
|
||
/**
|
||
* Sets the value of element’s first attribute whose qualified name is qualifiedName to value.
|
||
*
|
||
* @param {string} name
|
||
* @param {string} value
|
||
*/
|
||
setAttribute: function (name, value) {
|
||
if (this._isInHTMLDocumentAndNamespace()) {
|
||
name = name.toLowerCase();
|
||
}
|
||
var attr = this.getAttributeNode(name);
|
||
if (attr) {
|
||
attr.value = attr.nodeValue = '' + value;
|
||
} else {
|
||
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);
|
||
},
|
||
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;
|
||
},
|
||
/**
|
||
* Returns element’s attribute whose namespace is `namespaceURI` and local name is
|
||
* `localName`,
|
||
* or `null` if there is no such attribute.
|
||
*
|
||
* @param {string} namespaceURI
|
||
* @param {string} localName
|
||
* @returns {string | null}
|
||
*/
|
||
getAttributeNS: function (namespaceURI, localName) {
|
||
var attr = this.getAttributeNodeNS(namespaceURI, localName);
|
||
return attr ? attr.value : null;
|
||
},
|
||
/**
|
||
* Sets the value of element’s attribute whose namespace is `namespaceURI` and local name is
|
||
* `localName` to value.
|
||
*
|
||
* @param {string} namespaceURI
|
||
* @param {string} qualifiedName
|
||
* @param {string} value
|
||
* @see https://dom.spec.whatwg.org/#dom-element-setattributens
|
||
*/
|
||
setAttributeNS: function (namespaceURI, qualifiedName, value) {
|
||
var validated = validateAndExtract(namespaceURI, qualifiedName);
|
||
var localName = validated[2];
|
||
var attr = this.getAttributeNodeNS(namespaceURI, localName);
|
||
if (attr) {
|
||
attr.value = attr.nodeValue = '' + value;
|
||
} else {
|
||
attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName);
|
||
attr.value = attr.nodeValue = '' + value;
|
||
this.setAttributeNode(attr);
|
||
}
|
||
},
|
||
getAttributeNodeNS: function (namespaceURI, localName) {
|
||
return this.attributes.getNamedItemNS(namespaceURI, localName);
|
||
},
|
||
|
||
/**
|
||
* Returns a LiveNodeList of all child elements which have **all** of the given class name(s).
|
||
*
|
||
* Returns an empty list if `classNames` is an empty string or only contains HTML white space
|
||
* characters.
|
||
*
|
||
* Warning: This returns 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/Element/getElementsByClassName
|
||
* @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, 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;
|
||
});
|
||
},
|
||
|
||
/**
|
||
* Returns a LiveNodeList of elements with the given qualifiedName.
|
||
* Searching for all descendants can be done by passing `*` as `qualifiedName`.
|
||
*
|
||
* All descendants of the specified element are searched, but not the element itself.
|
||
* The returned list is live, which means it updates itself with the DOM tree automatically.
|
||
* Therefore, there is no need to call `Element.getElementsByTagName()`
|
||
* with the same element and arguments repeatedly if the DOM changes in between calls.
|
||
*
|
||
* When called on an HTML element in an HTML document,
|
||
* `getElementsByTagName` lower-cases the argument before searching for it.
|
||
* This is undesirable when trying to match camel-cased SVG elements (such as
|
||
* `<linearGradient>`) in an HTML document.
|
||
* Instead, use `Element.getElementsByTagNameNS()`,
|
||
* which preserves the capitalization of the tag name.
|
||
*
|
||
* `Element.getElementsByTagName` is similar to `Document.getElementsByTagName()`,
|
||
* except that it only searches for elements that are descendants of the specified element.
|
||
*
|
||
* @param {string} qualifiedName
|
||
* @returns {LiveNodeList}
|
||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByTagName
|
||
* @see https://dom.spec.whatwg.org/#concept-getelementsbytagname
|
||
*/
|
||
getElementsByTagName: function (qualifiedName) {
|
||
var isHTMLDocument = (this.nodeType === DOCUMENT_NODE ? this : this.ownerDocument).type === 'html';
|
||
var lowerQualifiedName = qualifiedName.toLowerCase();
|
||
return new LiveNodeList(this, function (base) {
|
||
var ls = [];
|
||
_visitNode(base, function (node) {
|
||
if (node === base || node.nodeType !== ELEMENT_NODE) {
|
||
return;
|
||
}
|
||
if (qualifiedName === '*') {
|
||
ls.push(node);
|
||
} else {
|
||
var nodeQualifiedName = node.getQualifiedName();
|
||
var matchingQName = isHTMLDocument && node.namespaceURI === NAMESPACE.HTML ? lowerQualifiedName : qualifiedName;
|
||
if (nodeQualifiedName === matchingQName) {
|
||
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.getElementsByClassName = Element.prototype.getElementsByClassName;
|
||
Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName;
|
||
Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS;
|
||
|
||
_extends(Element, Node);
|
||
function Attr(symbol) {
|
||
checkSymbol(symbol);
|
||
|
||
this.namespaceURI = null;
|
||
this.prefix = null;
|
||
this.ownerElement = null;
|
||
}
|
||
Attr.prototype.nodeType = ATTRIBUTE_NODE;
|
||
_extends(Attr, Node);
|
||
|
||
function CharacterData(symbol) {
|
||
checkSymbol(symbol);
|
||
}
|
||
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);
|
||
},
|
||
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(symbol) {
|
||
checkSymbol(symbol);
|
||
}
|
||
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(symbol) {
|
||
checkSymbol(symbol);
|
||
}
|
||
Comment.prototype = {
|
||
nodeName: '#comment',
|
||
nodeType: COMMENT_NODE,
|
||
};
|
||
_extends(Comment, CharacterData);
|
||
|
||
function CDATASection(symbol) {
|
||
checkSymbol(symbol);
|
||
}
|
||
CDATASection.prototype = {
|
||
nodeName: '#cdata-section',
|
||
nodeType: CDATA_SECTION_NODE,
|
||
};
|
||
_extends(CDATASection, Text);
|
||
|
||
function DocumentType(symbol) {
|
||
checkSymbol(symbol);
|
||
}
|
||
DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE;
|
||
_extends(DocumentType, Node);
|
||
|
||
function Notation(symbol) {
|
||
checkSymbol(symbol);
|
||
}
|
||
Notation.prototype.nodeType = NOTATION_NODE;
|
||
_extends(Notation, Node);
|
||
|
||
function Entity(symbol) {
|
||
checkSymbol(symbol);
|
||
}
|
||
Entity.prototype.nodeType = ENTITY_NODE;
|
||
_extends(Entity, Node);
|
||
|
||
function EntityReference(symbol) {
|
||
checkSymbol(symbol);
|
||
}
|
||
EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE;
|
||
_extends(EntityReference, Node);
|
||
|
||
function DocumentFragment(symbol) {
|
||
checkSymbol(symbol);
|
||
}
|
||
DocumentFragment.prototype.nodeName = '#document-fragment';
|
||
DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE;
|
||
_extends(DocumentFragment, Node);
|
||
|
||
function ProcessingInstruction(symbol) {
|
||
checkSymbol(symbol);
|
||
}
|
||
ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE;
|
||
_extends(ProcessingInstruction, CharacterData);
|
||
function XMLSerializer() {}
|
||
XMLSerializer.prototype.serializeToString = function (node, nodeFilter) {
|
||
return nodeSerializeToString.call(node, nodeFilter);
|
||
};
|
||
Node.prototype.toString = nodeSerializeToString;
|
||
function nodeSerializeToString(nodeFilter) {
|
||
var buf = [];
|
||
var refNode = (this.nodeType === DOCUMENT_NODE && this.documentElement) || this;
|
||
var prefix = refNode.prefix;
|
||
var uri = refNode.namespaceURI;
|
||
|
||
if (uri && prefix == null) {
|
||
var prefix = refNode.lookupPrefix(uri);
|
||
if (prefix == null) {
|
||
var visibleNamespaces = [
|
||
{ namespace: uri, prefix: null },
|
||
//{namespace:uri,prefix:''}
|
||
];
|
||
}
|
||
}
|
||
serializeToString(this, buf, nodeFilter, visibleNamespaces);
|
||
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;
|
||
}
|
||
/**
|
||
* 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).
|
||
*
|
||
* 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
|
||
* @see https://www.w3.org/TR/xml11/#AVNormalize
|
||
* @see https://w3c.github.io/DOM-Parsing/#serializing-an-element-s-attributes
|
||
* @prettierignore
|
||
*/
|
||
function addSerializedAttribute(buf, qualifiedName, value) {
|
||
buf.push(' ', qualifiedName, '="', value.replace(/[<>&"\t\n\r]/g, _xmlEncoder), '"');
|
||
}
|
||
|
||
function serializeToString(node, buf, nodeFilter, visibleNamespaces) {
|
||
if (!visibleNamespaces) {
|
||
visibleNamespaces = [];
|
||
}
|
||
var doc = node.nodeType === DOCUMENT_NODE ? node : node.ownerDocument;
|
||
var isHTML = doc.type === 'html';
|
||
|
||
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;
|
||
|
||
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, 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 });
|
||
}
|
||
// in XML elements can be closed when they have no children
|
||
var canCloseTag = !child;
|
||
if (canCloseTag && (isHTML || node.namespaceURI === NAMESPACE.HTML)) {
|
||
// in HTML (doc or ns) only void elements can be closed right away
|
||
canCloseTag = isHTMLVoidElement(nodeName);
|
||
}
|
||
if (canCloseTag) {
|
||
buf.push('/>');
|
||
} else {
|
||
buf.push('>');
|
||
//if is cdata child node
|
||
if (isHTML && isHTMLRawTextElement(nodeName)) {
|
||
while (child) {
|
||
if (child.data) {
|
||
buf.push(child.data);
|
||
} else {
|
||
serializeToString(child, buf, nodeFilter, visibleNamespaces.slice());
|
||
}
|
||
child = child.nextSibling;
|
||
}
|
||
} else {
|
||
while (child) {
|
||
serializeToString(child, buf, nodeFilter, visibleNamespaces.slice());
|
||
child = child.nextSibling;
|
||
}
|
||
}
|
||
buf.push('</', prefixedNodeName, '>');
|
||
}
|
||
// remove added visible namespaces
|
||
//visibleNamespaces.length = startVisibleNamespaces;
|
||
return;
|
||
case DOCUMENT_NODE:
|
||
case DOCUMENT_FRAGMENT_NODE:
|
||
var child = node.firstChild;
|
||
while (child) {
|
||
serializeToString(child, buf, 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(g.CDATA_START, node.data, g.CDATA_END);
|
||
case COMMENT_NODE:
|
||
return buf.push(g.COMMENT_START, node.data, g.COMMENT_END);
|
||
case DOCUMENT_TYPE_NODE:
|
||
var pubid = node.publicId;
|
||
var sysid = node.systemId;
|
||
buf.push(g.DOCTYPE_DECL_START, ' ', node.name);
|
||
if (pubid) {
|
||
buf.push(' ', g.PUBLIC, ' ', pubid);
|
||
if (sysid && sysid !== '.') {
|
||
buf.push(' ', sysid);
|
||
}
|
||
} else if (sysid && sysid !== '.') {
|
||
buf.push(' ', g.SYSTEM, ' ', sysid);
|
||
}
|
||
if (node.internalSubset) {
|
||
buf.push(' [', node.internalSubset, ']');
|
||
}
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* Creates a copy of a node from an existing one.
|
||
*
|
||
* @param {Document} doc
|
||
* The Document object representing the document that the new node will belong to.
|
||
* @param {Node} node
|
||
* The node to clone.
|
||
* @param {boolean} deep
|
||
* If true, the contents of the node are recursively copied.
|
||
* If false, only the node itself (and its attributes, if it is an element) are copied.
|
||
* @returns {Node}
|
||
* Returns the newly created copy of the node.
|
||
* @throws {DOMException}
|
||
* May throw a DOMException if operations within setAttributeNode or appendChild (which are
|
||
* potentially invoked in this function) do not meet their specific constraints.
|
||
*/
|
||
function cloneNode(doc, node, deep) {
|
||
var node2 = new node.constructor(PDC);
|
||
for (var n in node) {
|
||
if (hasOwn(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
|
||
}
|
||
|
||
exports._updateLiveList = _updateLiveList;
|
||
exports.Attr = Attr;
|
||
exports.CDATASection = CDATASection;
|
||
exports.CharacterData = CharacterData;
|
||
exports.Comment = Comment;
|
||
exports.Document = Document;
|
||
exports.DocumentFragment = DocumentFragment;
|
||
exports.DocumentType = DocumentType;
|
||
exports.DOMImplementation = DOMImplementation;
|
||
exports.Element = Element;
|
||
exports.Entity = Entity;
|
||
exports.EntityReference = EntityReference;
|
||
exports.LiveNodeList = LiveNodeList;
|
||
exports.NamedNodeMap = NamedNodeMap;
|
||
exports.Node = Node;
|
||
exports.NodeList = NodeList;
|
||
exports.Notation = Notation;
|
||
exports.Text = Text;
|
||
exports.ProcessingInstruction = ProcessingInstruction;
|
||
exports.XMLSerializer = XMLSerializer;
|