1'use strict'; 2 3var Doctype = require('../common/doctype'); 4 5//Conversion tables for DOM Level1 structure emulation 6var nodeTypes = { 7 element: 1, 8 text: 3, 9 cdata: 4, 10 comment: 8 11}; 12 13var nodePropertyShorthands = { 14 tagName: 'name', 15 childNodes: 'children', 16 parentNode: 'parent', 17 previousSibling: 'prev', 18 nextSibling: 'next', 19 nodeValue: 'data' 20}; 21 22//Node 23var Node = function (props) { 24 for (var key in props) { 25 if (props.hasOwnProperty(key)) 26 this[key] = props[key]; 27 } 28}; 29 30Node.prototype = { 31 get firstChild() { 32 var children = this.children; 33 return children && children[0] || null; 34 }, 35 36 get lastChild() { 37 var children = this.children; 38 return children && children[children.length - 1] || null; 39 }, 40 41 get nodeType() { 42 return nodeTypes[this.type] || nodeTypes.element; 43 } 44}; 45 46Object.keys(nodePropertyShorthands).forEach(function (key) { 47 var shorthand = nodePropertyShorthands[key]; 48 49 Object.defineProperty(Node.prototype, key, { 50 get: function () { 51 return this[shorthand] || null; 52 }, 53 set: function (val) { 54 this[shorthand] = val; 55 return val; 56 } 57 }); 58}); 59 60 61//Node construction 62exports.createDocument = 63exports.createDocumentFragment = function () { 64 return new Node({ 65 type: 'root', 66 name: 'root', 67 parent: null, 68 prev: null, 69 next: null, 70 children: [] 71 }); 72}; 73 74exports.createElement = function (tagName, namespaceURI, attrs) { 75 var attribs = {}, 76 attribsNamespace = {}, 77 attribsPrefix = {}; 78 79 for (var i = 0; i < attrs.length; i++) { 80 var attrName = attrs[i].name; 81 82 attribs[attrName] = attrs[i].value; 83 attribsNamespace[attrName] = attrs[i].namespace; 84 attribsPrefix[attrName] = attrs[i].prefix; 85 } 86 87 return new Node({ 88 type: tagName === 'script' || tagName === 'style' ? tagName : 'tag', 89 name: tagName, 90 namespace: namespaceURI, 91 attribs: attribs, 92 'x-attribsNamespace': attribsNamespace, 93 'x-attribsPrefix': attribsPrefix, 94 children: [], 95 parent: null, 96 prev: null, 97 next: null 98 }); 99}; 100 101exports.createCommentNode = function (data) { 102 return new Node({ 103 type: 'comment', 104 data: data, 105 parent: null, 106 prev: null, 107 next: null 108 }); 109}; 110 111var createTextNode = function (value) { 112 return new Node({ 113 type: 'text', 114 data: value, 115 parent: null, 116 prev: null, 117 next: null 118 }); 119}; 120 121 122//Tree mutation 123exports.setDocumentType = function (document, name, publicId, systemId) { 124 var data = Doctype.serializeContent(name, publicId, systemId), 125 doctypeNode = null; 126 127 for (var i = 0; i < document.children.length; i++) { 128 if (document.children[i].type === 'directive' && document.children[i].name === '!doctype') { 129 doctypeNode = document.children[i]; 130 break; 131 } 132 } 133 134 if (doctypeNode) { 135 doctypeNode.data = data; 136 doctypeNode['x-name'] = name; 137 doctypeNode['x-publicId'] = publicId; 138 doctypeNode['x-systemId'] = systemId; 139 } 140 141 else { 142 appendChild(document, new Node({ 143 type: 'directive', 144 name: '!doctype', 145 data: data, 146 'x-name': name, 147 'x-publicId': publicId, 148 'x-systemId': systemId 149 })); 150 } 151 152}; 153 154exports.setQuirksMode = function (document) { 155 document.quirksMode = true; 156}; 157 158exports.isQuirksMode = function (document) { 159 return document.quirksMode; 160}; 161 162var appendChild = exports.appendChild = function (parentNode, newNode) { 163 var prev = parentNode.children[parentNode.children.length - 1]; 164 165 if (prev) { 166 prev.next = newNode; 167 newNode.prev = prev; 168 } 169 170 parentNode.children.push(newNode); 171 newNode.parent = parentNode; 172}; 173 174var insertBefore = exports.insertBefore = function (parentNode, newNode, referenceNode) { 175 var insertionIdx = parentNode.children.indexOf(referenceNode), 176 prev = referenceNode.prev; 177 178 if (prev) { 179 prev.next = newNode; 180 newNode.prev = prev; 181 } 182 183 referenceNode.prev = newNode; 184 newNode.next = referenceNode; 185 186 parentNode.children.splice(insertionIdx, 0, newNode); 187 newNode.parent = parentNode; 188}; 189 190exports.detachNode = function (node) { 191 if (node.parent) { 192 var idx = node.parent.children.indexOf(node), 193 prev = node.prev, 194 next = node.next; 195 196 node.prev = null; 197 node.next = null; 198 199 if (prev) 200 prev.next = next; 201 202 if (next) 203 next.prev = prev; 204 205 node.parent.children.splice(idx, 1); 206 node.parent = null; 207 } 208}; 209 210exports.insertText = function (parentNode, text) { 211 var lastChild = parentNode.children[parentNode.children.length - 1]; 212 213 if (lastChild && lastChild.type === 'text') 214 lastChild.data += text; 215 else 216 appendChild(parentNode, createTextNode(text)); 217}; 218 219exports.insertTextBefore = function (parentNode, text, referenceNode) { 220 var prevNode = parentNode.children[parentNode.children.indexOf(referenceNode) - 1]; 221 222 if (prevNode && prevNode.type === 'text') 223 prevNode.data += text; 224 else 225 insertBefore(parentNode, createTextNode(text), referenceNode); 226}; 227 228exports.adoptAttributes = function (recipientNode, attrs) { 229 for (var i = 0; i < attrs.length; i++) { 230 var attrName = attrs[i].name; 231 232 if (typeof recipientNode.attribs[attrName] === 'undefined') { 233 recipientNode.attribs[attrName] = attrs[i].value; 234 recipientNode['x-attribsNamespace'][attrName] = attrs[i].namespace; 235 recipientNode['x-attribsPrefix'][attrName] = attrs[i].prefix; 236 } 237 } 238}; 239 240 241//Tree traversing 242exports.getFirstChild = function (node) { 243 return node.children[0]; 244}; 245 246exports.getChildNodes = function (node) { 247 return node.children; 248}; 249 250exports.getParentNode = function (node) { 251 return node.parent; 252}; 253 254exports.getAttrList = function (node) { 255 var attrList = []; 256 257 for (var name in node.attribs) { 258 if (node.attribs.hasOwnProperty(name)) { 259 attrList.push({ 260 name: name, 261 value: node.attribs[name], 262 namespace: node['x-attribsNamespace'][name], 263 prefix: node['x-attribsPrefix'][name] 264 }); 265 } 266 } 267 268 return attrList; 269}; 270 271 272//Node data 273exports.getTagName = function (element) { 274 return element.name; 275}; 276 277exports.getNamespaceURI = function (element) { 278 return element.namespace; 279}; 280 281exports.getTextNodeContent = function (textNode) { 282 return textNode.data; 283}; 284 285exports.getCommentNodeContent = function (commentNode) { 286 return commentNode.data; 287}; 288 289exports.getDocumentTypeNodeName = function (doctypeNode) { 290 return doctypeNode['x-name']; 291}; 292 293exports.getDocumentTypeNodePublicId = function (doctypeNode) { 294 return doctypeNode['x-publicId']; 295}; 296 297exports.getDocumentTypeNodeSystemId = function (doctypeNode) { 298 return doctypeNode['x-systemId']; 299}; 300 301 302//Node types 303exports.isTextNode = function (node) { 304 return node.type === 'text'; 305}; 306 307exports.isCommentNode = function (node) { 308 return node.type === 'comment'; 309}; 310 311exports.isDocumentTypeNode = function (node) { 312 return node.type === 'directive' && node.name === '!doctype'; 313}; 314 315exports.isElementNode = function (node) { 316 return !!node.attribs; 317}; 318