als-document 0.12.0 → 1.0.1-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/document.js +33 -589
- package/index.js +32 -0
- package/index.mjs +32 -0
- package/package.json +13 -10
- package/readme.md +196 -156
- package/src/build.js +65 -0
- package/src/node/class-list.js +25 -0
- package/src/node/dataset.js +15 -0
- package/src/node/find.js +12 -0
- package/src/node/node.js +159 -0
- package/src/node/single-node.js +30 -0
- package/src/node/style.js +26 -0
- package/src/node/text-node.js +9 -0
- package/src/parse/parse-atts.js +36 -0
- package/src/parse/parser.js +74 -0
- package/src/parse/void-tags.js +5 -0
- package/src/query/check-element.js +84 -0
- package/src/query/query.js +142 -0
- package/tests/data/html1.js +2579 -0
- package/tests/data/html2.js +1124 -0
- package/tests/data/svg.js +66 -0
- package/tests/index.html +30 -0
- package/tests/node.js +196 -0
- package/tests/parse-real.js +52 -0
- package/tests/parser.js +351 -0
- package/tests/query.js +66 -0
- package/tests/utils.js +37 -0
package/readme.md
CHANGED
|
@@ -1,204 +1,244 @@
|
|
|
1
|
-
#
|
|
1
|
+
# als-document: HTML Parser & DOM Manipulation Library
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
11
|
-
* events issue fixed
|
|
8
|
+
## Installation
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
* added getAttribute and setAttribute
|
|
10
|
+
To install the `als-document` library, use the following npm command:
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
The syntax:
|
|
19
|
-
```javascript
|
|
20
|
-
Document.writeFile(filePath,obj,encoding = 'utf-8')
|
|
21
|
-
Document.readFile(filePath,encoding = 'utf-8')
|
|
12
|
+
```bash
|
|
13
|
+
npm i als-document
|
|
22
14
|
```
|
|
23
15
|
|
|
24
|
-
* ``filepath`` - filepath can be string (absolute path to file) or array for joining.
|
|
25
|
-
* ``obj`` - obj can be string or object for stringify.
|
|
26
|
-
* ``encoding`` - encoding for read or write file
|
|
27
|
-
|
|
28
|
-
Example:
|
|
29
|
-
```javascript
|
|
30
|
-
let {Document} = require('als-document')
|
|
31
|
-
let html = Document.readFile([__dirname,'index.html'])
|
|
32
|
-
```
|
|
33
16
|
|
|
17
|
+
## Including the Library
|
|
34
18
|
|
|
35
|
-
|
|
19
|
+
The library provides three different files to cater to different module systems:
|
|
36
20
|
|
|
37
|
-
|
|
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".
|
|
38
22
|
|
|
39
23
|
```javascript
|
|
40
|
-
|
|
41
|
-
document.domTree // includes virtual DOM tree as array of elements
|
|
24
|
+
const { parseHTML, Node, Query, TextNode, SingleNode } = require('als-document');
|
|
42
25
|
```
|
|
43
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".
|
|
44
28
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
29
|
+
```js
|
|
30
|
+
import { parseHTML, Node, Query, TextNode, SingleNode } from 'als-document';
|
|
31
|
+
```
|
|
48
32
|
|
|
49
|
-
**
|
|
33
|
+
3. **document.js**: By including this file, a constant variable named `alsDocument` is created, which wraps all the exports.
|
|
50
34
|
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
|
|
35
|
+
```html
|
|
36
|
+
<script src="/node_modules/als-document/document.js"></script>
|
|
37
|
+
<script>
|
|
38
|
+
const { parseHTML, Node, Query, TextNode, SingleNode } = alsDocument
|
|
39
|
+
</script>
|
|
54
40
|
```
|
|
55
41
|
|
|
56
|
-
|
|
57
|
-
* Selects all elements - ``*``
|
|
58
|
-
* element - ``div``
|
|
59
|
-
* class - ``.some-class``
|
|
60
|
-
* id - ``#some-id``
|
|
61
|
-
* parent - ``div > p``
|
|
62
|
-
* next - ``div + p``
|
|
63
|
-
* previous - ``p ~ ul``
|
|
64
|
-
* attribute - ``[some-attribute="some value"]``
|
|
65
|
-
* ``[prop]``
|
|
66
|
-
* ``[prop~=value]``
|
|
67
|
-
* ``[prop|=value]``
|
|
68
|
-
* ``[prop^="value"]``
|
|
69
|
-
* ``[prop$="value"]``
|
|
70
|
-
* ``[prop*="value"]``
|
|
42
|
+
## parseHTML
|
|
71
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.
|
|
72
45
|
|
|
73
|
-
|
|
46
|
+
### API:
|
|
47
|
+
`parseHTML(html: string) -> Node`
|
|
74
48
|
|
|
49
|
+
Parses an HTML string and returns a tree structure representing its content.
|
|
75
50
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
parent, // parent element
|
|
80
|
-
prev, // previous element (null if no exists)
|
|
81
|
-
next, // next element (null if no exists)
|
|
82
|
-
innerText, // innner text of element and it's childNodes separated by |
|
|
83
|
-
children, // array of childNodes(elements and text nodes) - includes text element too
|
|
84
|
-
tagName, // tag name of element
|
|
85
|
-
id, // id of element if exists (not included in attributes)
|
|
86
|
-
attributes, // object of attributes (id not included)
|
|
87
|
-
classList, // array of classes and add and remove methods
|
|
88
|
-
getAttribute(name)
|
|
89
|
-
setAttribute(name,value)
|
|
90
|
-
$(selector),
|
|
91
|
-
$$(selector),
|
|
92
|
-
json(), // remove all methods and circular objects from object
|
|
93
|
-
remove(), // remove this element
|
|
94
|
-
add(element/outerHtml,place),
|
|
95
|
-
add0(element/outerHtml),
|
|
96
|
-
add1(element/outerHtml),
|
|
97
|
-
add2(element/outerHtml),
|
|
98
|
-
add3(element/outerHtml),
|
|
99
|
-
}
|
|
100
|
-
```
|
|
51
|
+
* `html`: The HTML string to parse.
|
|
52
|
+
* `Returns`: A Node object representing the root of the parsed HTML content tree.
|
|
101
53
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
next
|
|
108
|
-
}
|
|
109
|
-
```
|
|
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.
|
|
110
59
|
|
|
111
|
-
|
|
112
|
-
```javascript
|
|
113
|
-
tagName:comment,
|
|
114
|
-
comment // comment it self
|
|
115
|
-
```
|
|
60
|
+
Each node will have a tag name, a dictionary of attributes, and a list of child nodes (if applicable).
|
|
116
61
|
|
|
117
|
-
|
|
118
|
-
Example:
|
|
119
|
-
```javascript
|
|
120
|
-
let element = document.$('div')
|
|
121
|
-
element.classList.remove('some')
|
|
122
|
-
element.classList.add('another')
|
|
123
|
-
element.classList.add('onemore')
|
|
124
|
-
```
|
|
62
|
+
### Examples
|
|
125
63
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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.
|
|
130
70
|
```
|
|
131
71
|
|
|
132
|
-
|
|
72
|
+
```js
|
|
73
|
+
const parsedScript = parseHTML('<script>console.log("Hello, world!");</script>');
|
|
133
74
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
remove() // remove this element
|
|
137
|
-
add(element/outerHtml,place) // adding AdjacentHTML or AdjacentElement to place(0-3)
|
|
138
|
-
add0(element/outerHtml) // adding AdjacentHTML or AdjacentElement beforebegin
|
|
139
|
-
add1(element/outerHtml) // adding AdjacentHTML or AdjacentElement afterbegin
|
|
140
|
-
add2(element/outerHtml) // adding AdjacentHTML or AdjacentElement beforeend
|
|
141
|
-
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.
|
|
142
77
|
```
|
|
143
78
|
|
|
144
|
-
|
|
145
|
-
```javascript
|
|
146
|
-
let document = new Document(html)
|
|
147
|
-
let a = document.$('a')
|
|
148
|
-
let div = document.$('div')
|
|
149
|
-
div.add2('<div id="test">Hello world</div>')
|
|
150
|
-
div.add3(a)
|
|
151
|
-
a.remove()
|
|
152
|
-
```
|
|
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.
|
|
153
80
|
|
|
81
|
+
## Node
|
|
154
82
|
|
|
155
|
-
|
|
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.
|
|
156
84
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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.
|
|
160
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.
|
|
161
104
|
|
|
162
|
-
## QuerySelector for Collection ``$$()``
|
|
163
|
-
To select few elements, use ``$$(selector)`` method.
|
|
164
105
|
|
|
165
|
-
|
|
166
|
-
document.$$('div') // return collection of all div elements
|
|
167
|
-
```
|
|
106
|
+
### SingleNode
|
|
168
107
|
|
|
169
|
-
|
|
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.
|
|
170
109
|
|
|
171
|
-
|
|
110
|
+
### TextNode
|
|
172
111
|
|
|
173
|
-
|
|
174
|
-
```javascript
|
|
175
|
-
let array = []
|
|
176
|
-
document.$$('div').each((element,index,collection) => {
|
|
177
|
-
if(element.innerText.includes('some text'))
|
|
178
|
-
array.push(element)
|
|
179
|
-
})
|
|
180
|
-
```
|
|
112
|
+
`TextNode` is a class that represents text content within the DOM. A TextNode holds raw text data and does not have child nodes.
|
|
181
113
|
|
|
182
|
-
``parse`` method, gets two parameters: ``part`` and ``fn`` and return array with results.
|
|
183
|
-
* ``part`` is a part of element. It can be innerText, id, tagName or any property inside attributes.
|
|
184
|
-
* ``fn`` is a filter function which gets content of part. If return true, content will be included.
|
|
185
114
|
|
|
186
|
-
Example:
|
|
187
|
-
```javascript
|
|
188
|
-
new Document(htmlText).$$('div')
|
|
189
|
-
.parse('innerText',
|
|
190
|
-
content=> (content.length > 0) ? true : false)
|
|
191
|
-
```
|
|
192
115
|
|
|
193
|
-
|
|
116
|
+
### Examples:
|
|
194
117
|
|
|
195
|
-
For building html again, use ``build`` method.
|
|
196
|
-
Example:
|
|
197
118
|
```javascript
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
204
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
|
+
}
|
package/src/node/find.js
ADDED
|
@@ -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
|
+
}
|
package/src/node/node.js
ADDED
|
@@ -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
|
+
}
|