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