349 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			349 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'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);
 | 
						|
};
 |