Skip to content

Commit

Permalink
Use new parse5 streaming interface
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Mayr authored and Sebmaster committed Jan 15, 2016
1 parent 3cee6ee commit b7b7e74
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 123 deletions.
1 change: 0 additions & 1 deletion .jscsrc
Expand Up @@ -35,7 +35,6 @@
"node_modules",
"benchmark/browser-bundle.js",
"test/worker-bundle.js",
"lib/jsdom/browser/htmltodom.js",
"lib/jsdom/browser/default-stylesheet.js",
"lib/jsdom/level2/style.js",
"lib/jsdom/level3/xpath.js",
Expand Down
202 changes: 148 additions & 54 deletions lib/jsdom/browser/documentAdapter.js
@@ -1,73 +1,167 @@
"use strict";

const idlUtils = require("../living/generated/utils");
const attributes = require("../living/attributes");
const DocumentType = require("../living/generated/DocumentType");

module.exports = function (document) {
const exports = {};

// Node construction
exports.createDocument = function () {
return document;
};

exports.createDocumentFragment = function () {
return document.createDocumentFragment();
};

exports.createElement = function (tagName, namespaceURI, attrs) {
const elem = document._createElementWithCorrectElementInterface(tagName, namespaceURI);
elem._localName = tagName;
elem._namespaceURI = namespaceURI || null;
for (let i = 0; i < attrs.length; ++i) {
attributes.setAttributeValue(elem, attrs[i].name, attrs[i].value,
attrs[i].prefix || null, attrs[i].namespace || null);
}
return elem;
};

exports.createCommentNode = function (data) {
return document.createComment(data);
};

// Tree mutation
exports.appendChild = function (parentNode, newNode) {
parentNode.appendChild(newNode);
};

exports.insertBefore = function (parentNode, newNode, referenceNode) {
parentNode.insertBefore(newNode, referenceNode);
};

exports.setTemplateContent = function (templateElement, contentElement) {
templateElement.content = contentElement;
};

exports.setDocumentType = function (doc, name, publicId, systemId) {
const doctypeNode = doc.doctype;
if (doctypeNode) {
doctypeNode.parentNode.removeChild(doctypeNode);
}
const newType = DocumentType.createImpl([], { core: document.defaultView, ownerDocument: doc, name, publicId, systemId });
exports.appendChild(doc, newType);
};

// Tree traversing
exports.getFirstChild = function (node) {
return node.childNodes[0];
};
exports.setQuirksMode = function (doc) {
doc._mode = "quirks";
};

exports.isQuirksMode = function (doc) {
return doc._mode === "quirks";
};

exports.getChildNodes = function (node) {
// parse5 treats template elements specially, assuming you return an array whose single item is the document fragment
const children = node._templateContents ? [node._templateContents] : [];
if (children.length === 0) {
for (let i = 0; i < node.childNodes.length; ++i) {
children.push(idlUtils.implForWrapper(node.childNodes[i]));
exports.detachNode = function (node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
return children;
};
};

exports.insertText = function (parentNode, text) {
const lastChild = parentNode.lastChild;
if (lastChild && lastChild.nodeName === "#text") {
lastChild.nodeValue += text;
} else {
exports.appendChild(parentNode, document.createTextNode(text));
}
};

exports.insertTextBefore = function (parentNode, text, referenceNode) {
const prevNode = referenceNode.previousSibling;
if (prevNode && prevNode.nodeName === "#text") {
prevNode.nodeValue += text;
} else {
exports.insertBefore(parentNode, document.createTextNode(text), referenceNode);
}
};

exports.getParentNode = function (node) {
return node.parentNode;
};
exports.adoptAttributes = function (recipientNode, attrs) {
for (let i = 0; i < attrs.length; ++i) {
if (recipientNode.hasAttribute(attrs[i].name)) {
recipientNode.attributes.setNamedItemNS(attrs[i]);
}
}
};

exports.getAttrList = function (node) {
return node.attributes;
};
// Tree traversing
exports.getFirstChild = function (node) {
return node.childNodes[0];
};

// Node data
exports.getTagName = function (element) {
return element.tagName.toLowerCase();
};
exports.getChildNodes = function (node) {
return node.childNodes;
};

exports.getNamespaceURI = function (element) {
return element.namespaceURI || "http://www.w3.org/1999/xhtml";
};
exports.getParentNode = function (node) {
return node.parentNode;
};

exports.getTextNodeContent = function (textNode) {
return textNode.nodeValue;
};
exports.getAttrList = function (node) {
return node.attributes;
};

exports.getCommentNodeContent = function (commentNode) {
return commentNode.nodeValue;
};
// Node data
exports.getTagName = function (element) {
if (!element.tagName) {
return null;
}

exports.getDocumentTypeNodeName = function (doctypeNode) {
return doctypeNode.name;
};
return element.tagName.toLowerCase();
};

exports.getDocumentTypeNodePublicId = function (doctypeNode) {
return doctypeNode.publicId || null;
};
exports.getNamespaceURI = function (element) {
return element.namespaceURI || "http://www.w3.org/1999/xhtml";
};

exports.getDocumentTypeNodeSystemId = function (doctypeNode) {
return doctypeNode.systemId || null;
};
exports.getTextNodeContent = function (textNode) {
return textNode.nodeValue;
};

// Node types
exports.isTextNode = function (node) {
return node.nodeName === "#text";
};
exports.getCommentNodeContent = function (commentNode) {
return commentNode.nodeValue;
};

exports.isCommentNode = function (node) {
return node.nodeName === "#comment";
};
exports.getDocumentTypeNodeName = function (doctypeNode) {
return doctypeNode.name;
};

exports.isDocumentTypeNode = function (node) {
return node.nodeType === 10;
};
exports.getDocumentTypeNodePublicId = function (doctypeNode) {
return doctypeNode.publicId || null;
};

exports.getDocumentTypeNodeSystemId = function (doctypeNode) {
return doctypeNode.systemId || null;
};

exports.getTemplateContent = function (node) {
return node.content;
};

// Node types
exports.isTextNode = function (node) {
return node.nodeName === "#text";
};

exports.isCommentNode = function (node) {
return node.nodeName === "#comment";
};

exports.isDocumentTypeNode = function (node) {
return node.nodeType === 10;
};

exports.isElementNode = function (node) {
return Boolean(node.tagName);
};

exports.isElementNode = function (node) {
return Boolean(node.tagName);
return exports;
};
8 changes: 3 additions & 5 deletions lib/jsdom/browser/domtohtml.js
@@ -1,18 +1,16 @@
"use strict";
const parse5 = require("parse5");
const documentAdapter = require("./documentAdapter");
const adapter = require("./documentAdapter.js")(null); // don't need a doc for serialization
const NODE_TYPE = require("../living/node-type");
const idlUtils = require("../living/generated/utils");

const serializer = new parse5.TreeSerializer(documentAdapter);

exports.domToHtml = function (iterable) {
let ret = "";
for (const node of iterable) {
if (node.nodeType === NODE_TYPE.DOCUMENT_NODE) {
ret += serializer.serialize(node);
ret += parse5.serialize(node, { treeAdapter: adapter });
} else {
ret += serializer.serialize({ childNodes: [idlUtils.wrapperForImpl(node)] });
ret += parse5.serialize({ childNodes: [idlUtils.wrapperForImpl(node)] }, { treeAdapter: adapter });
}
}
return ret;
Expand Down
70 changes: 16 additions & 54 deletions lib/jsdom/browser/htmltodom.js
Expand Up @@ -4,7 +4,7 @@ const parse5 = require("parse5");
const sax = require("sax");
const attributes = require("../living/attributes");
const DocumentType = require("../living/generated/DocumentType");
const locationInfo = require("../living/helpers/internal-constants").locationInfo;
const adapter = require("./documentAdapter");

class HtmlToDom {
constructor(core, parser, parsingMode) {
Expand All @@ -20,14 +20,12 @@ class HtmlToDom {
this.parser = parser;
this.parsingMode = parsingMode;

if (parser.DefaultHandler) {
this.parserType = "htmlparser2";
} else if (parser.Parser && parser.TreeAdapters) {
this.parserType = "parse5v1";
} else if (parser.moduleName === "HTML5") {
this.parserType = "html5";
if (parser.ParserStream && parser.treeAdapters) {
this.parserType = "parse5";
} else if (parser.parser) {
this.parserType = "sax";
} else {
throw new Error("Provided an unsupported parser interface. Only parse5 2.0 and node-sax are supported.");
}
}

Expand All @@ -47,61 +45,27 @@ class HtmlToDom {
return this["_parseWith" + this.parserType](html, false, element);
}

_parseWithhtmlparser2(html, fragment, element) {
const handler = new this.parser.DefaultHandler();
// Check if document is XML
const isXML = this.parsingMode === "xml";
const parserInstance = new this.parser.Parser(handler, {
xmlMode: isXML,
lowerCaseTags: !isXML,
lowerCaseAttributeNames: !isXML,
decodeEntities: true
});

parserInstance.includeLocation = false;
parserInstance.parseComplete(html);

const parsed = handler.dom;
for (let i = 0; i < parsed.length; i++) {
setChild(this.core, element, parsed[i]);
}

return element;
}

_parseWithparse5v1(html, fragment, element) {
_parseWithparse5(html, fragment, element) {
if (this.parsingMode === "xml") {
throw new Error("Can't parse XML with parse5, please use htmlparser2 instead.");
}

const htmlparser2Adapter = this.parser.TreeAdapters.htmlparser2;
let dom;
if (fragment) {
const instance = new this.parser.Parser(htmlparser2Adapter);
const parentElement = htmlparser2Adapter.createElement(element.tagName.toLowerCase(), element.namespaceURI, []);
dom = instance.parseFragment(html, parentElement);
const domFragment = this.parser.parseFragment(element, html, {
treeAdapter: adapter(element.ownerDocument)
});
element.appendChild(domFragment);
} else {
const instance = new this.parser.Parser(htmlparser2Adapter, { locationInfo: true });
dom = instance.parse(html);
}

const parsed = dom.children;
for (let i = 0; i < parsed.length; i++) {
setChild(this.core, element, parsed[i]);
const instance = new this.parser.ParserStream({
treeAdapter: adapter(element),
locationInfo: true
});
instance.end(html);
}

return element;
}

_parseWithhtml5(html, fragment, element) {
if (element.nodeType === 9) {
new this.parser.Parser({ document: element }).parse(html);
} else {
const p = new this.parser.Parser({ document: element.ownerDocument });
p.parse_fragment(html, element);
}
}

_parseWithsax(html, fragment, element) {
const SaxParser = this.parser.parser;
const parser = new SaxParser(false, { xmlns: true });
Expand Down Expand Up @@ -236,15 +200,13 @@ function setChild(core, parentImpl, node) {
return null;
}

newNode[locationInfo] = node.__location;

if (node.attribs) {
for (let localName in node.attribs) {
const value = node.attribs[localName];
let prefix = node["x-attribsPrefix"] && node["x-attribsPrefix"][localName] || null;
const namespace = node["x-attribsNamespace"] && node["x-attribsNamespace"][localName] || null;
if (prefix === "xmlns" && localName === "") {
// intended weirdness in node-sax, see https://github.com/isaacs/sax-js/issues/165
// intended weirdness in node-sax, see https://github.com/isaacs/sax-js/issues/165
localName = prefix;
prefix = null;
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -24,7 +24,7 @@
"cssstyle": ">= 0.2.29 < 0.3.0",
"escodegen": "^1.6.1",
"nwmatcher": ">= 1.3.7 < 2.0.0",
"parse5": "^1.5.1",
"parse5": "^2.0.0",
"request": "^2.55.0",
"sax": "^1.1.4",
"symbol-tree": ">= 3.1.0 < 4.0.0",
Expand Down

0 comments on commit b7b7e74

Please sign in to comment.