'use strict';
var Doctype = require('../common/doctype');
//Conversion tables for DOM Level1 structure emulation var nodeTypes = {
element: 1, text: 3, cdata: 4, comment: 8
};
var nodePropertyShorthands = {
tagName: 'name', childNodes: 'children', parentNode: 'parent', previousSibling: 'prev', nextSibling: 'next', nodeValue: 'data'
};
//Node var Node = function (props) {
for (var key in props) { if (props.hasOwnProperty(key)) this[key] = props[key]; }
};
Node.prototype = {
get firstChild() { var children = this.children; return children && children[0] || null; }, get lastChild() { var children = this.children; return children && children[children.length - 1] || null; }, get nodeType() { return nodeTypes[this.type] || nodeTypes.element; }
};
Object.keys(nodePropertyShorthands).forEach(function (key) {
var 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 = exports.createDocumentFragment = function () {
return new Node({ type: 'root', name: 'root', parent: null, prev: null, next: null, children: [] });
};
exports.createElement = function (tagName, namespaceURI, attrs) {
var attribs = {}, attribsNamespace = {}, attribsPrefix = {}; for (var i = 0; i < attrs.length; i++) { var 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 });
};
var createTextNode = function (value) {
return new Node({ type: 'text', data: value, parent: null, prev: null, next: null });
};
//Tree mutation exports.setDocumentType = function (document, name, publicId, systemId) {
var data = Doctype.serializeContent(name, publicId, systemId), doctypeNode = null; for (var 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.setQuirksMode = function (document) {
document.quirksMode = true;
};
exports.isQuirksMode = function (document) {
return document.quirksMode;
};
var appendChild = exports.appendChild = function (parentNode, newNode) {
var prev = parentNode.children[parentNode.children.length - 1]; if (prev) { prev.next = newNode; newNode.prev = prev; } parentNode.children.push(newNode); newNode.parent = parentNode;
};
var insertBefore = exports.insertBefore = function (parentNode, newNode, referenceNode) {
var insertionIdx = parentNode.children.indexOf(referenceNode), 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.detachNode = function (node) {
if (node.parent) { var idx = node.parent.children.indexOf(node), prev = node.prev, 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) {
var 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) {
var 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 (recipientNode, attrs) {
for (var i = 0; i < attrs.length; i++) { var attrName = attrs[i].name; if (typeof recipientNode.attribs[attrName] === 'undefined') { recipientNode.attribs[attrName] = attrs[i].value; recipientNode['x-attribsNamespace'][attrName] = attrs[i].namespace; recipientNode['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 (node) {
var attrList = []; for (var name in node.attribs) { if (node.attribs.hasOwnProperty(name)) { attrList.push({ name: name, value: node.attribs[name], namespace: node['x-attribsNamespace'][name], prefix: node['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;
};