Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

348 lines
8.3 KiB

'use strict';
const doctype = require('parse5/lib/common/doctype');
const { DOCUMENT_MODE } = require('parse5/lib/common/html');
//Conversion tables for DOM Level1 structure emulation
const nodeTypes = {
element: 1,
text: 3,
cdata: 4,
comment: 8
};
const nodePropertyShorthands = {
tagName: 'name',
childNodes: 'children',
parentNode: 'parent',
previousSibling: 'prev',
nextSibling: 'next',
nodeValue: 'data'
};
//Node
class Node {
constructor(props) {
for (const key of Object.keys(props)) {
this[key] = props[key];
}
}
get firstChild() {
const children = this.children;
return (children && children[0]) || null;
}
get lastChild() {
const children = this.children;
return (children && children[children.length - 1]) || null;
}
get nodeType() {
return nodeTypes[this.type] || nodeTypes.element;
}
}
Object.keys(nodePropertyShorthands).forEach(key => {
const shorthand = nodePropertyShorthands[key];
Object.defineProperty(Node.prototype, key, {
get: function() {
return this[shorthand] || null;
},
set: function(val) {
this[shorthand] = val;
return val;
}
});
});
//Node construction
exports.createDocument = function() {
return new Node({
type: 'root',
name: 'root',
parent: null,
prev: null,
next: null,
children: [],
'x-mode': DOCUMENT_MODE.NO_QUIRKS
});
};
exports.createDocumentFragment = function() {
return new Node({
type: 'root',
name: 'root',
parent: null,
prev: null,
next: null,
children: []
});
};
exports.createElement = function(tagName, namespaceURI, attrs) {
const attribs = Object.create(null);
const attribsNamespace = Object.create(null);
const attribsPrefix = Object.create(null);
for (let i = 0; i < attrs.length; i++) {
const attrName = attrs[i].name;
attribs[attrName] = attrs[i].value;
attribsNamespace[attrName] = attrs[i].namespace;
attribsPrefix[attrName] = attrs[i].prefix;
}
return new Node({
type: tagName === 'script' || tagName === 'style' ? tagName : 'tag',
name: tagName,
namespace: namespaceURI,
attribs: attribs,
'x-attribsNamespace': attribsNamespace,
'x-attribsPrefix': attribsPrefix,
children: [],
parent: null,
prev: null,
next: null
});
};
exports.createCommentNode = function(data) {
return new Node({
type: 'comment',
data: data,
parent: null,
prev: null,
next: null
});
};
const createTextNode = function(value) {
return new Node({
type: 'text',
data: value,
parent: null,
prev: null,
next: null
});
};
//Tree mutation
const appendChild = (exports.appendChild = function(parentNode, newNode) {
const prev = parentNode.children[parentNode.children.length - 1];
if (prev) {
prev.next = newNode;
newNode.prev = prev;
}
parentNode.children.push(newNode);
newNode.parent = parentNode;
});
const insertBefore = (exports.insertBefore = function(parentNode, newNode, referenceNode) {
const insertionIdx = parentNode.children.indexOf(referenceNode);
const prev = referenceNode.prev;
if (prev) {
prev.next = newNode;
newNode.prev = prev;
}
referenceNode.prev = newNode;
newNode.next = referenceNode;
parentNode.children.splice(insertionIdx, 0, newNode);
newNode.parent = parentNode;
});
exports.setTemplateContent = function(templateElement, contentElement) {
appendChild(templateElement, contentElement);
};
exports.getTemplateContent = function(templateElement) {
return templateElement.children[0];
};
exports.setDocumentType = function(document, name, publicId, systemId) {
const data = doctype.serializeContent(name, publicId, systemId);
let doctypeNode = null;
for (let i = 0; i < document.children.length; i++) {
if (document.children[i].type === 'directive' && document.children[i].name === '!doctype') {
doctypeNode = document.children[i];
break;
}
}
if (doctypeNode) {
doctypeNode.data = data;
doctypeNode['x-name'] = name;
doctypeNode['x-publicId'] = publicId;
doctypeNode['x-systemId'] = systemId;
} else {
appendChild(
document,
new Node({
type: 'directive',
name: '!doctype',
data: data,
'x-name': name,
'x-publicId': publicId,
'x-systemId': systemId
})
);
}
};
exports.setDocumentMode = function(document, mode) {
document['x-mode'] = mode;
};
exports.getDocumentMode = function(document) {
return document['x-mode'];
};
exports.detachNode = function(node) {
if (node.parent) {
const idx = node.parent.children.indexOf(node);
const prev = node.prev;
const next = node.next;
node.prev = null;
node.next = null;
if (prev) {
prev.next = next;
}
if (next) {
next.prev = prev;
}
node.parent.children.splice(idx, 1);
node.parent = null;
}
};
exports.insertText = function(parentNode, text) {
const lastChild = parentNode.children[parentNode.children.length - 1];
if (lastChild && lastChild.type === 'text') {
lastChild.data += text;
} else {
appendChild(parentNode, createTextNode(text));
}
};
exports.insertTextBefore = function(parentNode, text, referenceNode) {
const prevNode = parentNode.children[parentNode.children.indexOf(referenceNode) - 1];
if (prevNode && prevNode.type === 'text') {
prevNode.data += text;
} else {
insertBefore(parentNode, createTextNode(text), referenceNode);
}
};
exports.adoptAttributes = function(recipient, attrs) {
for (let i = 0; i < attrs.length; i++) {
const attrName = attrs[i].name;
if (typeof recipient.attribs[attrName] === 'undefined') {
recipient.attribs[attrName] = attrs[i].value;
recipient['x-attribsNamespace'][attrName] = attrs[i].namespace;
recipient['x-attribsPrefix'][attrName] = attrs[i].prefix;
}
}
};
//Tree traversing
exports.getFirstChild = function(node) {
return node.children[0];
};
exports.getChildNodes = function(node) {
return node.children;
};
exports.getParentNode = function(node) {
return node.parent;
};
exports.getAttrList = function(element) {
const attrList = [];
for (const name in element.attribs) {
attrList.push({
name: name,
value: element.attribs[name],
namespace: element['x-attribsNamespace'][name],
prefix: element['x-attribsPrefix'][name]
});
}
return attrList;
};
//Node data
exports.getTagName = function(element) {
return element.name;
};
exports.getNamespaceURI = function(element) {
return element.namespace;
};
exports.getTextNodeContent = function(textNode) {
return textNode.data;
};
exports.getCommentNodeContent = function(commentNode) {
return commentNode.data;
};
exports.getDocumentTypeNodeName = function(doctypeNode) {
return doctypeNode['x-name'];
};
exports.getDocumentTypeNodePublicId = function(doctypeNode) {
return doctypeNode['x-publicId'];
};
exports.getDocumentTypeNodeSystemId = function(doctypeNode) {
return doctypeNode['x-systemId'];
};
//Node types
exports.isTextNode = function(node) {
return node.type === 'text';
};
exports.isCommentNode = function(node) {
return node.type === 'comment';
};
exports.isDocumentTypeNode = function(node) {
return node.type === 'directive' && node.name === '!doctype';
};
exports.isElementNode = function(node) {
return !!node.attribs;
};
// Source code location
exports.setNodeSourceCodeLocation = function(node, location) {
node.sourceCodeLocation = location;
};
exports.getNodeSourceCodeLocation = function(node) {
return node.sourceCodeLocation;
};
exports.updateNodeSourceCodeLocation = function(node, endLocation) {
node.sourceCodeLocation = Object.assign(node.sourceCodeLocation, endLocation);
};