/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/** * This is a relatively lightweight DOMParser that is safe to use in a web * worker. This is far from a complete DOM implementation; however, it should * contain the minimal set of functionality necessary for Readability.js. * * Aside from not implementing the full DOM API, there are other quirks to be * aware of when using the JSDOMParser: * * 1) Properly formed HTML/XML must be used. This means you should be extra * careful when using this parser on anything received directly from an * XMLHttpRequest. Providing a serialized string from an XMLSerializer, * however, should be safe (since the browser's XMLSerializer should * generate valid HTML/XML). Therefore, if parsing a document from an XHR, * the recommended approach is to do the XHR in the main thread, use * XMLSerializer.serializeToString() on the responseXML, and pass the * resulting string to the worker. * * 2) Live NodeLists are not supported. DOM methods and properties such as * getElementsByTagName() and childNodes return standard arrays. If you * want these lists to be updated when nodes are removed or added to the * document, you must take care to manually update them yourself.
*/
(function (global) {
function getElementsByTagName(tag) {
tag = tag.toUpperCase(); var elems = []; var allTags = (tag === "*"); function getElems(node) { var length = node.children.length; for (var i = 0; i < length; i++) { var child = node.children[i]; if (allTags || (child.tagName === tag))
elems.push(child);
getElems(child);
}
}
getElems(this);
elems._isLiveNodeList = true; return elems;
}
removeChild: function (child) { var childNodes = this.childNodes; var childIndex = childNodes.indexOf(child); if (childIndex === -1) { throw"removeChild: node not found";
} else {
child.parentNode = null; var prev = child.previousSibling; var next = child.nextSibling; if (prev)
prev.nextSibling = next; if (next)
next.previousSibling = prev;
if (child.nodeType === Node.ELEMENT_NODE) {
prev = child.previousElementSibling;
next = child.nextElementSibling; if (prev)
prev.nextElementSibling = next; if (next)
next.previousElementSibling = prev; this.children.splice(this.children.indexOf(child), 1);
}
replaceChild: function (newNode, oldNode) { var childNodes = this.childNodes; var childIndex = childNodes.indexOf(oldNode); if (childIndex === -1) { throw"replaceChild: node not found";
} else { // This will take care of updating the new node if it was somewhere else before: if (newNode.parentNode)
newNode.parentNode.removeChild(newNode);
childNodes[childIndex] = newNode;
// update the new node's sibling properties, and its new siblings' sibling properties
newNode.nextSibling = oldNode.nextSibling;
newNode.previousSibling = oldNode.previousSibling; if (newNode.nextSibling)
newNode.nextSibling.previousSibling = newNode; if (newNode.previousSibling)
newNode.previousSibling.nextSibling = newNode;
newNode.parentNode = this;
// Now deal with elements before we clear out those values for the old node, // because it can help us take shortcuts here: if (newNode.nodeType === Node.ELEMENT_NODE) { if (oldNode.nodeType === Node.ELEMENT_NODE) { // Both were elements, which makes this easier, we just swap things out:
newNode.previousElementSibling = oldNode.previousElementSibling;
newNode.nextElementSibling = oldNode.nextElementSibling; if (newNode.previousElementSibling)
newNode.previousElementSibling.nextElementSibling = newNode; if (newNode.nextElementSibling)
newNode.nextElementSibling.previousElementSibling = newNode; this.children[this.children.indexOf(oldNode)] = newNode;
} else { // Hard way:
newNode.previousElementSibling = (function() { for (var i = childIndex - 1; i >= 0; i--) { if (childNodes[i].nodeType === Node.ELEMENT_NODE) return childNodes[i];
} returnnull;
})(); if (newNode.previousElementSibling) {
newNode.nextElementSibling = newNode.previousElementSibling.nextElementSibling;
} else {
newNode.nextElementSibling = (function() { for (var i = childIndex + 1; i < childNodes.length; i++) { if (childNodes[i].nodeType === Node.ELEMENT_NODE) return childNodes[i];
} returnnull;
})();
} if (newNode.previousElementSibling)
newNode.previousElementSibling.nextElementSibling = newNode; if (newNode.nextElementSibling)
newNode.nextElementSibling.previousElementSibling = newNode;
if (newNode.nextElementSibling) this.children.splice(this.children.indexOf(newNode.nextElementSibling), 0, newNode); else this.children.push(newNode);
}
} elseif (oldNode.nodeType === Node.ELEMENT_NODE) { // new node is not an element node. // if the old one was, update its element siblings: if (oldNode.previousElementSibling)
oldNode.previousElementSibling.nextElementSibling = oldNode.nextElementSibling; if (oldNode.nextElementSibling)
oldNode.nextElementSibling.previousElementSibling = oldNode.previousElementSibling; this.children.splice(this.children.indexOf(oldNode), 1);
// If the old node wasn't an element, neither the new nor the old node was an element, // and the children array and its members shouldn't need any updating.
}
getElementById: function (id) { function getElem(node) { var length = node.children.length; if (node.id === id) return node; for (var i = 0; i < length; i++) { var el = getElem(node.children[i]); if (el) return el;
} returnnull;
} return getElem(this);
},
createElement: function (tag) { var node = new Element(tag); return node;
},
createTextNode: function (text) { var node = new Text();
node.textContent = text; return node;
},
get baseURI() { if (!this.hasOwnProperty("_baseURI")) { this._baseURI = this.documentURI; var baseElements = this.getElementsByTagName("base"); var href = baseElements[0] && baseElements[0].getAttribute("href"); if (href) { try { this._baseURI = (new URL(href, this._baseURI)).href;
} catch (ex) {/* Just fall back to documentURI */}
}
} returnthis._baseURI;
},
};
var Element = function (tag) { // We use this to find the closing tag. this._matchingTag = tag; // We're explicitly a non-namespace aware parser, we just pretend it's all HTML. var lastColonIndex = tag.lastIndexOf(":"); if (lastColonIndex != -1) {
tag = tag.substring(lastColonIndex + 1);
} this.attributes = []; this.childNodes = []; this.children = []; this.nextElementSibling = this.previousElementSibling = null; this.localName = tag.toLowerCase(); this.tagName = tag.toUpperCase(); this.style = new Style(this);
};
Element.prototype = {
__proto__: Node.prototype,
nodeType: Node.ELEMENT_NODE,
getElementsByTagName: getElementsByTagName,
get className() { returnthis.getAttribute("class") || "";
},
set className(str) { this.setAttribute("class", str);
},
get id() { returnthis.getAttribute("id") || "";
},
set id(str) { this.setAttribute("id", str);
},
get href() { returnthis.getAttribute("href") || "";
},
set href(str) { this.setAttribute("href", str);
},
get src() { returnthis.getAttribute("src") || "";
},
set src(str) { this.setAttribute("src", str);
},
get srcset() { returnthis.getAttribute("srcset") || "";
},
set srcset(str) { this.setAttribute("srcset", str);
},
get nodeName() { returnthis.tagName;
},
get innerHTML() { function getHTML(node) { var i = 0; for (i = 0; i < node.childNodes.length; i++) { var child = node.childNodes[i]; if (child.localName) {
arr.push("<" + child.localName);
// serialize attribute list for (var j = 0; j < child.attributes.length; j++) { var attr = child.attributes[j]; // the attribute value will be HTML escaped. var val = attr.getEncodedValue(); var quote = (val.indexOf('"') === -1 ? '"' : "'");
arr.push(" " + attr.name + "=" + quote + val + quote);
}
if (child.localName in voidElems && !child.childNodes.length) { // if this is a self-closing element, end it here
arr.push("/>");
} else { // otherwise, add its children
arr.push(">");
getHTML(child);
arr.push("" + child.localName + ">");
}
} else { // This is a text node, so asking for innerHTML won't recurse.
arr.push(child.innerHTML);
}
}
}
// Using Array.join() avoids the overhead from lazy string concatenation. var arr = [];
getHTML(this); return arr.join("");
},
set innerHTML(html) { var parser = new JSDOMParser(); var node = parser.parse(html); var i; for (i = this.childNodes.length; --i >= 0;) { this.childNodes[i].parentNode = null;
} this.childNodes = node.childNodes; this.children = node.children; for (i = this.childNodes.length; --i >= 0;) { this.childNodes[i].parentNode = this;
}
},
set textContent(text) { // clear parentNodes for existing children for (var i = this.childNodes.length; --i >= 0;) { this.childNodes[i].parentNode = null;
}
var node = new Text(); this.childNodes = [ node ]; this.children = [];
node.textContent = text;
node.parentNode = this;
},
get textContent() { function getText(node) { var nodes = node.childNodes; for (var i = 0; i < nodes.length; i++) { var child = nodes[i]; if (child.nodeType === 3) {
text.push(child.textContent);
} else {
getText(child);
}
}
}
var Style = function (node) { this.node = node;
};
// getStyle() and setStyle() use the style attribute string directly. This // won't be very efficient if there are a lot of style manipulations, but // it's the easiest way to make sure the style attribute string and the JS // style property stay in sync. Readability.js doesn't do many style // manipulations, so this should be okay.
Style.prototype = {
getStyle: function (styleName) { var attr = this.node.getAttribute("style"); if (!attr) return undefined;
var styles = attr.split(";"); for (var i = 0; i < styles.length; i++) { var style = styles[i].split(":"); var name = style[0].trim(); if (name === styleName) return style[1].trim();
}
return undefined;
},
setStyle: function (styleName, styleValue) { var value = this.node.getAttribute("style") || ""; var index = 0; do { var next = value.indexOf(";", index) + 1; var length = next - index - 1; var style = (length > 0 ? value.substr(index, length) : value.substr(index)); if (style.substr(0, style.indexOf(":")).trim() === styleName) {
value = value.substr(0, index).trim() + (next ? " " + value.substr(next).trim() : ""); break;
}
index = next;
} while (index);
// For each item in styleMap, define a getter and setter on the style // property. for (var jsName in styleMap) {
(function (cssName) {
Style.prototype.__defineGetter__(jsName, function () { returnthis.getStyle(cssName);
});
Style.prototype.__defineSetter__(jsName, function (value) { this.setStyle(cssName, value);
});
})(styleMap[jsName]);
}
var JSDOMParser = function () { this.currentChar = 0;
// In makeElementNode() we build up many strings one char at a time. Using // += for this results in lots of short-lived intermediate strings. It's // better to build an array of single-char strings and then join() them // together at the end. And reusing a single array (i.e. |this.strBuf|) // over and over for this purpose uses less memory than using a new array // for each string. this.strBuf = [];
// Similarly, we reuse this array to return the two arguments from // makeElementNode(), which saves us from having to allocate a new array // every time. this.retPair = [];
this.errorState = "";
};
JSDOMParser.prototype = {
error: function(m) { if (typeof console !== "undefined") {
console.log("JSDOMParser error: " + m + "\n");
} elseif (typeof dump !== "undefined") { /* global dump */
dump("JSDOMParser error: " + m + "\n");
} this.errorState += m + "\n";
},
/** * Look at the next character without advancing the index.
*/
peekNext: function () { returnthis.html[this.currentChar];
},
/** * Get the next character and advance the index.
*/
nextChar: function () { returnthis.html[this.currentChar++];
},
/** * Called after a quote character is read. This finds the next quote * character and returns the text string in between.
*/
readString: function (quote) { var str; var n = this.html.indexOf(quote, this.currentChar); if (n === -1) { this.currentChar = this.html.length;
str = null;
} else {
str = this.html.substring(this.currentChar, n); this.currentChar = n + 1;
}
return str;
},
/** * Called when parsing a node. This finds the next name/value attribute * pair and adds the result to the attributes list.
*/
readAttribute: function (node) { var name = "";
var n = this.html.indexOf("=", this.currentChar); if (n === -1) { this.currentChar = this.html.length;
} else { // Read until a '=' character is hit; this will be the attribute key
name = this.html.substring(this.currentChar, n); this.currentChar = n + 1;
}
if (!name) return;
// After a '=', we should see a '"' for the attribute value var c = this.nextChar(); if (c !== '"' && c !== "'") { this.error("Error reading attribute " + name + ", expecting '\"'"); return;
}
// Read the attribute value (and consume the matching quote) var value = this.readString(c);
/** * Parses and returns an Element node. This is called after a '<' has been * read. * * @returns an array; the first index of the array is the parsed node; * the second index is a boolean indicating whether this is a void * Element
*/
makeElementNode: function (retPair) { var c = this.nextChar();
// Read the Element tag name var strBuf = this.strBuf;
strBuf.length = 0; while (whitespace.indexOf(c) == -1 && c !== ">" && c !== "/") { if (c === undefined) returnfalse;
strBuf.push(c);
c = this.nextChar();
} var tag = strBuf.join("");
if (!tag) returnfalse;
var node = new Element(tag);
// Read Element attributes while (c !== "/" && c !== ">") { if (c === undefined) returnfalse; while (whitespace.indexOf(this.html[this.currentChar++]) != -1) { // Advance cursor to first non-whitespace char.
} this.currentChar--;
c = this.nextChar(); if (c !== "/" && c !== ">") {
--this.currentChar; this.readAttribute(node);
}
}
// If this is a self-closing tag, read '/>' var closed = false; if (c === "/") {
closed = true;
c = this.nextChar(); if (c !== ">") { this.error("expected '>' to close " + tag); returnfalse;
}
}
/** * If the current input matches this string, advance the input index; * otherwise, do nothing. * * @returns whether input matched string
*/
match: function (str) { var strlen = str.length; if (this.html.substr(this.currentChar, strlen).toLowerCase() === str.toLowerCase()) { this.currentChar += strlen; returntrue;
} returnfalse;
},
/** * Searches the input until a string is found and discards all input up to * and including the matched string.
*/
discardTo: function (str) { var index = this.html.indexOf(str, this.currentChar) + str.length; if (index === -1) this.currentChar = this.html.length; this.currentChar = index;
},
/** * Reads child nodes for the given node.
*/
readChildren: function (node) { var child; while ((child = this.readNode())) { // Don't keep Comment nodes if (child.nodeType !== 8) {
node.appendChild(child);
}
}
},
discardNextComment: function() { if (this.match("--")) { this.discardTo("-->");
} else { var c = this.nextChar(); while (c !== ">") { if (c === undefined) returnnull; if (c === '"' || c === "'") this.readString(c);
c = this.nextChar();
}
} returnnew Comment();
},
/** * Reads the next child node from the input. If we're reading a closing * tag, or if we've reached the end of input, return null. * * @returns the node
*/
readNode: function () { var c = this.nextChar();
if (c === undefined) returnnull;
// Read any text as Text node var textNode; if (c !== "<") {
--this.currentChar;
textNode = new Text(); var n = this.html.indexOf("<", this.currentChar); if (n === -1) {
textNode.innerHTML = this.html.substring(this.currentChar, this.html.length); this.currentChar = this.html.length;
} else {
textNode.innerHTML = this.html.substring(this.currentChar, n); this.currentChar = n;
} return textNode;
}
if (this.match("![CDATA[")) { var endChar = this.html.indexOf("]]>", this.currentChar); if (endChar === -1) { this.error("unclosed CDATA section"); returnnull;
}
textNode = new Text();
textNode.textContent = this.html.substring(this.currentChar, endChar); this.currentChar = endChar + ("]]>").length; return textNode;
}
c = this.peekNext();
// Read Comment node. Normally, Comment nodes know their inner // textContent, but we don't really care about Comment nodes (we throw // them away in readChildren()). So just returning an empty Comment node // here is sufficient. if (c === "!" || c === "?") { // We're still before the ! or ? that is starting this comment: this.currentChar++; returnthis.discardNextComment();
}
// If we're reading a closing tag, return null. This means we've reached // the end of this set of child nodes. if (c === "/") {
--this.currentChar; returnnull;
}
// Otherwise, we're looking at an Element node var result = this.makeElementNode(this.retPair); if (!result) returnnull;
var node = this.retPair[0]; var closed = this.retPair[1]; var localName = node.localName;
// If this isn't a void Element, read its child nodes if (!closed) { this.readChildren(node); var closingTag = "" + node._matchingTag + ">"; if (!this.match(closingTag)) { this.error("expected '" + closingTag + "' and got " + this.html.substr(this.currentChar, closingTag.length)); returnnull;
}
}
// Only use the first title, because SVG might have other // title elements which we don't care about (medium.com // does this, at least). if (localName === "title" && !this.doc.title) { this.doc.title = node.textContent.trim();
} elseif (localName === "head") { this.doc.head = node;
} elseif (localName === "body") { this.doc.body = node;
} elseif (localName === "html") { this.doc.documentElement = node;
}
return node;
},
/** * Parses an HTML string and returns a JS implementation of the Document.
*/
parse: function (html, url) { this.html = html; var doc = this.doc = new Document(url); this.readChildren(doc);
// If this is an HTML document, remove root-level children except for the // <html> node if (doc.documentElement) { for (var i = doc.childNodes.length; --i >= 0;) { var child = doc.childNodes[i]; if (child !== doc.documentElement) {
doc.removeChild(child);
}
}
}
return doc;
}
};
// Attach the standard DOM types to the global scope
global.Node = Node;
global.Comment = Comment;
global.Document = Document;
global.Element = Element;
global.Text = Text;
// Attach JSDOMParser to the global scope
global.JSDOMParser = JSDOMParser;
})(this);
if (typeof module === "object") { /* global module */
module.exports = this.JSDOMParser;
}
Messung V0.5
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.56Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.