source: trip-planner-front/node_modules/critters/src/dom2.js@ 6a80231

Last change on this file since 6a80231 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 10.6 KB
Line 
1/**
2 * Copyright 2018 Google LLC
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17import parse5 from 'parse5';
18import nwsapi from 'nwsapi';
19// import crawl from 'tree-crawl';
20// import select from 'css-select';
21
22// htmlparser2 has a relatively DOM-like tree format, which we'll massage into a DOM elsewhere
23const treeAdapter = require('parse5-htmlparser2-tree-adapter');
24
25const PARSE5_OPTS = {
26 treeAdapter
27};
28
29/**
30 * Parse HTML into a mutable, serializable DOM Document.
31 * The DOM implementation is an htmlparser2 DOM enhanced with basic DOM mutation methods.
32 * @param {String} html HTML to parse into a Document instance
33 */
34export function createDocument(html) {
35 const document = parse5.parse(html, PARSE5_OPTS);
36
37 defineProperties(document, DocumentExtensions);
38
39 // Extend Element.prototype with DOM manipulation methods.
40 const scratch = document.createElement('div');
41 // Get a reference to the base Node class - used by createTextNode()
42 document.$$Node = scratch.constructor;
43 const elementProto = Object.getPrototypeOf(scratch);
44 defineProperties(elementProto, ElementExtensions);
45 elementProto.ownerDocument = document;
46
47 // nwsapi is a selector engine that happens to work with Parse5's htmlparser2 DOM (they form the base of jsdom).
48 // It is exposed to the document so that it can be used within Element.prototype methods.
49 document.$match = nwsapi({ document });
50 document.$match.configure({
51 IDS_DUPES: false,
52 LIVECACHE: false,
53 MIXEDCASE: true,
54 LOGERRORS: false
55 });
56
57 return document;
58}
59
60/**
61 * Serialize a Document to an HTML String
62 * @param {Document} document A Document, such as one created via `createDocument()`
63 */
64export function serializeDocument(document) {
65 return parse5.serialize(document, PARSE5_OPTS);
66}
67
68/**
69 * Methods and descriptors to mix into Element.prototype
70 */
71const ElementExtensions = {
72 /** @extends htmlparser2.Element.prototype */
73
74 nodeName: {
75 get() {
76 return this.tagName.toUpperCase();
77 }
78 },
79
80 id: reflectedProperty('id'),
81
82 className: reflectedProperty('class'),
83
84 classList: {
85 get() {
86 return this.className ? this.className.split(' ') : [];
87 }
88 },
89
90 insertBefore(child, referenceNode) {
91 if (!referenceNode) return this.appendChild(child);
92 treeAdapter.insertBefore(this, child, referenceNode);
93 return child;
94 },
95
96 appendChild(child) {
97 treeAdapter.appendChild(this, child);
98 return child;
99 },
100
101 removeChild(child) {
102 treeAdapter.detachNode(child);
103 },
104
105 remove() {
106 treeAdapter.detachNode(this);
107 },
108
109 textContent: {
110 get() {
111 return this.children.map((node) => node.nodeValue).join('\n');
112 },
113
114 set(text) {
115 this.children = [];
116 treeAdapter.insertText(this, text);
117 }
118 },
119
120 setAttribute(name, value) {
121 if (this.attribs == null) this.attribs = {};
122 if (value == null) value = '';
123 this.attribs[name] = value;
124 },
125
126 removeAttribute(name) {
127 if (this.attribs != null) {
128 delete this.attribs[name];
129 }
130 },
131
132 getAttribute(name) {
133 return this.attribs != null && this.attribs[name];
134 },
135
136 hasAttribute(name) {
137 return this.attribs != null && this.attribs[name] != null;
138 },
139
140 getAttributeNode(name) {
141 const value = this.getAttribute(name);
142 if (value != null) return { specified: true, value };
143 },
144
145 // These are used by nwsapi to implement its selector engine.
146 parentElement: {
147 get() {
148 const parent = this.parentNode;
149 if (parent.nodeType === 1) return parent;
150 return null;
151 }
152 },
153 firstElementChild: {
154 get() {
155 const children = this.children;
156
157 return (
158 (children && children.find((child) => child.nodeType === 1)) || null
159 );
160 }
161 },
162 nextElementSibling: {
163 get() {
164 let sibling = this.nextSibling;
165 while (sibling && sibling.nodeType !== 1) {
166 sibling = sibling.nextSibling;
167 }
168
169 return sibling;
170 }
171 },
172 previousElementSibling: {
173 get() {
174 let sibling = this.previousSibling;
175 while (sibling && sibling.nodeType !== 1) {
176 sibling = sibling.previousSibling;
177 }
178
179 return sibling;
180 }
181 },
182
183 getElementsByTagName,
184 getElementsByClassName
185};
186
187/**
188 * Methods and descriptors to mix into the global document instance
189 * @private
190 */
191const DocumentExtensions = {
192 /** @extends htmlparser2.Document.prototype */
193
194 // document is just an Element in htmlparser2, giving it a nodeType of ELEMENT_NODE.
195 // nwsapi requires that it at least report a correct nodeType of DOCUMENT_NODE.
196 nodeType: {
197 get() {
198 return 9;
199 }
200 },
201
202 contentType: {
203 get() {
204 return 'text/html';
205 }
206 },
207
208 nodeName: {
209 get() {
210 return '#document';
211 }
212 },
213
214 documentElement: {
215 get() {
216 // Find the first <html> element within the document
217 return this.childNodes.filter(
218 (child) => String(child.tagName).toLowerCase() === 'html'
219 )[0];
220 }
221 },
222
223 compatMode: {
224 get() {
225 const compatMode = {
226 'no-quirks': 'CSS1Compat',
227 quirks: 'BackCompat',
228 'limited-quirks': 'CSS1Compat'
229 };
230 return compatMode[treeAdapter.getDocumentMode(this)];
231 }
232 },
233
234 body: {
235 get() {
236 return this.querySelector('body');
237 }
238 },
239
240 createElement(name) {
241 return treeAdapter.createElement(name, null, []);
242 },
243
244 createTextNode(text) {
245 // there is no dedicated createTextNode equivalent exposed in htmlparser2's DOM
246 const Node = this.$$Node;
247 return new Node({
248 type: 'text',
249 data: text,
250 parent: null,
251 prev: null,
252 next: null
253 });
254 },
255
256 querySelector(sel) {
257 return this.$match.first(sel, this.documentElement);
258 // return select.selectOne(sel, this.documentElement);
259 },
260
261 querySelectorAll(sel) {
262 return this.$match.select(sel, this.documentElement);
263 // return select(sel, this.documentElement);
264 },
265
266 getElementsByTagName,
267 getElementsByClassName
268};
269
270/**
271 * Essentially `Object.defineProperties()`, except function values are assigned as value descriptors for convenience.
272 * @private
273 */
274function defineProperties(obj, properties) {
275 for (const i in properties) {
276 const value = properties[i];
277 Object.defineProperty(
278 obj,
279 i,
280 typeof value === 'function' ? { value } : value
281 );
282 }
283}
284
285/**
286 * A simple implementation of Element.prototype.getElementsByTagName().
287 * This is used by nwsapi to implement its selector engine.
288 * @private
289 * @note
290 * If perf issues arise, 2 faster but more verbose implementations are benchmarked here:
291 * https://esbench.com/bench/5ac3b647f2949800a0f619e1
292 */
293// function getElementsByTagName(tagName) {
294// // Only return Element/Document nodes
295// if (
296// (this.nodeType !== 1 && this.nodeType !== 9) ||
297// this.type === 'directive'
298// ) {
299// return [];
300// }
301// return Array.prototype.concat.apply(
302// // Add current element if it matches tag
303// tagName === '*' ||
304// (this.tagName &&
305// (this.tagName === tagName || this.nodeName === tagName.toUpperCase()))
306// ? [this]
307// : [],
308// // Check children recursively
309// this.children.map((child) => getElementsByTagName.call(child, tagName))
310// );
311// }
312
313function getElementsByTagName(tagName) {
314 if (this.nodeType !== 1 && this.nodeType !== 9) return [];
315 const stack = [this];
316 const matches = [];
317 const isWildCard = tagName === '*';
318 const tagNameUpper = tagName.toUpperCase();
319 while (stack.length !== 0) {
320 const el = stack.pop();
321 let child = el.lastChild;
322 while (child) {
323 if (child.nodeType === 1) stack.push(child);
324 child = child.previousSibling;
325 }
326 if (
327 isWildCard ||
328 (el.tagName != null &&
329 (el.tagName === tagNameUpper ||
330 el.tagName.toUpperCase() === tagNameUpper))
331 ) {
332 matches.push(el);
333 }
334 }
335 return matches;
336}
337
338// function getElementsByTagName(tagName) {
339// if (this.nodeType !== 1 && this.nodeType !== 9) return [];
340// const matches = [];
341// const isWildCard = tagName === '*';
342// const tagNameUpper = tagName.toUpperCase();
343
344// crawl(
345// this,
346// (node, context) => {
347// if (node.nodeType !== 1) {
348// context.skip();
349// }
350
351// if (
352// isWildCard ||
353// (node.tagName != null &&
354// (node.tagName === tagNameUpper ||
355// node.tagName.toUpperCase() === tagNameUpper))
356// ) {
357// matches.push(node);
358// }
359// },
360// { order: 'pre' }
361// );
362// return matches;
363// }
364
365// function getElementsByClassName(className) {
366// // Only return Element/Document nodes
367// if (
368// (this.nodeType !== 1 && this.nodeType !== 9) ||
369// this.type === 'directive'
370// ) {
371// return [];
372// }
373// return Array.prototype.concat.apply(
374// // Add current element if it matches tag
375// className && this.classList.includes(className.trim()) ? [this] : [],
376// // Check children recursively
377// this.children.map((child) => getElementsByClassName.call(child, className))
378// );
379// }
380
381function getElementsByClassName(className) {
382 if (this.nodeType !== 1 && this.nodeType !== 9) return [];
383 const stack = [...this.children];
384 const matches = [];
385 while (stack.length !== 0) {
386 const el = stack.pop();
387 let child = el.lastChild;
388 while (child) {
389 if (child.nodeType === 1) stack.push(child);
390 child = child.previousSibling;
391 }
392
393 // const classRe = new RegExp('(^|s)' + className + '(s|$)');
394 if (el.classList.includes(className.trim())) {
395 matches.push(el);
396 }
397 }
398 return matches;
399}
400
401// function getElementsByClassName(className) {
402// if (this.nodeType !== 1 && this.nodeType !== 9) return [];
403// const matches = [];
404
405// crawl(
406// this,
407// (node, context) => {
408// if (node.nodeType !== 1) {
409// context.skip();
410// }
411
412// if (node.classList.includes(className)) {
413// matches.push(node);
414// }
415// },
416// { order: 'pre' }
417// );
418
419// return matches;
420// }
421
422/**
423 * Create a property descriptor defining a getter/setter pair alias for a named attribute.
424 * @private
425 */
426function reflectedProperty(attributeName) {
427 return {
428 get() {
429 return this.getAttribute(attributeName);
430 },
431 set(value) {
432 this.setAttribute(attributeName, value);
433 }
434 };
435}
Note: See TracBrowser for help on using the repository browser.