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
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); |
|
};
|
|
|