als-document 0.11.0 → 1.0.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.md CHANGED
@@ -1,200 +1,244 @@
1
- # Als-Document
1
+ # als-document: HTML Parser & DOM Manipulation Library
2
2
 
3
- *If something wrong or not working properly, please write me to: sh.mashkanta@gmail.com*
3
+ ## Overview
4
4
 
5
+ `als-document` is a powerful library for parsing HTML and manipulating the DOM structure on backend and frontend. It provides a robust and intuitive API for querying and interacting with DOM elements using selectors, making it a valuable tool for web developers.
5
6
 
6
- Document is a class which gets html as string and return new object with DOM tree.
7
- You can add or remove elements from DOM tree and modify each element.
8
- You can select elements and collections and read and modify them with given instruments.
9
7
 
10
- **0.11**
11
- * events issue fixed
8
+ ## Installation
12
9
 
10
+ To install the `als-document` library, use the following npm command:
13
11
 
14
- ## Write and Read files
15
- Document have 2 static methods to read and write files.
16
- The syntax:
17
- ```javascript
18
- Document.writeFile(filePath,obj,encoding = 'utf-8')
19
- Document.readFile(filePath,encoding = 'utf-8')
12
+ ```bash
13
+ npm i als-document
20
14
  ```
21
15
 
22
- * ``filepath`` - filepath can be string (absolute path to file) or array for joining.
23
- * ``obj`` - obj can be string or object for stringify.
24
- * ``encoding`` - encoding for read or write file
25
-
26
- Example:
27
- ```javascript
28
- let {Document} = require('als-document')
29
- let html = Document.readFile([__dirname,'index.html'])
30
- ```
31
16
 
17
+ ## Including the Library
32
18
 
33
- ## Creating new object
19
+ The library provides three different files to cater to different module systems:
34
20
 
35
- Document constructor get single string parameter - the outerHTML for converting to virtual DOM tree.
21
+ 1. **index.js**: This file uses the CommonJS module system. It's suitable for projects using Node.js or bundlers like Browserify or Webpack. The entry point in `package.json` for this file is "main".
36
22
 
37
23
  ```javascript
38
- let document = new Document(html) // html has to be string
39
- document.domTree // includes virtual DOM tree as array of elements
24
+ const { parseHTML, Node, Query, TextNode, SingleNode } = require('als-document');
40
25
  ```
41
26
 
27
+ 2. **index.mjs**: This file uses the ES Modules (ESM) system. It's suitable for modern JavaScript environments that support ESM. The entry point in `package.json` for this file is "module".
42
28
 
43
- ## QuerySelector for single element
44
- Then document object has created, you can select elements or collections.
45
- For selecting single element, use ``$(selector)`` and for selecting collections ``$$(selector)``.
29
+ ```js
30
+ import { parseHTML, Node, Query, TextNode, SingleNode } from 'als-document';
31
+ ```
46
32
 
47
- **Selecting element**
33
+ 3. **document.js**: By including this file, a constant variable named `alsDocument` is created, which wraps all the exports.
48
34
 
49
- ```javascript
50
- document.$('div') // select first div in document
51
- document.$('div.some') // select first div element with some class
35
+ ```html
36
+ <script src="/node_modules/als-document/document.js"></script>
37
+ <script>
38
+ const { parseHTML, Node, Query, TextNode, SingleNode } = alsDocument
39
+ </script>
52
40
  ```
53
41
 
54
- At this time, selector supports this:
55
- * Selects all elements - ``*``
56
- * element - ``div``
57
- * class - ``.some-class``
58
- * id - ``#some-id``
59
- * parent - ``div > p``
60
- * next - ``div + p``
61
- * previous - ``p ~ ul``
62
- * attribute - ``[some-attribute="some value"]``
63
- * ``[prop]``
64
- * ``[prop~=value]``
65
- * ``[prop|=value]``
66
- * ``[prop^="value"]``
67
- * ``[prop$="value"]``
68
- * ``[prop*="value"]``
42
+ ## parseHTML
69
43
 
44
+ `parseHTML` is a function that takes an HTML string and constructs a DOM tree representation from it. It recognizes various HTML elements, such as comments, scripts, styles, and CDATA, and organizes them into nodes that can be manipulated and queried.
70
45
 
71
- The folowing, **won't work**: ``div p``.
46
+ ### API:
47
+ `parseHTML(html: string) -> Node`
72
48
 
49
+ Parses an HTML string and returns a tree structure representing its content.
73
50
 
74
- Each returned element, has the folowing:
75
- ```javascript
76
- {
77
- parent, // parent element
78
- prev, // previous element (null if no exists)
79
- next, // next element (null if no exists)
80
- innerText, // innner text of element and it's childNodes separated by |
81
- children, // array of childNodes(elements and text nodes) - includes text element too
82
- tagName, // tag name of element
83
- id, // id of element if exists (not included in attributes)
84
- attributes, // object of attributes (id not included)
85
- classList, // array of classes and add and remove methods
86
- $(selector),
87
- $$(selector),
88
- json(), // remove all methods and circular objects from object
89
- remove(), // remove this element
90
- add(element/outerHtml,place),
91
- add0(element/outerHtml),
92
- add1(element/outerHtml),
93
- add2(element/outerHtml),
94
- add3(element/outerHtml),
95
- }
96
- ```
51
+ * `html`: The HTML string to parse.
52
+ * `Returns`: A Node object representing the root of the parsed HTML content tree.
97
53
 
98
- Text node has the folowing:
99
- ```javascript
100
- {
101
- text,
102
- prev,
103
- next
104
- }
105
- ```
54
+ ### Expected Outcome:
55
+ When using the parseHTML function, the output will be a tree of nodes representing the HTML content. Each node can be one of the following:
56
+ * **Node**: A standard HTML element node with tag name, attributes, and child nodes.
57
+ * **SingleNode**: Represents self-closing or void HTML elements.
58
+ * **TextNode**: Represents text content in the HTML.
106
59
 
107
- Comment node:
108
- ```javascript
109
- tagName:comment,
110
- comment // comment it self
111
- ```
60
+ Each node will have a tag name, a dictionary of attributes, and a list of child nodes (if applicable).
112
61
 
113
- You can add or remove classes with classList methods.
114
- Example:
115
- ```javascript
116
- let element = document.$('div')
117
- element.classList.remove('some')
118
- element.classList.add('another')
119
- element.classList.add('onemore')
120
- ```
62
+ ### Examples
121
63
 
122
- Also you can change element's id:
123
- ```javascript
124
- let element = document.$('div')
125
- element.id = 'new-id'
64
+ ```js
65
+ const parsedHTML = parseHTML('<div class="container"><img src="image.jpg" alt="Image"/><p>Hello, world!</p></div>');
66
+
67
+ // The returned `parsedHTML` object will be a tree-like structure.
68
+ // For instance, parsedHTML.childNodes[0] would represent the <div> element,
69
+ // and parsedHTML.childNodes[0].childNodes[0] would represent the <img> element inside it.
126
70
  ```
127
71
 
128
- ## Element methods
72
+ ```js
73
+ const parsedScript = parseHTML('<script>console.log("Hello, world!");</script>');
129
74
 
130
- ```javascript
131
- json() // remove all methods and circular objects from object
132
- remove() // remove this element
133
- add(element/outerHtml,place) // adding AdjacentHTML or AdjacentElement to place(0-3)
134
- add0(element/outerHtml) // adding AdjacentHTML or AdjacentElement beforebegin
135
- add1(element/outerHtml) // adding AdjacentHTML or AdjacentElement afterbegin
136
- add2(element/outerHtml) // adding AdjacentHTML or AdjacentElement beforeend
137
- add3(element/outerHtml) // adding AdjacentHTML or AdjacentElement afterend
75
+ // The returned `parsedScript` object will contain a `script` Node with a child node
76
+ // holding the JavaScript code as text content.
138
77
  ```
139
78
 
140
- Example:
141
- ```javascript
142
- let document = new Document(html)
143
- let a = document.$('a')
144
- let div = document.$('div')
145
- div.add2('<div id="test">Hello world</div>')
146
- div.add3(a)
147
- a.remove()
148
- ```
79
+ Remember, the actual tree structure will be more complex and detailed, but the provided examples give you a basic understanding of how to navigate through the parsed result.
149
80
 
81
+ ## Node
150
82
 
151
- Create new element with ``Document.newElement(outerHtml)``
83
+ `Node` is a fundamental class that represents an element node in the DOM tree. It provides functionality similar to the native DOM API in browsers, but with its own implementation.
152
84
 
153
- ```javascript
154
- Document.newElement('<div id="test">Hello world</div>')
155
- ```
85
+ ### Properties:
86
+ - **tagName**: Represents the tag name of the element.
87
+ - **attributes**: A dictionary of attributes and their values.
88
+ - **childNodes**: An array of child nodes for the element.
89
+ - **isSingle**: Boolean value to check if the node is a self-closing tag.
90
+ - **parentNode, previousElementSibling, nextElementSibling, children**: Navigation properties to move through the DOM tree.
91
+ - **dataset, classList, style**: Special properties for interacting with `data-*` attributes, classes, and inline styles.
156
92
 
93
+ ### Methods:
94
+ - **getAttribute, setAttribute, removeAttribute**: Manipulate element's attributes.
95
+ - **remove**: Removes the element from its parent.
96
+ - **innerHTML, outerHTML**: Get and set the inner or entire HTML of the element.
97
+ - **querySelector, querySelectorAll**: Find elements within the node based on CSS-like selectors.
98
+ - limits: pseudo selector like `:first-of-type` or `:checked` not available
99
+ - namaspace for tags `some:namspace` available
100
+ - there are additional methods `$` for `querySelector` and `$$` for `querySelectorAll`
101
+ - **getElementsByClassName, getElementsByTagName, getElementById**: Get elements by class, tag, or id respectively.
102
+ - **insertAdjacentElement, insertAdjacentHTML, insertAdjacentText**: Insert content relative to the element.
103
+ - **appendChild**: Add a child node to the element.
157
104
 
158
- ## QuerySelector for Collection ``$$()``
159
- To select few elements, use ``$$(selector)`` method.
160
105
 
161
- ```javascript
162
- document.$$('div') // return collection of all div elements
163
- ```
106
+ ### SingleNode
164
107
 
165
- The collection is array which has the elements and two methods: ``each`` and ``parse``.
108
+ `SingleNode` extends from the `Node` class and represents elements that don't have closing tags (self-closing tags) in HTML. Examples include `<img>`, `<br>`, and `<!DOCTYPE>`. This class has restricted methods and properties since these elements can't have child nodes.
166
109
 
167
- ``each`` method gets callback function with 3 parameters: element it self, index of the element in collection and collection itself.
110
+ ### TextNode
168
111
 
169
- Here example:
170
- ```javascript
171
- let array = []
172
- document.$$('div').each((element,index,collection) => {
173
- if(element.innerText.includes('some text'))
174
- array.push(element)
175
- })
176
- ```
112
+ `TextNode` is a class that represents text content within the DOM. A TextNode holds raw text data and does not have child nodes.
177
113
 
178
- ``parse`` method, gets two parameters: ``part`` and ``fn`` and return array with results.
179
- * ``part`` is a part of element. It can be innerText, id, tagName or any property inside attributes.
180
- * ``fn`` is a filter function which gets content of part. If return true, content will be included.
181
114
 
182
- Example:
183
- ```javascript
184
- new Document(htmlText).$$('div')
185
- .parse('innerText',
186
- content=> (content.length > 0) ? true : false)
187
- ```
188
115
 
189
- ## Building html
116
+ ### Examples:
190
117
 
191
- For building html again, use ``build`` method.
192
- Example:
193
118
  ```javascript
194
- let element = document.$('div')
195
- element.classList.add('another')
196
- element.classList.remove('some')
197
- element.id = 'new-id'
198
- document.build() // return new html text
199
- document.build([__dirname,'new-index.html']) // will create a file with new html text
119
+ const div = new Node('div');
120
+ div.setAttribute('class', 'container');
121
+
122
+ const img = new SingleNode('img', { src: 'image.jpg', alt: 'An image' });
123
+ div.appendChild(img);
124
+
125
+ console.log(div.outerHTML); // Outputs: <div class="container"><img src="image.jpg" alt="An image"></div>
126
+
127
+ const p = new Node('p',{},div); // adding as last child to parent div
128
+ p.textContent = "Hello, world!";
129
+
130
+ const foundP = div.querySelector('p');
131
+ console.log(foundP.textContent); // Outputs: Hello, world!
132
+ ```
133
+
134
+
135
+ ## Query
136
+
137
+ The `Query` class is designed to parse CSS selector strings and transform them into a structured object format, providing detailed insights into each selector and its components.
138
+
139
+ By using the class, one can expect to transform a CSS selector string into an array of objects.
140
+
141
+ Each object will represent a selector, containing detailed information such as its tag, identifier, classes, attributes, and associated selectors if any. This can be useful for further processing or analysis of CSS selectors in an application.
142
+
143
+ ### Example
144
+
145
+ ```javascript
146
+ let q1 = 'html>body>div.tabs~.some[type $= "radio and some"]>p+div>.some-id .tab-content~input[disabled] div.some'
147
+ let result = new Query(q1).selectors
148
+ let result1 = Query.get(q1)
149
+ // result and result1 has to be same
150
+ console.log(result)
151
+ ```
152
+
153
+ Result:
154
+ ```javascript
155
+ [
156
+ {
157
+ "query": "div.some",
158
+ "tag": "div",
159
+ "classList": [
160
+ "some"
161
+ ],
162
+ "ancestors": [
163
+ {
164
+ "query": ".some-id",
165
+ "classList": [
166
+ "some-id"
167
+ ],
168
+ "parents": [
169
+ {
170
+ "query": "div",
171
+ "tag": "div"
172
+ }
173
+ ],
174
+ "prev": {
175
+ "query": "p",
176
+ "tag": "p",
177
+ "parents": [
178
+ {
179
+ "query": ".some[0]",
180
+ "classList": [
181
+ "some"
182
+ ],
183
+ "attribs": [
184
+ {
185
+ check:(f),
186
+ "query": "[type$=\"radio and some\"]",
187
+ "name": "type",
188
+ "value": "radio and some",
189
+ "sign": "$="
190
+ }
191
+ ]
192
+ }
193
+ ],
194
+ "prevAny": {
195
+ "query": "div.tabs",
196
+ "tag": "div",
197
+ "classList": [
198
+ "tabs"
199
+ ],
200
+ "parents": [
201
+ {
202
+ "query": "html",
203
+ "tag": "html"
204
+ },
205
+ {
206
+ "query": "body",
207
+ "tag": "body"
208
+ }
209
+ ]
210
+ },
211
+ "group": "html>body>div.tabs~.some[0]>p"
212
+ },
213
+ "group": "html>body>div.tabs~.some[0]>p+div>.some-id"
214
+ },
215
+ {
216
+ "query": "input[1]",
217
+ "tag": "input",
218
+ "attribs": [
219
+ {
220
+ "query": "[disabled]",
221
+ "name": "disabled"
222
+ }
223
+ ],
224
+ "prevAny": {
225
+ "query": ".tab-content",
226
+ "classList": [
227
+ "tab-content"
228
+ ]
229
+ },
230
+ "group": ".tab-content~input[1]"
231
+ }
232
+ ],
233
+ "group": "html>body>div.tabs~.some[type $= \"radio and some\"]>p+div>.some-id .tab-content~input[disabled] div.some"
234
+ }
235
+ ]
236
+ ```
237
+
238
+ ### Attribs and check function
239
+ if attribute has value, attrib object will contain check function with one parameter for value to check.
240
+
241
+ ```javascript
242
+ let s = Query.get('[test^="some"]')[0]
243
+ console.log(s.attribs[0].check('some value test')) // true
200
244
  ```
package/src/build.js ADDED
@@ -0,0 +1,65 @@
1
+ const { readFileSync, writeFileSync, watchFile, watch } = require('fs')
2
+ const { join,basename } = require('path')
3
+
4
+ function optimizeCode(content) {
5
+ content = content.replace(/(?<!\\)\/\/.*$/gm, '') // remove comments
6
+ // content = content.replace(/\;\s*?$/gm,'') // remove ; at end of line
7
+ content = content.replace(/\[\s*?\n\s*/gm, '[') //
8
+ content = content.replace(/\s*?\]/gm, ']') //
9
+ content = content.replace(/\s*?$/gm, '') // remove space at end of line
10
+ content = content.replace(/^\s\s\s/gm, ' ') // change tripple space to double space
11
+ content = content.replace(/\s(\=|\>|\<|\-|\+|\*|\!)/g, (s) => s.trim()) // replace space before operators
12
+ content = content.replace(/(\=|\>|\-|\+|\*)\s/g, (s) => s.includes(' ') ? s.trim() : s) // replace space after operators
13
+ content = content.replace(/\,\s*?$\s*/gm, ',') // join lines separated with comma
14
+ content = content.replace(/\s?,\s?/g, ',') // trim comma
15
+ content = content.replace(/\s\{/g, '{')
16
+ return content
17
+ }
18
+
19
+ const root = join(__dirname, '..')
20
+ const files = {
21
+ 'query': ['query','check-element'],
22
+ 'node':[
23
+ 'dataset','find','text-node',
24
+ 'style','class-list','node','single-node'
25
+ ],
26
+ 'parse':['parse-atts','void-tags','parser'],
27
+ }
28
+ const fileList = []
29
+ function buildFileList() {
30
+ Object.entries(files).forEach(([dir,filenames]) => {
31
+ filenames.forEach(filename => {
32
+ fileList.push(join(__dirname,dir,filename+'.js'))
33
+ });
34
+ });
35
+ }
36
+
37
+ buildFileList()
38
+
39
+ function build() {
40
+ let content = fileList.map(filePath => readFileSync(filePath, 'utf-8')).join('\n');
41
+
42
+ const toReturn = '{ parseHTML, Node, Query, TextNode, SingleNode }'
43
+ content = optimizeCode(content)
44
+ writeFileSync(join(root, 'document.js'), `const alsDocument = (function(){\n${content}\nreturn ${toReturn}\n})()`)
45
+ writeFileSync(join(root, 'index.js'), content + '\n' + `module.exports = ${toReturn}`)
46
+ writeFileSync(join(root, 'index.mjs'), content + '\n' + `export default ${toReturn}`)
47
+ console.log('Files are builded')
48
+ }
49
+
50
+ build()
51
+ if (process.argv[2] === '--watch') {
52
+ let count = 1;
53
+ console.log('Waching...')
54
+ let lastChangeTime = Date.now()
55
+ Object.keys(files).forEach(dirName => {
56
+ const dirPath = join(__dirname,dirName)
57
+ watch(dirPath, (eventType, filename) => {
58
+ let newChangeTime = Date.now()
59
+ if(newChangeTime - lastChangeTime < 1000) return
60
+ build()
61
+ lastChangeTime = newChangeTime
62
+ console.log(`${filename} has changed (${count++})`)
63
+ });
64
+ })
65
+ }
@@ -0,0 +1,25 @@
1
+ class NodeClassList {
2
+ constructor(node) { this.node = node }
3
+ get classes() { return (this.node.attributes.class || "").split(" ").filter(Boolean) }
4
+ set classes(val) { this.node.attributes.class = val.join(" ") }
5
+ contains(className) { return this.classes.includes(className) }
6
+
7
+ add(className) {
8
+ const currentClasses = this.classes;
9
+ if (!currentClasses.includes(className)) this.classes = [...currentClasses, className];
10
+ }
11
+
12
+ remove(className) { this.classes = this.classes.filter(cls => cls !== className); }
13
+
14
+ toggle(className) {
15
+ if (this.classes.includes(className)) this.remove(className);
16
+ else this.add(className);
17
+ }
18
+
19
+ replace(oldClass, newClass) {
20
+ if (this.classes.includes(oldClass)) {
21
+ this.remove(oldClass);
22
+ this.add(newClass);
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,15 @@
1
+ const getDataName = prop => 'data-' + prop.toLowerCase()
2
+ function getDataset(element) {
3
+ return new Proxy(element.attributes, {
4
+ get: (target, prop) => {return target[getDataName(prop)]},
5
+ set: (target, prop, value) => {target[getDataName(prop)] = value; return true},
6
+ deleteProperty: (target, prop) => { // Удаляем data-* атрибут
7
+ const dataAttr = getDataName(prop)
8
+ if (dataAttr in target) {
9
+ delete target[dataAttr];
10
+ return true; // обозначает успешное удаление
11
+ }
12
+ return false;
13
+ }
14
+ });
15
+ }
@@ -0,0 +1,12 @@
1
+ function find(selectors,element,collection,first=false,firstTime = true) {
2
+ for(let selector of selectors) {
3
+ if(checkElement(element,selector)) collection.add(element)
4
+ }
5
+ if(element.children)
6
+ element.children.forEach(child => {
7
+ if(first && collection.size > 0) return
8
+ find(selectors,child,collection,first,false)
9
+ })
10
+
11
+ return firstTime ? [...collection] : collection
12
+ }
@@ -0,0 +1,159 @@
1
+ class Node {
2
+ constructor(tagName, attributes = {}, parent = null) {
3
+ this.isSingle = false;
4
+ this.tagName = tagName;
5
+ this.attributes = attributes;
6
+ this.childNodes = [];
7
+ if (parent !== null) parent.childNodes.push(this)
8
+ this.parent = parent;
9
+ this._classList = null; // Cache the classList instance
10
+ this.__style = null;
11
+ this._dataset = null
12
+ }
13
+
14
+ get id() { return this.attributes.id || null; }
15
+ get className() {return this.attributes.class || null}
16
+ get parentNode() { return this.parent }
17
+ get ancestors() {
18
+ const ancestors = []
19
+ let element = this.parent
20
+ while (element.tagName !== 'ROOT') {
21
+ ancestors.push(element)
22
+ element = element.parent
23
+ }
24
+ return ancestors.reverse()
25
+ }
26
+
27
+ get childIndex() { return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null }
28
+ get previousElementSibling() { return this.prev }
29
+ get prev() {
30
+ if (!this.childIndex) return null // if no index or index == 0
31
+ return this.parent.childNodes[this.childIndex - 1]
32
+ }
33
+
34
+ get nextElementSibling() { return this.next }
35
+ get next() {
36
+ if (!this.childIndex) return null
37
+ return this.parent.childNodes[this.childIndex + 1] || null
38
+ }
39
+
40
+ get dataset() {
41
+ if (!this._dataset) this._dataset = getDataset(this);
42
+ return this._dataset;
43
+ }
44
+
45
+ get classList() {
46
+ if (!this._classList) this._classList = new NodeClassList(this);
47
+ return this._classList;
48
+ }
49
+
50
+ get style() {
51
+ if (!this.__style) this.__style = buildStyle(this.attributes)
52
+ return this.__style
53
+ }
54
+
55
+ get outerHTML() {
56
+ const attrs = Object.entries(this.attributes).map(([key, val]) => `${key}="${val}"`).join(" ");
57
+ return `<${this.tagName} ${attrs}>${this.innerHTML}</${this.tagName}>`;
58
+ }
59
+
60
+ getAttribute(attrName) { return this.attributes[attrName] || null }
61
+ setAttribute(attrName, value) { this.attributes[attrName] = value }
62
+ removeAttribute(attrName) { delete this.attributes[attrName] }
63
+
64
+ remove() {
65
+ if (!this.parent) return
66
+ const index = this.childIndex;
67
+ if (index !== null) this.parent.childNodes.splice(index, 1);
68
+ }
69
+
70
+ get innerHTML() {
71
+ return this.childNodes.map(child => {
72
+ if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
73
+ else if (child instanceof TextNode) return child.textContent; // Assuming child is a text node or something that can be stringified.
74
+ else return child
75
+ }).join("");
76
+ }
77
+
78
+ $$(query) {return this.querySelectorAll(query)}
79
+ querySelectorAll(query) {
80
+ const selectors = Query.get(query)
81
+ return find(selectors, this, new Set())
82
+ }
83
+ $(query) {return this.querySelector(query)}
84
+ querySelector(query) {
85
+ const selectors = Query.get(query)
86
+ return find(selectors, this, new Set(), true)[0]
87
+ }
88
+
89
+ getElementsByClassName(query) { return this.querySelectorAll('.' + query) }
90
+ getElementsByTagName(query) { return this.querySelectorAll(query) }
91
+ getElementById(query) { return this.querySelector('#' + query) }
92
+
93
+ get children() {
94
+ return this.childNodes.filter(child => {
95
+ if (!(child instanceof Node)) return false
96
+ if (child.tagName === '#comment') return false
97
+ return true
98
+ });
99
+ }
100
+
101
+ insertAdjacentElement(position, newElement) {
102
+ if(newElement.tagName === 'ROOT' && newElement.childNodes.length > 0) newElement = newElement.childNodes[0]
103
+ const pos = position.toLowerCase();
104
+ if (pos === "afterbegin") this.childNodes.unshift(newElement);
105
+ else if (pos === "beforeend") this.childNodes.push(newElement);
106
+ if (!this.parent) return newElement
107
+ if (pos === "beforebegin") this.parent.childNodes.unshift(newElement);
108
+ else if (pos === "afterend") this.parent.childNodes.splice(this.childIndex + 1, 0, newElement);
109
+ return newElement
110
+ }
111
+
112
+ insertAdjacentHTML(position, html) {
113
+ const newNode = parseHTML(html);
114
+ return this.insertAdjacentElement(position, newNode);
115
+ }
116
+
117
+ insertAdjacentText(position, text) {
118
+ return this.insertAdjacentElement(position, new TextNode(text));
119
+ }
120
+
121
+ set innerHTML(html) {
122
+ const parsed = parseHTML(html);
123
+ this.childNodes = parsed.childNodes;
124
+ }
125
+
126
+ set outerHTML(html) {
127
+ const parsed = parseHTML(html);
128
+ if (!this.parent) return console.log('element has no parent node')
129
+ const index = this.childIndex
130
+ if (index !== null) this.parent.childNodes.splice(index, 1, ...parsed.childNodes);
131
+ }
132
+
133
+ appendChild(newChild) {
134
+ if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode) {
135
+ if (newChild.parent) newChild.parent.childNodes = newChild.parent.childNodes.filter(child => child !== newChild); // Если у newChild уже есть родительский узел, необходимо его удалить оттуда
136
+ } else if(typeof newChild === 'string') newChild = new TextNode(newChild)
137
+ else return newChild
138
+ this.childNodes.push(newChild);
139
+ newChild.parent = this;
140
+ return newChild; // возвращаем добавленный узел (по спецификации DOM)
141
+ }
142
+
143
+ get textContent() {
144
+ if (this.childNodes.length === 0) return this.nodeName === '#text' ? this.nodeValue : '';
145
+ return this.childNodes.map(child => { // Concatenate text content of this node and all descendants
146
+ if(child instanceof SingleNode) return ''
147
+ if(child instanceof TextNode) return child.nodeValue
148
+ if(child instanceof Node) return child.textContent;
149
+ else return child;
150
+ }).join(" ");
151
+ }
152
+
153
+ set textContent(value) {
154
+ this.childNodes = []; // Clear the current child nodes
155
+ if (value !== null && value !== undefined) { // Add a new text node with the given value
156
+ this.childNodes.push(value.toString()); // In your code, text nodes are just strings.
157
+ }
158
+ }
159
+ }