als-document 1.0.4-alpha → 1.0.5
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 +13 -6
- package/index.js +13 -6
- package/index.mjs +13 -6
- package/package.json +2 -5
- package/readme.md +33 -3
- package/src/build.js +3 -3
- package/src/node/node.js +43 -23
- package/src/node/root.js +11 -0
- package/src/node/single-node.js +5 -4
- package/src/parse/cache.js +33 -0
- package/src/parse/parse-atts.js +1 -1
- package/src/parse/parser.js +6 -4
- package/src/query/query.js +13 -6
- package/tests/cache.js +19 -0
- package/tests/index.html +6 -5
- package/tests/node.js +1 -1
- package/tests/parse-real.js +3 -2
- package/tests/parser.js +6 -6
- package/tests/test.js +169 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "als-document",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "A powerful HTML parser & DOM manipulation library for both backend and frontend.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -10,8 +10,5 @@
|
|
|
10
10
|
},
|
|
11
11
|
"keywords": ["HTML", "DOM", "parser", "manipulation", "backend", "frontend", "als-document"],
|
|
12
12
|
"author": "Alex Sorkin <alexsorkin1980@gmail.com>",
|
|
13
|
-
"license": "ISC"
|
|
14
|
-
"devDependencies": {
|
|
15
|
-
"als-simple-test": "^0.3.5"
|
|
16
|
-
}
|
|
13
|
+
"license": "ISC"
|
|
17
14
|
}
|
package/readme.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# als-document: HTML Parser & DOM Manipulation Library
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
## Overview
|
|
4
5
|
|
|
5
6
|
`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.
|
|
6
7
|
|
|
8
|
+
## Release notes
|
|
9
|
+
* als-document is still on alpha testing. All tested features works fine, but through the use, discovering some bugs or things that should work different. For example in this release, changed the way for storing attributes with empty value.
|
|
10
|
+
* Also, this release, has additional very powefull feature which is building cache for storing DOM tree as json and building back DOM from cache.
|
|
11
|
+
|
|
7
12
|
|
|
8
13
|
## Installation
|
|
9
14
|
|
|
@@ -21,13 +26,13 @@ The library provides three different files to cater to different module systems:
|
|
|
21
26
|
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".
|
|
22
27
|
|
|
23
28
|
```javascript
|
|
24
|
-
const { parseHTML, Node, Query, TextNode, SingleNode } = require('als-document');
|
|
29
|
+
const { parseHTML, Node, Query, TextNode, SingleNode,Root } = require('als-document');
|
|
25
30
|
```
|
|
26
31
|
|
|
27
32
|
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".
|
|
28
33
|
|
|
29
34
|
```js
|
|
30
|
-
import { parseHTML, Node, Query, TextNode, SingleNode } from 'als-document';
|
|
35
|
+
import { parseHTML, Node, Query, TextNode, SingleNode, Root } from 'als-document';
|
|
31
36
|
```
|
|
32
37
|
|
|
33
38
|
3. **document.js**: By including this file, a constant variable named `alsDocument` is created, which wraps all the exports.
|
|
@@ -35,7 +40,7 @@ import { parseHTML, Node, Query, TextNode, SingleNode } from 'als-document';
|
|
|
35
40
|
```html
|
|
36
41
|
<script src="/node_modules/als-document/document.js"></script>
|
|
37
42
|
<script>
|
|
38
|
-
const { parseHTML, Node, Query, TextNode, SingleNode } = alsDocument
|
|
43
|
+
const { parseHTML, Node, Query, TextNode, SingleNode, buildFromCache, cacheDoc, Root } = alsDocument
|
|
39
44
|
</script>
|
|
40
45
|
```
|
|
41
46
|
|
|
@@ -101,6 +106,7 @@ Remember, the actual tree structure will be more complex and detailed, but the p
|
|
|
101
106
|
- **getElementsByClassName, getElementsByTagName, getElementById**: Get elements by class, tag, or id respectively.
|
|
102
107
|
- **insertAdjacentElement, insertAdjacentHTML, insertAdjacentText**: Insert content relative to the element.
|
|
103
108
|
- **appendChild**: Add a child node to the element.
|
|
109
|
+
- **insert(place,element)**: place (0-3) or beforebegin,afterbegin,... eleemnt - raw html or element
|
|
104
110
|
|
|
105
111
|
|
|
106
112
|
### SingleNode
|
|
@@ -112,6 +118,15 @@ Remember, the actual tree structure will be more complex and detailed, but the p
|
|
|
112
118
|
`TextNode` is a class that represents text content within the DOM. A TextNode holds raw text data and does not have child nodes.
|
|
113
119
|
|
|
114
120
|
|
|
121
|
+
### Root node (extends Node)
|
|
122
|
+
|
|
123
|
+
Has additional getters and setters:
|
|
124
|
+
* getter root.title
|
|
125
|
+
* setter root.title
|
|
126
|
+
* getter root.body
|
|
127
|
+
* getter root.head
|
|
128
|
+
|
|
129
|
+
|
|
115
130
|
|
|
116
131
|
### Examples:
|
|
117
132
|
|
|
@@ -242,3 +257,18 @@ if attribute has value, attrib object will contain check function with one param
|
|
|
242
257
|
let s = Query.get('[test^="some"]')[0]
|
|
243
258
|
console.log(s.attribs[0].check('some value test')) // true
|
|
244
259
|
```
|
|
260
|
+
|
|
261
|
+
## buildFromCache and cacheDoc
|
|
262
|
+
|
|
263
|
+
Building DOM from raw html, usually takes tens of milliseconds. But now, you can build DOM once and save it's cache as regular stringified JSON.
|
|
264
|
+
The caching process and building from cache takes less then 5ms for each and require realy low resources.
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
How it works?
|
|
268
|
+
```js
|
|
269
|
+
const html = `` // some real html 255KB
|
|
270
|
+
const root = parseHTML(html); // 31.9ms
|
|
271
|
+
const cache = cacheDoc(root); // 2.4ms
|
|
272
|
+
const root1 = buildFromCache(cache); // 1.2ms
|
|
273
|
+
console.log(root.inneHTML === root1.innerHTML) // true
|
|
274
|
+
```
|
package/src/build.js
CHANGED
|
@@ -22,9 +22,9 @@ const files = {
|
|
|
22
22
|
'query': ['query','check-element'],
|
|
23
23
|
'node':[
|
|
24
24
|
'dataset','find','text-node',
|
|
25
|
-
'style','class-list','node','single-node'
|
|
25
|
+
'style','class-list','node','single-node','root'
|
|
26
26
|
],
|
|
27
|
-
'parse':['parse-atts','void-tags','parser'],
|
|
27
|
+
'parse':['parse-atts','void-tags','parser','cache'],
|
|
28
28
|
}
|
|
29
29
|
const fileList = []
|
|
30
30
|
function buildFileList() {
|
|
@@ -40,7 +40,7 @@ buildFileList()
|
|
|
40
40
|
function build() {
|
|
41
41
|
let content = fileList.map(filePath => readFileSync(filePath, 'utf-8')).join('\n');
|
|
42
42
|
|
|
43
|
-
const toReturn = '{ parseHTML, Node, Query, TextNode, SingleNode }'
|
|
43
|
+
const toReturn = '{ parseHTML, Node, Query, TextNode, SingleNode, buildFromCache, cacheDoc, Root }'
|
|
44
44
|
content = optimizeCode(content)
|
|
45
45
|
writeFileSync(join(root, 'document.js'), `const alsDocument = (function(){\n${content}\nreturn ${toReturn}\n})()`)
|
|
46
46
|
writeFileSync(join(root, 'index.js'), content + '\n' + `module.exports = ${toReturn}`)
|
package/src/node/node.js
CHANGED
|
@@ -19,10 +19,10 @@ class Node {
|
|
|
19
19
|
|
|
20
20
|
get id() { return this.attributes.id ? this.attributes.id : null; }
|
|
21
21
|
set id(newValue) { this.attributes.id = newValue; }
|
|
22
|
-
get className() {return this.attributes.class || null}
|
|
22
|
+
get className() { return this.attributes.class || null }
|
|
23
23
|
get parentNode() { return this.parent }
|
|
24
24
|
get ancestors() {
|
|
25
|
-
if(!this.parent) return []
|
|
25
|
+
if (!this.parent) return []
|
|
26
26
|
const ancestors = []
|
|
27
27
|
let element = this.parent
|
|
28
28
|
while (element.tagName !== 'ROOT') {
|
|
@@ -33,13 +33,13 @@ class Node {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
get childNodeIndex() {
|
|
36
|
-
if(!this.parent) return null
|
|
37
|
-
return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
|
|
36
|
+
if (!this.parent) return null
|
|
37
|
+
return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
get childIndex() {
|
|
41
|
-
if(!this.parent) return null
|
|
42
|
-
return this.parent.children ? this.parent.children.indexOf(this) : null
|
|
40
|
+
get childIndex() {
|
|
41
|
+
if (!this.parent) return null
|
|
42
|
+
return this.parent.children ? this.parent.children.indexOf(this) : null
|
|
43
43
|
}
|
|
44
44
|
get previousElementSibling() { return this.prev }
|
|
45
45
|
get prev() {
|
|
@@ -69,8 +69,8 @@ class Node {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
get outerHTML() {
|
|
72
|
-
const attrs = Object.entries(this.attributes).map(([key, val]) => `${key}="${val}"`).join(" ");
|
|
73
|
-
return `<${this.tagName}${attrs ? ' '+attrs : ''}>${this.innerHTML}</${this.tagName}>`;
|
|
72
|
+
const attrs = Object.entries(this.attributes).map(([key, val]) => val.length ? `${key}="${val}"` : key).join(" ");
|
|
73
|
+
return `<${this.tagName}${attrs ? ' ' + attrs : ''}>${this.innerHTML}</${this.tagName}>`;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
getAttribute(attrName) { return this.attributes[attrName] || null }
|
|
@@ -79,7 +79,7 @@ class Node {
|
|
|
79
79
|
|
|
80
80
|
remove() {
|
|
81
81
|
if (!this.parent) return
|
|
82
|
-
const index = this.
|
|
82
|
+
const index = this.childNodeIndex;
|
|
83
83
|
if (index !== null) this.parent.childNodes.splice(index, 1);
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -91,12 +91,12 @@ class Node {
|
|
|
91
91
|
}).join("");
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
$$(query) {return this.querySelectorAll(query)}
|
|
94
|
+
$$(query) { return this.querySelectorAll(query) }
|
|
95
95
|
querySelectorAll(query) {
|
|
96
96
|
const selectors = Query.get(query)
|
|
97
97
|
return find(selectors, this, new Set())
|
|
98
98
|
}
|
|
99
|
-
$(query) {return this.querySelector(query)}
|
|
99
|
+
$(query) { return this.querySelector(query) }
|
|
100
100
|
querySelector(query) {
|
|
101
101
|
const selectors = Query.get(query)
|
|
102
102
|
return find(selectors, this, new Set(), true)[0] || null
|
|
@@ -115,12 +115,15 @@ class Node {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
insertAdjacentElement(position, newElement) {
|
|
118
|
-
if(newElement.tagName === 'ROOT' && newElement.childNodes.length > 0) newElement = newElement.childNodes[0]
|
|
118
|
+
if (newElement.tagName === 'ROOT' && newElement.childNodes.length > 0) newElement = newElement.childNodes[0]
|
|
119
119
|
const pos = position.toLowerCase();
|
|
120
|
-
if (pos ===
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
if (pos === 'afterbegin' || pos === 'beforeend') {
|
|
121
|
+
if (pos === "afterbegin") this.childNodes.unshift(newElement);
|
|
122
|
+
else if (pos === "beforeend") this.childNodes.push(newElement);
|
|
123
|
+
newElement.parent = this
|
|
124
|
+
return newElement
|
|
125
|
+
}
|
|
126
|
+
if (!this.parent) throw new Error("Can't insert element to element without parent")
|
|
124
127
|
if (pos === "beforebegin") insertBefore(this.parent.childNodes, this.childNodeIndex, newElement)
|
|
125
128
|
else if (pos === "afterend") this.parent.childNodes.splice(this.childNodeIndex + 1, 0, newElement);
|
|
126
129
|
newElement.parent = this.parent
|
|
@@ -139,9 +142,26 @@ class Node {
|
|
|
139
142
|
return this.insertAdjacentElement(position, new TextNode(text));
|
|
140
143
|
}
|
|
141
144
|
|
|
145
|
+
insert(position, element) {
|
|
146
|
+
const positions = ['beforebegin', 'afterbegin', 'beforeend', 'afterend']
|
|
147
|
+
if (positions[position]) position = positions[position]
|
|
148
|
+
if (typeof element === 'string') {
|
|
149
|
+
element = element.trim()
|
|
150
|
+
if (element.startsWith('<') && element.endsWith('>')) {
|
|
151
|
+
return this.insertAdjacentHTML(position, element)
|
|
152
|
+
}
|
|
153
|
+
return this.insertAdjacentText(position, element)
|
|
154
|
+
}
|
|
155
|
+
return this.insertAdjacentElement(position, element)
|
|
156
|
+
}
|
|
157
|
+
|
|
142
158
|
set innerHTML(html) {
|
|
143
|
-
|
|
144
|
-
|
|
159
|
+
if (this.tagName === 'script' || this.tagName === 'style') {
|
|
160
|
+
this.childNodes.length ? this.childNodes[0] = html : this.childNodes.push(html)
|
|
161
|
+
} else {
|
|
162
|
+
const parsed = parseHTML(html);
|
|
163
|
+
this.childNodes = parsed.childNodes;
|
|
164
|
+
}
|
|
145
165
|
}
|
|
146
166
|
|
|
147
167
|
set outerHTML(html) {
|
|
@@ -154,7 +174,7 @@ class Node {
|
|
|
154
174
|
appendChild(newChild) {
|
|
155
175
|
if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode) {
|
|
156
176
|
if (newChild.parent) newChild.parent.childNodes = newChild.parent.childNodes.filter(child => child !== newChild); // Если у newChild уже есть родительский узел, необходимо его удалить оттуда
|
|
157
|
-
} else if(typeof newChild === 'string') newChild = new TextNode(newChild)
|
|
177
|
+
} else if (typeof newChild === 'string') newChild = new TextNode(newChild)
|
|
158
178
|
else return newChild
|
|
159
179
|
this.childNodes.push(newChild);
|
|
160
180
|
newChild.parent = this;
|
|
@@ -164,9 +184,9 @@ class Node {
|
|
|
164
184
|
get textContent() {
|
|
165
185
|
if (this.childNodes.length === 0) return this.nodeName === '#text' ? this.nodeValue : '';
|
|
166
186
|
return this.childNodes.map(child => { // Concatenate text content of this node and all descendants
|
|
167
|
-
if(child instanceof SingleNode) return ''
|
|
168
|
-
if(child instanceof TextNode) return child.nodeValue
|
|
169
|
-
if(child instanceof Node) return child.textContent;
|
|
187
|
+
if (child instanceof SingleNode) return ''
|
|
188
|
+
if (child instanceof TextNode) return child.nodeValue
|
|
189
|
+
if (child instanceof Node) return child.textContent;
|
|
170
190
|
else return child;
|
|
171
191
|
}).join(" ");
|
|
172
192
|
}
|
package/src/node/root.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class Root extends Node {
|
|
2
|
+
constructor() {
|
|
3
|
+
super('ROOT',{},null);
|
|
4
|
+
this.isSingle = false
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
get body() {return this.$('body')}
|
|
8
|
+
get head() {return this.$('head')}
|
|
9
|
+
get title() {return this.$('title')}
|
|
10
|
+
set title(title) {return this.$('title').innerHTML = title}
|
|
11
|
+
}
|
package/src/node/single-node.js
CHANGED
|
@@ -5,12 +5,12 @@ class SingleNode extends Node {
|
|
|
5
5
|
this.isSingle = true
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
get outerHTML() { //
|
|
8
|
+
get outerHTML() { // outerHTML for single node
|
|
9
9
|
if (this.tagName === "#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
|
|
10
|
-
const attrs = Object.entries(this.attributes).map(([key, val]) => `${key}="${val}"`).join(" ");
|
|
10
|
+
const attrs = Object.entries(this.attributes).map(([key, val]) => val.length ? `${key}="${val}"` : key).join(" ");
|
|
11
11
|
return `<${this.tagName} ${attrs}${this.tagName === '?xml' ? '?' : ''}>`;
|
|
12
12
|
}
|
|
13
|
-
//
|
|
13
|
+
// Remove getters,setters and methods which no make sence in single node
|
|
14
14
|
get innerHTML() { return ""; }
|
|
15
15
|
set innerHTML(_) { }
|
|
16
16
|
$(_) {return null}
|
|
@@ -25,6 +25,7 @@ class SingleNode extends Node {
|
|
|
25
25
|
insertAdjacentHTML(_, __) { }
|
|
26
26
|
insertAdjacentText(_, __) { }
|
|
27
27
|
appendChild(_) { }
|
|
28
|
+
insert(_,__) { }
|
|
28
29
|
get textContent() { return ""; }
|
|
29
30
|
set textContent(_) { }
|
|
30
|
-
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function buildFromCache(cached) {
|
|
2
|
+
function buildNode(cache,parent=null) {
|
|
3
|
+
if(typeof cache === 'string') return parent.childNodes.push(cache)
|
|
4
|
+
const {isSingle,tagName,attributes,childNodes,textContent} = cache
|
|
5
|
+
if(textContent) return parent.childNodes.push(new TextNode(textContent))
|
|
6
|
+
if(isSingle) return parent.childNodes.push(new SingleNode(tagName,attributes))
|
|
7
|
+
const newDoc = tagName === 'ROOT' ? new Root() : new Node(tagName,attributes,parent)
|
|
8
|
+
childNodes.forEach(childNode => {
|
|
9
|
+
buildNode(childNode,newDoc)
|
|
10
|
+
});
|
|
11
|
+
return newDoc
|
|
12
|
+
}
|
|
13
|
+
return buildNode(cached)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
function cacheDoc(doc) {
|
|
18
|
+
const props = ['isSingle','tagName','attributes']
|
|
19
|
+
function addToCache(element,cache={}) {
|
|
20
|
+
if(typeof element === 'string') return element
|
|
21
|
+
if(element.nodeName === '#text') return {textContent:element.textContent}
|
|
22
|
+
props.forEach(prop => {
|
|
23
|
+
if(element[prop]) cache[prop] = element[prop]
|
|
24
|
+
});
|
|
25
|
+
if(!element.childNodes) return cache
|
|
26
|
+
cache.childNodes = []
|
|
27
|
+
element.childNodes.forEach(childNode => {
|
|
28
|
+
cache.childNodes.push(addToCache(childNode))
|
|
29
|
+
});
|
|
30
|
+
return cache
|
|
31
|
+
}
|
|
32
|
+
return addToCache(doc)
|
|
33
|
+
}
|
package/src/parse/parse-atts.js
CHANGED
|
@@ -31,6 +31,6 @@ function parseAttributes(str) {
|
|
|
31
31
|
else value += char;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
if (key.trim() && !value) attrs[key.trim()] =
|
|
34
|
+
if (key.trim() && !value) attrs[key.trim()] = ''; // After the loop, check if there's a leftover key, which would be an attribute without a value
|
|
35
35
|
return attrs;
|
|
36
36
|
}
|
package/src/parse/parser.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
function parseHTML(html) {
|
|
2
|
-
const root = new
|
|
2
|
+
const root = new Root();
|
|
3
3
|
const stack = [root];
|
|
4
4
|
let currentText = "", i = 0;
|
|
5
5
|
let max = 0
|
|
@@ -51,8 +51,10 @@ function parseHTML(html) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
if (html.startsWith("<", i)) {
|
|
54
|
-
if (currentText) {
|
|
55
|
-
|
|
54
|
+
if (currentText && stack[stack.length - 1]) {
|
|
55
|
+
const textNode = new TextNode(currentText)
|
|
56
|
+
stack[stack.length - 1].childNodes.push(textNode);
|
|
57
|
+
textNode.parent = stack[stack.length - 1]
|
|
56
58
|
currentText = "";
|
|
57
59
|
}
|
|
58
60
|
|
|
@@ -91,6 +93,6 @@ function parseHTML(html) {
|
|
|
91
93
|
i++;
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
|
-
if (currentText.trim()) stack[stack.length - 1].childNodes.push(new TextNode(currentText));
|
|
96
|
+
if (currentText.trim() && stack[stack.length - 1]) stack[stack.length - 1].childNodes.push(new TextNode(currentText));
|
|
95
97
|
return root;
|
|
96
98
|
}
|
package/src/query/query.js
CHANGED
|
@@ -31,7 +31,7 @@ class Query {
|
|
|
31
31
|
|
|
32
32
|
splitAndCutLast(string, splitBy) {
|
|
33
33
|
const array = string.split(splitBy);
|
|
34
|
-
const last = array.pop();
|
|
34
|
+
const last = array.pop();
|
|
35
35
|
return [last, array];
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -104,11 +104,18 @@ class Query {
|
|
|
104
104
|
attribs = attribs.map(attrib => {
|
|
105
105
|
let query = attrib
|
|
106
106
|
attrib = attrib.replace('[', '').replace(']', '')
|
|
107
|
-
let [name
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if
|
|
107
|
+
let [name,...values] = attrib.split('=')
|
|
108
|
+
const value = values.join('=').trim().replace(/^\"/,'').replace(/\"$/,'')
|
|
109
|
+
let sign
|
|
110
|
+
attrib = {query,name}
|
|
111
|
+
if(value) {
|
|
112
|
+
sign = '='
|
|
113
|
+
attrib.name = attrib.name.replace(/[\~\|\^\$\*]$/,(match => {
|
|
114
|
+
sign = match+sign
|
|
115
|
+
return ''
|
|
116
|
+
}))
|
|
117
|
+
attrib.value = value
|
|
118
|
+
}
|
|
112
119
|
if (sign) {
|
|
113
120
|
attrib.sign = sign
|
|
114
121
|
attrib.check = this.getAttribFn(sign).bind(attrib)
|
package/tests/cache.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function mesureTime(fn) {
|
|
2
|
+
let time = performance.now()
|
|
3
|
+
const result = fn()
|
|
4
|
+
time = performance.now() - time
|
|
5
|
+
return {result,time}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
describe('Cache and build from cache', () => {
|
|
9
|
+
const {result:root,time:rootTime} = mesureTime(() => parseHTML(html1))
|
|
10
|
+
const {result:cache,time:cacheTime} = mesureTime(() => cacheDoc(root))
|
|
11
|
+
const {result:root1,time:root1Time} = mesureTime(() => buildFromCache(cache))
|
|
12
|
+
|
|
13
|
+
it('HTML from cache and HTML from not cached same',() => {
|
|
14
|
+
console.log({rootTime,cacheTime,root1Time})
|
|
15
|
+
assert(cacheTime < 5,'Build cache takes less then 5ms')
|
|
16
|
+
assert(root1Time < 5,'Build DOM from cache takes less then 5ms')
|
|
17
|
+
assert(root.innerHTML === root1.innerHTML)
|
|
18
|
+
})
|
|
19
|
+
})
|
package/tests/index.html
CHANGED
|
@@ -4,20 +4,21 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Document</title>
|
|
7
|
-
<script src="
|
|
7
|
+
<script src="test.js"></script>
|
|
8
8
|
<script src="../document.js"></script>
|
|
9
|
-
<script src="./data/html1.js"></script>
|
|
10
|
-
|
|
9
|
+
<!-- <script src="./data/html1.js"></script> -->
|
|
10
|
+
<script src="./data/html2.js"></script>
|
|
11
11
|
<script src="./data/svg.js"></script>
|
|
12
12
|
<script>
|
|
13
|
-
const { parseHTML, Node, Query, TextNode, SingleNode } = alsDocument
|
|
13
|
+
const { parseHTML, Node, Query, TextNode, SingleNode, buildFromCache, cacheDoc,Root } = alsDocument
|
|
14
14
|
let {describe,it,beforeEach,runTests,expect,delay,assert,beforeAll} = SimpleTest
|
|
15
15
|
SimpleTest.showFullError = true
|
|
16
16
|
</script>
|
|
17
|
-
<script src="./query.js"></script>
|
|
18
17
|
<script src="utils.js"></script>
|
|
18
|
+
<script src="./query.js"></script>
|
|
19
19
|
<script src="parser.js"></script>
|
|
20
20
|
<script src="node.js"></script>
|
|
21
|
+
<script src="./cache.js"></script>
|
|
21
22
|
</head>
|
|
22
23
|
<body>
|
|
23
24
|
<script src="parse-real.js"></script>
|
package/tests/node.js
CHANGED
|
@@ -130,7 +130,7 @@ describe('Content Manipulation', () => {
|
|
|
130
130
|
expect(rootNode.childNodes[2].tagName).equalTo('a');
|
|
131
131
|
|
|
132
132
|
childNode.insertAdjacentHTML('beforebegin', '<strong></strong>');
|
|
133
|
-
expect(rootNode.childNodes[
|
|
133
|
+
expect(rootNode.childNodes[1].tagName).equalTo('strong');
|
|
134
134
|
|
|
135
135
|
childNode.insertAdjacentText('afterend', 'Some text');
|
|
136
136
|
expect(typeof rootNode.childNodes[3].nodeValue).equalTo('string');
|
package/tests/parse-real.js
CHANGED
|
@@ -32,8 +32,9 @@ describe('Real data html1', async () => {
|
|
|
32
32
|
it('Text nodes check', () => {
|
|
33
33
|
const realParagraph = iframe.querySelector('p');
|
|
34
34
|
const parsedParagraph = parsedHTML.querySelector('p');
|
|
35
|
-
const real = realParagraph.textContent.trim().replace(/\n
|
|
36
|
-
const parsed = parsedParagraph.textContent.trim()
|
|
35
|
+
const real = realParagraph.textContent.trim().replace(/\n|\s/gm,'')
|
|
36
|
+
const parsed = parsedParagraph.textContent.trim().replace(/\n|\s/gm,'')
|
|
37
|
+
console.log({parsed,real})
|
|
37
38
|
assert(real === parsed, 'Text contents are the same');
|
|
38
39
|
});
|
|
39
40
|
|
package/tests/parser.js
CHANGED
|
@@ -70,7 +70,7 @@ describe('Advanced tests', () => {
|
|
|
70
70
|
let time = Date.now() - now
|
|
71
71
|
// console.log(memoryAfter - memoryBefore)
|
|
72
72
|
assert(time < 20, `Big html (${(deepHTML.length / 1024).toFixed(2)}KB) in less then 20ms (${time}ms)`)
|
|
73
|
-
expect(result).instanceof(
|
|
73
|
+
expect(result).instanceof(Root); // or any other validation you see fit
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
it('handles incorrectly closed tags', () => {
|
|
@@ -225,11 +225,11 @@ describe('signle tags, script and style', () => {
|
|
|
225
225
|
<link rel="stylesheet" href="styles.css">
|
|
226
226
|
`;
|
|
227
227
|
const rootMetaLink = parseHTML(testMetaLink);
|
|
228
|
-
assert(rootMetaLink.
|
|
229
|
-
assert(rootMetaLink.
|
|
230
|
-
assert(rootMetaLink.
|
|
231
|
-
assert(rootMetaLink.
|
|
232
|
-
assert(rootMetaLink.
|
|
228
|
+
assert(rootMetaLink.children[0].tagName === "meta", "Test Meta/Link 1: Meta tag not created");
|
|
229
|
+
assert(rootMetaLink.children[0].getAttribute("charset") === "UTF-8", "Test Meta/Link 1: Meta content not correct");
|
|
230
|
+
assert(rootMetaLink.children[1].tagName === "link", "Test Meta/Link 2: Link tag not created");
|
|
231
|
+
assert(rootMetaLink.children[1].getAttribute("rel") === "stylesheet", "Test Meta/Link 2: Link rel attribute not correct");
|
|
232
|
+
assert(rootMetaLink.children[1].getAttribute("href") === "styles.css", "Test Meta/Link 2: Link href attribute not correct");
|
|
233
233
|
})
|
|
234
234
|
|
|
235
235
|
it('broken html structure', () => {
|