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