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