source: trip-planner-front/node_modules/critters/src/dom.js@ 6a3a178

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

initial commit

  • Property mode set to 100644
File size: 5.8 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 select from 'css-select';
19
20// htmlparser2 has a relatively DOM-like tree format, which we'll massage into a DOM elsewhere
21const treeAdapter = require('parse5-htmlparser2-tree-adapter');
22
23const PARSE5_OPTS = {
24 treeAdapter
25};
26
27/**
28 * Parse HTML into a mutable, serializable DOM Document.
29 * The DOM implementation is an htmlparser2 DOM enhanced with basic DOM mutation methods.
30 * @param {String} html HTML to parse into a Document instance
31 */
32export function createDocument(html) {
33 const document = parse5.parse(html, PARSE5_OPTS);
34
35 defineProperties(document, DocumentExtensions);
36
37 // Extend Element.prototype with DOM manipulation methods.
38 const scratch = document.createElement('div');
39 // Get a reference to the base Node class - used by createTextNode()
40 document.$$Node = scratch.constructor;
41 const elementProto = Object.getPrototypeOf(scratch);
42 defineProperties(elementProto, ElementExtensions);
43 elementProto.ownerDocument = document;
44
45 return document;
46}
47
48/**
49 * Serialize a Document to an HTML String
50 * @param {Document} document A Document, such as one created via `createDocument()`
51 */
52export function serializeDocument(document) {
53 return parse5.serialize(document, PARSE5_OPTS);
54}
55
56/**
57 * Methods and descriptors to mix into Element.prototype
58 */
59const ElementExtensions = {
60 /** @extends htmlparser2.Element.prototype */
61
62 nodeName: {
63 get() {
64 return this.tagName.toUpperCase();
65 }
66 },
67
68 id: reflectedProperty('id'),
69
70 className: reflectedProperty('class'),
71
72 insertBefore(child, referenceNode) {
73 if (!referenceNode) return this.appendChild(child);
74 treeAdapter.insertBefore(this, child, referenceNode);
75 return child;
76 },
77
78 appendChild(child) {
79 treeAdapter.appendChild(this, child);
80 return child;
81 },
82
83 removeChild(child) {
84 treeAdapter.detachNode(child);
85 },
86
87 remove() {
88 treeAdapter.detachNode(this);
89 },
90
91 textContent: {
92 get() {
93 return getText(this);
94 },
95
96 set(text) {
97 this.children = [];
98 treeAdapter.insertText(this, text);
99 }
100 },
101
102 setAttribute(name, value) {
103 if (this.attribs == null) this.attribs = {};
104 if (value == null) value = '';
105 this.attribs[name] = value;
106 },
107
108 removeAttribute(name) {
109 if (this.attribs != null) {
110 delete this.attribs[name];
111 }
112 },
113
114 getAttribute(name) {
115 return this.attribs != null && this.attribs[name];
116 },
117
118 hasAttribute(name) {
119 return this.attribs != null && this.attribs[name] != null;
120 },
121
122 getAttributeNode(name) {
123 const value = this.getAttribute(name);
124 if (value != null) return { specified: true, value };
125 }
126};
127
128/**
129 * Methods and descriptors to mix into the global document instance
130 * @private
131 */
132const DocumentExtensions = {
133 /** @extends htmlparser2.Document.prototype */
134
135 // document is just an Element in htmlparser2, giving it a nodeType of ELEMENT_NODE.
136 // TODO: verify if these are needed for css-select
137 nodeType: {
138 get() {
139 return 9;
140 }
141 },
142
143 contentType: {
144 get() {
145 return 'text/html';
146 }
147 },
148
149 nodeName: {
150 get() {
151 return '#document';
152 }
153 },
154
155 documentElement: {
156 get() {
157 // Find the first <html> element within the document
158 return this.childNodes.filter(
159 (child) => String(child.tagName).toLowerCase() === 'html'
160 );
161 }
162 },
163
164 compatMode: {
165 get() {
166 const compatMode = {
167 'no-quirks': 'CSS1Compat',
168 quirks: 'BackCompat',
169 'limited-quirks': 'CSS1Compat'
170 };
171 return compatMode[treeAdapter.getDocumentMode(this)];
172 }
173 },
174
175 body: {
176 get() {
177 return this.querySelector('body');
178 }
179 },
180
181 createElement(name) {
182 return treeAdapter.createElement(name, null, []);
183 },
184
185 createTextNode(text) {
186 // there is no dedicated createTextNode equivalent exposed in htmlparser2's DOM
187 const Node = this.$$Node;
188 return new Node({
189 type: 'text',
190 data: text,
191 parent: null,
192 prev: null,
193 next: null
194 });
195 },
196
197 querySelector(sel) {
198 return select.selectOne(sel, this.documentElement);
199 },
200
201 querySelectorAll(sel) {
202 if (sel === ':root') {
203 return this;
204 }
205 return select(sel, this.documentElement);
206 }
207};
208
209/**
210 * Essentially `Object.defineProperties()`, except function values are assigned as value descriptors for convenience.
211 * @private
212 */
213function defineProperties(obj, properties) {
214 for (const i in properties) {
215 const value = properties[i];
216 Object.defineProperty(
217 obj,
218 i,
219 typeof value === 'function' ? { value } : value
220 );
221 }
222}
223
224/**
225 * Create a property descriptor defining a getter/setter pair alias for a named attribute.
226 * @private
227 */
228function reflectedProperty(attributeName) {
229 return {
230 get() {
231 return this.getAttribute(attributeName);
232 },
233 set(value) {
234 this.setAttribute(attributeName, value);
235 }
236 };
237}
238
239/**
240 * Helper to get the text content of a node
241 * https://github.com/fb55/domutils/blob/master/src/stringify.ts#L21
242 * @private
243 */
244function getText(node) {
245 if (Array.isArray(node)) return node.map(getText).join('');
246 if (treeAdapter.isElementNode(node))
247 return node.name === 'br' ? '\n' : getText(node.children);
248 if (treeAdapter.isTextNode(node)) return node.data;
249 return '';
250}
Note: See TracBrowser for help on using the repository browser.