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