"use strict";
|
|
const parse5 = require("parse5");
|
|
const DocumentType = require("../../living/generated/DocumentType");
|
const DocumentFragment = require("../../living/generated/DocumentFragment");
|
const Text = require("../../living/generated/Text");
|
const Comment = require("../../living/generated/Comment");
|
|
const attributes = require("../../living/attributes");
|
const nodeTypes = require("../../living/node-type");
|
|
const serializationAdapter = require("../../living/domparsing/parse5-adapter-serialization");
|
|
const OpenElementStack = require("parse5/lib/parser/open-element-stack");
|
const OpenElementStackOriginalPop = OpenElementStack.prototype.pop;
|
const OpenElementStackOriginalPush = OpenElementStack.prototype.push;
|
|
class JSDOMParse5Adapter {
|
constructor(documentImpl) {
|
this._documentImpl = documentImpl;
|
|
// Since the createElement hook doesn't provide the parent element, we keep track of this using _currentElement:
|
// https://github.com/inikulin/parse5/issues/285
|
this._currentElement = undefined;
|
|
// Horrible monkey-patch to implement https://github.com/inikulin/parse5/issues/237
|
const adapter = this;
|
OpenElementStack.prototype.push = function (...args) {
|
OpenElementStackOriginalPush.apply(this, args);
|
adapter._currentElement = this.current;
|
|
const after = this.items[this.stackTop];
|
if (after._pushedOnStackOfOpenElements) {
|
after._pushedOnStackOfOpenElements();
|
}
|
};
|
OpenElementStack.prototype.pop = function (...args) {
|
const before = this.items[this.stackTop];
|
|
OpenElementStackOriginalPop.apply(this, args);
|
adapter._currentElement = this.current;
|
|
if (before._poppedOffStackOfOpenElements) {
|
before._poppedOffStackOfOpenElements();
|
}
|
};
|
}
|
|
_ownerDocument() {
|
// The _currentElement is undefined when parsing elements at the root of the document. In this case we would
|
// fallback to the global _documentImpl.
|
return this._currentElement ? this._currentElement._ownerDocument : this._documentImpl;
|
}
|
|
createDocument() {
|
// parse5's model assumes that parse(html) will call into here to create the new Document, then return it. However,
|
// jsdom's model assumes we can create a Window (and through that create an empty Document), do some other setup
|
// stuff, and then parse, stuffing nodes into that Document as we go. So to adapt between these two models, we just
|
// return the already-created Document when asked by parse5 to "create" a Document.
|
return this._documentImpl;
|
}
|
|
createDocumentFragment() {
|
return DocumentFragment.createImpl([], { ownerDocument: this._currentElement._ownerDocument });
|
}
|
|
createElement(localName, namespace, attrs) {
|
const ownerDocument = this._ownerDocument();
|
|
const element = ownerDocument._createElementWithCorrectElementInterface(localName, namespace);
|
element._namespaceURI = namespace;
|
this.adoptAttributes(element, attrs);
|
|
if ("_parserInserted" in element) {
|
element._parserInserted = true;
|
}
|
|
return element;
|
}
|
|
createCommentNode(data) {
|
const ownerDocument = this._ownerDocument();
|
return Comment.createImpl([], { data, ownerDocument });
|
}
|
|
appendChild(parentNode, newNode) {
|
parentNode._append(newNode);
|
}
|
|
insertBefore(parentNode, newNode, referenceNode) {
|
parentNode._insert(newNode, referenceNode);
|
}
|
|
setTemplateContent(templateElement, contentFragment) {
|
// This code makes the glue between jsdom and parse5 HTMLTemplateElement parsing:
|
//
|
// * jsdom during the construction of the HTMLTemplateElement (for example when create via
|
// `document.createElement("template")`), creates a DocumentFragment and set it into _templateContents.
|
// * parse5 when parsing a <template> tag creates an HTMLTemplateElement (`createElement` adapter hook) and also
|
// create a DocumentFragment (`createDocumentFragment` adapter hook).
|
//
|
// At this point we now have to replace the one created in jsdom with one created by parse5.
|
const { _ownerDocument, _host } = templateElement._templateContents;
|
contentFragment._ownerDocument = _ownerDocument;
|
contentFragment._host = _host;
|
|
templateElement._templateContents = contentFragment;
|
}
|
|
setDocumentType(document, name, publicId, systemId) {
|
const ownerDocument = this._ownerDocument();
|
const documentType = DocumentType.createImpl([], { name, publicId, systemId, ownerDocument });
|
|
document._append(documentType);
|
}
|
|
setDocumentMode(document, mode) {
|
// TODO: the rest of jsdom ignores this
|
document._mode = mode;
|
}
|
|
detachNode(node) {
|
node.remove();
|
}
|
|
insertText(parentNode, text) {
|
const { lastChild } = parentNode;
|
if (lastChild && lastChild.nodeType === nodeTypes.TEXT_NODE) {
|
lastChild.data += text;
|
} else {
|
const ownerDocument = this._ownerDocument();
|
const textNode = Text.createImpl([], { data: text, ownerDocument });
|
parentNode._append(textNode);
|
}
|
}
|
|
insertTextBefore(parentNode, text, referenceNode) {
|
const { previousSibling } = referenceNode;
|
if (previousSibling && previousSibling.nodeType === nodeTypes.TEXT_NODE) {
|
previousSibling.data += text;
|
} else {
|
const ownerDocument = this._ownerDocument();
|
const textNode = Text.createImpl([], { data: text, ownerDocument });
|
parentNode._append(textNode, referenceNode);
|
}
|
}
|
|
adoptAttributes(element, attrs) {
|
for (const attr of attrs) {
|
const prefix = attr.prefix === "" ? null : attr.prefix;
|
attributes.setAttributeValue(element, attr.name, attr.value, prefix, attr.namespace);
|
}
|
}
|
}
|
|
// Assign shared adapters with serializer.
|
Object.assign(JSDOMParse5Adapter.prototype, serializationAdapter);
|
|
function parseFragment(markup, contextElement) {
|
const ownerDocument = contextElement._ownerDocument;
|
|
const config = Object.assign({}, ownerDocument._parseOptions, {
|
treeAdapter: new JSDOMParse5Adapter(ownerDocument)
|
});
|
|
return parse5.parseFragment(contextElement, markup, config);
|
}
|
|
function parseIntoDocument(markup, ownerDocument) {
|
const config = Object.assign({}, ownerDocument._parseOptions, {
|
treeAdapter: new JSDOMParse5Adapter(ownerDocument)
|
});
|
|
return parse5.parse(markup, config);
|
}
|
|
module.exports = {
|
parseFragment,
|
parseIntoDocument
|
};
|