als-document 1.4.2 → 1.5.0
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/browser-tests/index.html +6 -8
- package/browser-tests/node.js +18 -18
- package/browser-tests/parse-real.js +8 -8
- package/browser-tests/parser.js +36 -36
- package/browser-tests/query.js +36 -39
- package/build-readme.js +29 -7
- package/build.js +14 -14
- package/docs/changelog.md +18 -0
- package/document.js +198 -9
- package/index.js +198 -9
- package/index.mjs +199 -9
- package/lib/node/document.js +23 -6
- package/lib/node/node.js +10 -3
- package/lib/node/style.js +47 -26
- package/lib/parse/cache.js +6 -2
- package/lib/parse/parse-atts.js +36 -36
- package/lib/parse/parser.js +134 -99
- package/lib/query/query.js +3 -3
- package/package.json +2 -2
- package/readme.md +26 -8
- package/tests/cache.test.js +68 -3
- package/tests/document.test.js +98 -2
- package/tests/node.test.js +129 -3
- package/tests/parser.test.js +242 -6
- package/tests/query.test.js +61 -3
- package/browser-tests/simple-test.js +0 -169
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class Query{
|
|
2
1
|
static get(query){
|
|
3
2
|
let q=new Query(query)
|
|
4
3
|
return q.selectors
|
|
5
4
|
}
|
|
6
5
|
constructor(query){
|
|
7
6
|
this.query=query
|
|
8
7
|
this.selectors=[]
|
|
9
8
|
this.stringValues=[];
|
|
10
9
|
this.parseSelectors(query.split(','))
|
|
11
10
|
}
|
|
12
11
|
parseSelectors(selectors){
|
|
13
12
|
selectors.forEach(selector=>{
|
|
14
13
|
let originalSelector=selector.trim()
|
|
15
14
|
selector=this.removeSpaces(selector)
|
|
16
15
|
this.stringValues=[]
|
|
17
16
|
selector=selector.replace(/\[.*?\]/g,(value)=>{
|
|
18
17
|
this.stringValues.push(value)
|
|
19
18
|
return `[${this.stringValues.length-1}]`
|
|
20
19
|
})
|
|
21
20
|
let [element,ancestors]=this.splitAndCutLast(selector,' ')
|
|
22
21
|
element=this.getFamily(element)
|
|
23
22
|
if (ancestors.length>0)
|
|
24
23
|
element.ancestors=ancestors.map(ancestor=>this.getFamily(ancestor))
|
|
25
24
|
element.group=originalSelector
|
|
26
25
|
this.selectors.push(element)
|
|
27
26
|
});
|
|
28
27
|
}
|
|
29
28
|
splitAndCutLast(string,splitBy){
|
|
30
29
|
const array=string.split(splitBy);
|
|
31
30
|
const last=array.pop();
|
|
32
31
|
return [last,array];
|
|
33
32
|
}
|
|
34
33
|
getFamily(group,element,prev,prevAny,sign){
|
|
35
34
|
if (group.match(/\~|\+/)!==null){
|
|
36
35
|
let [last,prevBrothers]=this.splitAndCutLast(group,/\~|\+/)
|
|
37
36
|
let signs=group.replace(last,'')
|
|
38
37
|
prevBrothers.forEach(el=>signs=signs.replace(el,''))
|
|
39
38
|
signs=signs.match(/\~|\+/g)
|
|
40
39
|
if (signs.length==1){
|
|
41
40
|
sign=signs[0]
|
|
42
41
|
} else if (signs.length>1){
|
|
43
42
|
sign=signs.splice(signs.length-1,signs.length-1)[0]
|
|
44
43
|
prevBrothers[0]=prevBrothers.map((b,i)=>{
|
|
45
44
|
if (i< prevBrothers.length-1) b+=signs[i]
|
|
46
45
|
return b
|
|
47
46
|
}).join('')
|
|
48
47
|
prevBrothers[0]=this.getFamily(prevBrothers[0])
|
|
49
48
|
}
|
|
50
49
|
if (sign=='~') prevAny=prevBrothers[0]
|
|
51
50
|
else if (sign=='+') prev=prevBrothers[0]
|
|
52
51
|
element=last
|
|
53
52
|
} else element=group
|
|
54
53
|
let family
|
|
55
54
|
if (prev || prevAny){
|
|
56
55
|
family=this.getParents(element)
|
|
57
56
|
if (prev) family.prev=this.getParents(prev)
|
|
58
57
|
if (prevAny) family.prevAny=this.getParents(prevAny)
|
|
59
58
|
} else family=this.getParents(element)
|
|
60
59
|
if (family.query!==group) family.group=group
|
|
61
60
|
return family
|
|
62
61
|
}
|
|
63
62
|
getParents(selector){
|
|
64
63
|
if (typeof selector=='string'){
|
|
65
64
|
let [element,parents]=this.splitAndCutLast(selector,'>')
|
|
66
65
|
element=this.buildElement(element)
|
|
67
66
|
parents=parents.map(parent=>this.buildElement(parent))
|
|
68
67
|
if (parents.length>0) element.parents=parents
|
|
69
68
|
return element
|
|
70
69
|
} else return selector
|
|
71
70
|
}
|
|
72
71
|
buildElement(element,id=null,tag=null,classList=[]){
|
|
73
72
|
let query=element
|
|
74
73
|
element=element.replace(/\#(\w-?)*/,$id=>{
|
|
75
74
|
id=$id.replace(/^\#/,''); return ''
|
|
76
75
|
})
|
|
77
76
|
element=element.replace(/\.(\w-?)*/,$class=>{
|
|
78
77
|
classList.push($class.replace(/^\./,'')); return ''
|
|
79
78
|
})
|
|
80
79
|
element=element.replace(/(\w\:?-?)*/,$tag=>{
|
|
81
80
|
tag=$tag=='' ? null : $tag; return ''
|
|
82
81
|
})
|
|
83
82
|
let attribs=this.getAttributes(element)
|
|
84
83
|
element={ query }
|
|
85
84
|
if (id) element.id=id
|
|
86
85
|
if (tag) element.tag=tag
|
|
87
86
|
if (classList.length>0) element.classList=classList
|
|
88
87
|
if (attribs.length>0) element.attribs=attribs
|
|
89
88
|
return element
|
|
90
89
|
}
|
|
91
90
|
getAttributes(element){
|
|
92
91
|
let attribs=this.stringValues.filter((value,index)=>{
|
|
93
92
|
let searchValue=`[${index}]`
|
|
94
93
|
if (element.match(searchValue)) return true
|
|
95
94
|
else return false
|
|
96
95
|
})
|
|
97
96
|
attribs=attribs.map(attrib=>{
|
|
98
97
|
let query=attrib
|
|
99
98
|
attrib=attrib.replace('[','').replace(']','')
|
|
100
99
|
let [name,...values]=attrib.split('=')
|
|
101
100
|
const value=values.join('=').trim().replace(/^\"/,'').replace(/\"$/,'')
|
|
102
101
|
let sign
|
|
103
102
|
attrib={query,name}
|
|
104
103
|
if(value){
|
|
105
104
|
sign='='
|
|
106
105
|
attrib.name=attrib.name.replace(/[\~\|\^\$\*]$/,(match=>{
|
|
107
106
|
sign=match+sign
|
|
108
107
|
return ''
|
|
109
108
|
}))
|
|
110
109
|
attrib.value=value
|
|
111
110
|
}
|
|
112
111
|
if (sign){
|
|
113
112
|
attrib.sign=sign
|
|
114
113
|
attrib.check=this.getAttribFn(sign).bind(attrib)
|
|
115
114
|
}
|
|
116
115
|
return attrib
|
|
117
116
|
});
|
|
118
117
|
return attribs
|
|
119
118
|
}
|
|
120
119
|
getAttribFn(sign){
|
|
121
120
|
if (sign=='=') return function (value){ return value===this.value }
|
|
122
121
|
if (sign=='*=') return function (value){ return value.includes(this.value) }
|
|
123
122
|
if (sign=='^=') return function (value){ return value.startsWith(this.value) }
|
|
124
123
|
if (sign=='$=') return function (value){ return value.endsWith(this.value) }
|
|
125
124
|
if (sign=='|=') return function (value){
|
|
126
125
|
return value.trim().split(' ').length==1
|
|
127
126
|
&& (value.startsWith(this.value) || value.startsWith(this.value+'-'))
|
|
128
127
|
? true : false
|
|
129
128
|
}
|
|
130
129
|
if (sign=='~=') return function (value){
|
|
131
130
|
return this.value.trim().split(' ').length==1 && value.includes(this.value) ? true : false
|
|
132
131
|
}
|
|
133
132
|
}
|
|
134
133
|
removeSpaces(selector){
|
|
135
134
|
selector=selector.replace(/\s{2}/g,' ')
|
|
136
135
|
selector=selector.replace(/\s?\^?\$?\|?\~?\*?\=\s*/g,(m)=>m.trim())
|
|
137
136
|
selector=selector.replace(/\s?(\+|\~|\>)\s?/g,(m)=>m.trim())
|
|
138
137
|
return selector
|
|
139
138
|
}
|
|
139
|
+
class Query{
|
|
140
140
|
static get(query){
|
|
141
141
|
let q=new Query(query)
|
|
142
142
|
return q.selectors
|
|
143
143
|
}
|
|
144
144
|
constructor(query){
|
|
145
145
|
this.query=query
|
|
146
146
|
this.selectors=[]
|
|
147
147
|
this.stringValues=[];
|
|
148
148
|
this.parseSelectors(query.split(','))
|
|
149
149
|
}
|
|
150
150
|
parseSelectors(selectors){
|
|
151
151
|
selectors.forEach(selector=>{
|
|
152
152
|
let originalSelector=selector.trim()
|
|
153
153
|
selector=this.removeSpaces(selector)
|
|
154
154
|
this.stringValues=[]
|
|
155
155
|
selector=selector.replace(/\[.*?\]/g,(value)=>{
|
|
156
156
|
this.stringValues.push(value)
|
|
157
157
|
return `[${this.stringValues.length-1}]`
|
|
158
158
|
})
|
|
159
159
|
let [element,ancestors]=this.splitAndCutLast(selector.trim(),/\s+/)
|
|
160
160
|
element=this.getFamily(element)
|
|
161
161
|
if (ancestors.length>0)
|
|
162
162
|
element.ancestors=ancestors.map(ancestor=>this.getFamily(ancestor))
|
|
163
163
|
element.group=originalSelector
|
|
164
164
|
this.selectors.push(element)
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
167
|
splitAndCutLast(string,splitBy){
|
|
168
168
|
const array=string.split(splitBy);
|
|
169
169
|
const last=array.pop();
|
|
170
170
|
return [last,array];
|
|
171
171
|
}
|
|
172
172
|
getFamily(group,element,prev,prevAny,sign){
|
|
173
173
|
if (group.match(/\~|\+/)!==null){
|
|
174
174
|
let [last,prevBrothers]=this.splitAndCutLast(group,/\~|\+/)
|
|
175
175
|
let signs=group.replace(last,'')
|
|
176
176
|
prevBrothers.forEach(el=>signs=signs.replace(el,''))
|
|
177
177
|
signs=signs.match(/\~|\+/g)
|
|
178
178
|
if (signs.length==1){
|
|
179
179
|
sign=signs[0]
|
|
180
180
|
} else if (signs.length>1){
|
|
181
181
|
sign=signs.splice(signs.length-1,signs.length-1)[0]
|
|
182
182
|
prevBrothers[0]=prevBrothers.map((b,i)=>{
|
|
183
183
|
if (i< prevBrothers.length-1) b+=signs[i]
|
|
184
184
|
return b
|
|
185
185
|
}).join('')
|
|
186
186
|
prevBrothers[0]=this.getFamily(prevBrothers[0])
|
|
187
187
|
}
|
|
188
188
|
if (sign=='~') prevAny=prevBrothers[0]
|
|
189
189
|
else if (sign=='+') prev=prevBrothers[0]
|
|
190
190
|
element=last
|
|
191
191
|
} else element=group
|
|
192
192
|
let family
|
|
193
193
|
if (prev || prevAny){
|
|
194
194
|
family=this.getParents(element)
|
|
195
195
|
if (prev) family.prev=this.getParents(prev)
|
|
196
196
|
if (prevAny) family.prevAny=this.getParents(prevAny)
|
|
197
197
|
} else family=this.getParents(element)
|
|
198
198
|
if (family.query!==group) family.group=group
|
|
199
199
|
return family
|
|
200
200
|
}
|
|
201
201
|
getParents(selector){
|
|
202
202
|
if (typeof selector=='string'){
|
|
203
203
|
let [element,parents]=this.splitAndCutLast(selector,'>')
|
|
204
204
|
element=this.buildElement(element)
|
|
205
205
|
parents=parents.map(parent=>this.buildElement(parent))
|
|
206
206
|
if (parents.length>0) element.parents=parents
|
|
207
207
|
return element
|
|
208
208
|
} else return selector
|
|
209
209
|
}
|
|
210
210
|
buildElement(element,id=null,tag=null,classList=[]){
|
|
211
211
|
let query=element
|
|
212
212
|
element=element.replace(/\#(\w-?)*/,$id=>{
|
|
213
213
|
id=$id.replace(/^\#/,''); return ''
|
|
214
214
|
})
|
|
215
215
|
element=element.replace(/\.(\w-?)*/,$class=>{
|
|
216
216
|
classList.push($class.replace(/^\./,'')); return ''
|
|
217
217
|
})
|
|
218
218
|
element=element.replace(/(\w\:?-?)*/,$tag=>{
|
|
219
219
|
tag=$tag=='' ? null : $tag.toLowerCase(); return ''
|
|
220
220
|
})
|
|
221
221
|
let attribs=this.getAttributes(element)
|
|
222
222
|
element={ query }
|
|
223
223
|
if (id) element.id=id
|
|
224
224
|
if (tag) element.tag=tag
|
|
225
225
|
if (classList.length>0) element.classList=classList
|
|
226
226
|
if (attribs.length>0) element.attribs=attribs
|
|
227
227
|
return element
|
|
228
228
|
}
|
|
229
229
|
getAttributes(element){
|
|
230
230
|
let attribs=this.stringValues.filter((value,index)=>{
|
|
231
231
|
let searchValue=`[${index}]`
|
|
232
232
|
if (element.match(searchValue)) return true
|
|
233
233
|
else return false
|
|
234
234
|
})
|
|
235
235
|
attribs=attribs.map(attrib=>{
|
|
236
236
|
let query=attrib
|
|
237
237
|
attrib=attrib.replace('[','').replace(']','')
|
|
238
238
|
let [name,...values]=attrib.split('=')
|
|
239
239
|
const value=values.join('=').trim().replace(/^['"]/,'').replace(/['"]$/,'')
|
|
240
240
|
let sign
|
|
241
241
|
attrib={query,name}
|
|
242
242
|
if(value){
|
|
243
243
|
sign='='
|
|
244
244
|
attrib.name=attrib.name.replace(/[\~\|\^\$\*]$/,(match=>{
|
|
245
245
|
sign=match+sign
|
|
246
246
|
return ''
|
|
247
247
|
}))
|
|
248
248
|
attrib.value=value
|
|
249
249
|
}
|
|
250
250
|
if (sign){
|
|
251
251
|
attrib.sign=sign
|
|
252
252
|
attrib.check=this.getAttribFn(sign).bind(attrib)
|
|
253
253
|
}
|
|
254
254
|
return attrib
|
|
255
255
|
});
|
|
256
256
|
return attribs
|
|
257
257
|
}
|
|
258
258
|
getAttribFn(sign){
|
|
259
259
|
if (sign=='=') return function (value){ return value===this.value }
|
|
260
260
|
if (sign=='*=') return function (value){ return value.includes(this.value) }
|
|
261
261
|
if (sign=='^=') return function (value){ return value.startsWith(this.value) }
|
|
262
262
|
if (sign=='$=') return function (value){ return value.endsWith(this.value) }
|
|
263
263
|
if (sign=='|=') return function (value){
|
|
264
264
|
return value.trim().split(' ').length==1
|
|
265
265
|
&& (value.startsWith(this.value) || value.startsWith(this.value+'-'))
|
|
266
266
|
? true : false
|
|
267
267
|
}
|
|
268
268
|
if (sign=='~=') return function (value){
|
|
269
269
|
return this.value.trim().split(' ').length==1 && value.includes(this.value) ? true : false
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
removeSpaces(selector){
|
|
273
273
|
selector=selector.replace(/\s{2}/g,' ')
|
|
274
274
|
selector=selector.replace(/\s?\^?\$?\|?\~?\*?\=\s*/g,(m)=>m.trim())
|
|
275
275
|
selector=selector.replace(/\s?(\+|\~|\>)\s?/g,(m)=>m.trim())
|
|
276
276
|
return selector
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
function checkElement(el,selector){
|
|
280
280
|
if(selector==undefined) return true
|
|
281
281
|
if(el==null) return false
|
|
282
282
|
let{tag,classList,attribs:attributes,id,prev,ancestors,parents,prevAny}=selector
|
|
283
283
|
if(typeof el==='string') return false
|
|
284
284
|
if(id!==undefined && el.id===null) return false
|
|
285
285
|
if(id && id!==el.id) return false
|
|
286
286
|
if(tag && el._tagName===undefined) return false
|
|
287
287
|
else if(tag && tag!==el._tagName) return false
|
|
288
288
|
const clas=el.attributes.class
|
|
289
289
|
if(classList!==undefined && (clas===undefined || clas==='')) return false
|
|
290
290
|
else if(classList!==undefined){
|
|
291
291
|
if(classList.every(e=>el.classList.contains(e))===false) return false
|
|
292
292
|
}
|
|
293
293
|
if(checkattributes(attributes,el)===false) return false
|
|
294
294
|
if(checkElement(el.prev,prev)===false) return false
|
|
295
295
|
if(checkAncestors(el.ancestors,ancestors)===false) return false
|
|
296
296
|
if(checkParents(el.ancestors,parents)===false) return false
|
|
297
297
|
if(el.parent){
|
|
298
298
|
if(checkPrevAny(el.parent.children,el.childIndex,prevAny)==false) return false
|
|
299
299
|
}
|
|
300
300
|
return true
|
|
301
301
|
}
|
|
302
302
|
function checkattributes(attributes=[],el){
|
|
303
303
|
let elattributes=el.attributes
|
|
304
304
|
let names=Object.keys(elattributes)
|
|
305
305
|
let passedTests=0
|
|
306
306
|
if(attributes) for(let i=0; i<attributes.length; i++){
|
|
307
307
|
let{name,value,check}=attributes[i]
|
|
308
308
|
if(name=='inner' && value!==undefined && check && el.inner){
|
|
309
309
|
if(check(el.inner)) passedTests++
|
|
310
310
|
}
|
|
311
311
|
if(!names.includes(name)) continue
|
|
312
312
|
else if(value==undefined) passedTests++
|
|
313
313
|
else if(value && elattributes[name]){
|
|
314
314
|
if(check(elattributes[name])==false) continue
|
|
315
315
|
else passedTests++
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
318
|
if(passedTests==attributes.length) return true
|
|
319
319
|
else return false
|
|
@@ -15,28 +15,217 @@ function find(selectors,element,collection,first=false,firstTime=true){
|
|
|
15
15
|
if(!f
|
|
16
16
|
class TextNode{
|
|
17
17
|
constructor(data){
|
|
18
18
|
this.nodeName='#text';
|
|
19
19
|
this.parent=null;
|
|
20
20
|
this.textContent=data;
|
|
21
21
|
}
|
|
22
22
|
get nodeValue(){return this.textContent}
|
|
23
23
|
get parentNode(){return this.parent}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function buildStyle(attributes){
|
|
27
26
|
const styles=attributes.style || "";
|
|
28
27
|
const camelToKebab=str=>str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,'$1-$2').toLowerCase();
|
|
29
28
|
const kebabToCamel=str=>str.replace(/-([a-z])/g,g=>g[1].toUpperCase());
|
|
30
29
|
const baseStyleObj=styles.split(";").reduce((acc,style)=>{
|
|
31
30
|
const [key,value]=style.split(":").map(s=>s.trim());
|
|
32
31
|
if (key && value) acc[kebabToCamel(key)]=value;
|
|
33
32
|
return acc;
|
|
34
33
|
},{});
|
|
35
34
|
return new Proxy(baseStyleObj,{
|
|
36
35
|
get: (obj,prop)=>obj[camelToKebab(prop)] || obj[prop],set: (obj,prop,value)=>{
|
|
37
36
|
obj[camelToKebab(prop)]=value;
|
|
38
37
|
attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
|
|
39
38
|
return true;
|
|
40
39
|
},deleteProperty: (obj,prop)=>{
|
|
41
40
|
delete obj[camelToKebab(prop)];
|
|
42
41
|
attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
|
|
43
42
|
return true;
|
|
44
43
|
}
|
|
45
44
|
});
|
|
46
|
-
|
|
45
|
+
function buildStyle(attributes){
|
|
46
|
+
const styles=attributes.style || "";
|
|
47
|
+
const camelToKebab=str=>str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,'$1-$2').toLowerCase();
|
|
48
|
+
const kebabToCamel=str=>str.replace(/-([a-z])/g,g=>g[1].toUpperCase());
|
|
49
|
+
const splitDeclarations=str=>{
|
|
50
|
+
const out=[];
|
|
51
|
+
let buf="",depth=0,quote=null;
|
|
52
|
+
for (let i=0; i< str.length; i++){
|
|
53
|
+
const c=str[i];
|
|
54
|
+
if (quote){ if (c===quote) quote=null; buf+=c; }
|
|
55
|
+
else if (c==='"' || c==="'"){ quote=c; buf+=c; }
|
|
56
|
+
else if (c==='('){ depth++; buf+=c; }
|
|
57
|
+
else if (c===')'){ if (depth>0) depth--; buf+=c; }
|
|
58
|
+
else if (c===';' && depth===0){ out.push(buf); buf=""; }
|
|
59
|
+
else buf+=c;
|
|
60
|
+
}
|
|
61
|
+
if (buf.trim()) out.push(buf);
|
|
62
|
+
return out;
|
|
63
|
+
};
|
|
64
|
+
const serialize=obj=>Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
|
|
65
|
+
const baseStyleObj=splitDeclarations(styles).reduce((acc,decl)=>{
|
|
66
|
+
const idx=decl.indexOf(":");
|
|
67
|
+
if (idx===-1) return acc;
|
|
68
|
+
const key=decl.slice(0,idx).trim();
|
|
69
|
+
const value=decl.slice(idx+1).trim();
|
|
70
|
+
if (key && value) acc[kebabToCamel(key)]=value;
|
|
71
|
+
return acc;
|
|
72
|
+
},{});
|
|
73
|
+
return new Proxy(baseStyleObj,{
|
|
74
|
+
get: (obj,prop)=>typeof prop==='string' ? obj[kebabToCamel(prop)] : obj[prop],set: (obj,prop,value)=>{
|
|
75
|
+
obj[kebabToCamel(prop)]=value;
|
|
76
|
+
attributes.style=serialize(obj);
|
|
77
|
+
return true;
|
|
78
|
+
},deleteProperty: (obj,prop)=>{
|
|
79
|
+
delete obj[kebabToCamel(prop)];
|
|
80
|
+
attributes.style=serialize(obj);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
47
85
|
|
|
48
86
|
class NodeClassList{
|
|
49
87
|
constructor(node){ this.node=node }
|
|
50
88
|
get classes(){ return (this.node.attributes.class || "").split(" ").filter(Boolean) }
|
|
51
89
|
set classes(val){ this.node.attributes.class=val.join(" ") }
|
|
52
90
|
contains(className){ return this.classes.includes(className) }
|
|
53
91
|
add(className){
|
|
54
92
|
const currentClasses=this.classes;
|
|
55
93
|
if (!currentClasses.includes(className)) this.classes=[...currentClasses,className];
|
|
56
94
|
}
|
|
57
95
|
remove(className){ this.classes=this.classes.filter(cls=>cls!==className); }
|
|
58
96
|
toggle(className){
|
|
59
97
|
if (this.classes.includes(className)) this.remove(className);
|
|
60
98
|
else this.add(className);
|
|
61
99
|
}
|
|
62
100
|
replace(oldClass,newClass){
|
|
63
101
|
if (this.classes.includes(oldClass)){
|
|
64
102
|
this.remove(oldClass);
|
|
65
103
|
this.add(newClass);
|
|
66
104
|
}
|
|
67
105
|
}
|
|
68
106
|
}
|
|
69
107
|
function insertBefore(arr,index,newItem){
|
|
70
108
|
const existingIndex=arr.indexOf(newItem);
|
|
71
109
|
if (existingIndex!==-1) arr.splice(existingIndex,1);
|
|
72
110
|
arr.splice(index,0,newItem);
|
|
73
|
-
}
|
|
74
111
|
class Node{
|
|
75
112
|
constructor(tagName,attributes={},parent=null){
|
|
76
113
|
this.isSingle=false;
|
|
77
114
|
this._tagName=tagName.toLowerCase();
|
|
78
115
|
this.tagName=tagName.toUpperCase();
|
|
79
116
|
this.attributes=attributes;
|
|
80
117
|
this.childNodes=[];
|
|
81
118
|
if (parent!==null) parent.childNodes.push(this)
|
|
82
119
|
this.parent=parent;
|
|
83
120
|
this._classList=null;
|
|
84
121
|
this.__style=null;
|
|
85
122
|
this._dataset=null
|
|
86
123
|
}
|
|
87
124
|
get id(){ return this.attributes.id ? this.attributes.id : null; }
|
|
88
125
|
set id(newValue){ this.attributes.id=newValue; }
|
|
89
126
|
get className(){ return this.attributes.class || null }
|
|
90
127
|
get parentNode(){ return this.parent }
|
|
91
128
|
get ancestors(){
|
|
92
129
|
if (!this.parent) return []
|
|
93
130
|
const ancestors=[]
|
|
94
131
|
let element=this.parent
|
|
95
132
|
while (element.tagName!=='ROOT'){
|
|
96
133
|
ancestors.push(element)
|
|
97
134
|
element=element.parent
|
|
98
135
|
}
|
|
99
136
|
return ancestors.reverse()
|
|
100
137
|
}
|
|
101
138
|
get childNodeIndex(){
|
|
102
139
|
if (!this.parent) return null
|
|
103
140
|
return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
|
|
104
141
|
}
|
|
105
142
|
get childIndex(){
|
|
106
143
|
if (!this.parent) return null
|
|
107
144
|
return this.parent.children ? this.parent.children.indexOf(this) : null
|
|
108
145
|
}
|
|
109
146
|
get previousElementSibling(){ return this.prev }
|
|
110
147
|
get prev(){
|
|
111
148
|
if (this.childIndex===null) return null
|
|
112
149
|
return this.parent.children[this.childIndex-1]
|
|
113
150
|
}
|
|
114
151
|
get nextElementSibling(){ return this.next }
|
|
115
152
|
get next(){
|
|
116
153
|
if (this.childIndex===null) return null
|
|
117
154
|
return this.parent.children[this.childIndex+1] || null
|
|
118
155
|
}
|
|
119
156
|
get dataset(){
|
|
120
157
|
if (!this._dataset) this._dataset=getDataset(this);
|
|
121
158
|
return this._dataset;
|
|
122
159
|
}
|
|
123
160
|
get classList(){
|
|
124
161
|
if (!this._classList) this._classList=new NodeClassList(this);
|
|
125
162
|
return this._classList;
|
|
126
163
|
}
|
|
127
164
|
get style(){
|
|
128
165
|
if (!this.__style) this.__style=buildStyle(this.attributes)
|
|
129
166
|
return this.__style
|
|
130
167
|
}
|
|
131
168
|
get outerHTML(){
|
|
132
169
|
const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
|
|
133
170
|
return `<${this._tagName}${attrs ? ' '+attrs : ''}>${this.innerHTML}</${this._tagName}>`;
|
|
134
171
|
}
|
|
135
172
|
getAttribute(attrName){ return this.attributes[attrName]!==undefined ? this.attributes[attrName] : null }
|
|
136
173
|
setAttribute(attrName,value=''){ this.attributes[attrName]=value }
|
|
137
174
|
removeAttribute(attrName){ delete this.attributes[attrName] }
|
|
138
175
|
remove(){
|
|
139
176
|
if (!this.parent) return
|
|
140
177
|
const index=this.childNodeIndex;
|
|
141
178
|
if (index!==null) this.parent.childNodes.splice(index,1);
|
|
142
179
|
}
|
|
143
180
|
get innerHTML(){
|
|
144
181
|
return this.childNodes.map(child=>{
|
|
145
182
|
if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
|
|
146
183
|
else if (child instanceof TextNode) return child.textContent;
|
|
147
184
|
else return child
|
|
148
185
|
}).join("");
|
|
149
186
|
}
|
|
150
187
|
get innerText(){
|
|
151
188
|
return this.childNodes.map(child=>{
|
|
152
189
|
if (child instanceof Node || child instanceof SingleNode) return child.innerText;
|
|
153
190
|
else if (child instanceof TextNode) return child.textContent;
|
|
154
191
|
else return child
|
|
155
192
|
}).join("");
|
|
156
193
|
}
|
|
157
194
|
set innerText(value){
|
|
158
195
|
this.childNodes=[new TextNode(value)]
|
|
159
196
|
return value
|
|
160
197
|
}
|
|
161
198
|
$$(query){ return this.querySelectorAll(query) }
|
|
162
199
|
querySelectorAll(query){
|
|
163
200
|
const selectors=Query.get(query)
|
|
164
201
|
return find(selectors,this,new Set())
|
|
165
202
|
}
|
|
166
203
|
$(query){ return this.querySelector(query) }
|
|
167
204
|
querySelector(query){
|
|
168
205
|
const selectors=Query.get(query)
|
|
169
206
|
return find(selectors,this,new Set(),true)[0] || null
|
|
170
207
|
}
|
|
171
208
|
getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
|
|
172
209
|
getElementsByTagName(query){ return this.querySelectorAll(query) }
|
|
173
210
|
getElementById(query){ return this.querySelector('#'+query) }
|
|
174
211
|
get children(){
|
|
175
212
|
return this.childNodes.filter(child=>{
|
|
176
213
|
if (!(child instanceof Node)) return false
|
|
177
214
|
if (child._tagName==='#comment') return false
|
|
178
215
|
return true
|
|
179
216
|
});
|
|
180
217
|
}
|
|
181
218
|
insertAdjacentElement(position,newElement){
|
|
182
219
|
if (newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
|
|
183
220
|
const pos=position.toLowerCase();
|
|
184
221
|
if(newElement.parentNode){
|
|
185
222
|
newElement.parentNode.childNodes=newElement.parentNode.childNodes.filter(el=>el!==newElement)
|
|
186
223
|
}
|
|
187
224
|
if (pos==='afterbegin' || pos==='beforeend'){
|
|
188
225
|
if (pos==="afterbegin") this.childNodes.unshift(newElement);
|
|
189
226
|
else if (pos==="beforeend") this.childNodes.push(newElement);
|
|
190
227
|
newElement.parent=this
|
|
191
228
|
return newElement
|
|
192
229
|
}
|
|
193
230
|
if (!this.parent) throw new Error("Can't insert element to element without parent")
|
|
194
231
|
if (pos==="beforebegin") insertBefore(this.parent.childNodes,this.childNodeIndex,newElement)
|
|
195
232
|
else if (pos==="afterend") this.parent.childNodes.splice(this.childNodeIndex+1,0,newElement);
|
|
196
233
|
newElement.parent=this.parent
|
|
197
234
|
return newElement
|
|
198
235
|
}
|
|
199
236
|
insertAdjacentHTML(position,html){
|
|
200
237
|
const newNode=parseHTML(html);
|
|
201
238
|
newNode.childNodes.forEach(node=>{
|
|
202
239
|
this.insertAdjacentElement(position,node);
|
|
203
240
|
});
|
|
204
241
|
return newNode
|
|
205
242
|
}
|
|
206
243
|
insertAdjacentText(position,text){
|
|
207
244
|
return this.insertAdjacentElement(position,new TextNode(text));
|
|
208
245
|
}
|
|
209
246
|
insert(position,element){
|
|
210
247
|
const positions=['beforebegin','afterbegin','beforeend','afterend']
|
|
211
248
|
if (positions[position]) position=positions[position]
|
|
212
249
|
if (typeof element==='string'){
|
|
213
250
|
element=element.trim()
|
|
214
251
|
if (element.startsWith('<') && element.endsWith('>')){
|
|
215
252
|
return this.insertAdjacentHTML(position,element)
|
|
216
253
|
}
|
|
217
254
|
return this.insertAdjacentText(position,element)
|
|
218
255
|
}
|
|
219
256
|
return this.insertAdjacentElement(position,element)
|
|
220
257
|
}
|
|
221
258
|
set innerHTML(html){
|
|
222
259
|
this.childNodes=html.trim().startsWith('<') ? parseHTML(html).childNodes : [html]
|
|
223
260
|
this.children.forEach(child=>child.parent=this);
|
|
224
261
|
}
|
|
225
262
|
set outerHTML(html){
|
|
226
263
|
const parsed=parseHTML(html);
|
|
227
264
|
if (!this.parent) throw new Error('element has no parent node')
|
|
228
265
|
const index=this.childIndex
|
|
229
266
|
if (index!==null) this.parent.childNodes.splice(index,1,...parsed.childNodes);
|
|
230
267
|
}
|
|
231
268
|
appendChild(newChild){
|
|
232
269
|
if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
|
|
233
270
|
if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
|
|
234
271
|
} else if (typeof newChild==='string') newChild=new TextNode(newChild)
|
|
235
272
|
else return newChild
|
|
236
273
|
this.childNodes.push(newChild);
|
|
237
274
|
newChild.parent=this;
|
|
238
275
|
return newChild;
|
|
239
276
|
}
|
|
240
277
|
get textContent(){
|
|
241
278
|
if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
|
|
242
279
|
return this.childNodes.map(child=>{
|
|
243
280
|
if (child instanceof SingleNode) return ''
|
|
244
281
|
if (child instanceof TextNode) return child.nodeValue
|
|
245
282
|
if (child instanceof Node) return child.textContent;
|
|
246
283
|
else return child;
|
|
247
284
|
}).join('');
|
|
248
285
|
}
|
|
249
286
|
set textContent(value){
|
|
250
287
|
this.childNodes=[];
|
|
251
288
|
if (value!==null && value!==undefined){
|
|
252
289
|
this.childNodes.push(value.toString());
|
|
253
290
|
}
|
|
254
291
|
}
|
|
292
|
+
}
|
|
255
293
|
class Node{
|
|
256
294
|
constructor(tagName,attributes={},parent=null){
|
|
257
295
|
this.isSingle=false;
|
|
258
296
|
this._tagName=tagName.toLowerCase();
|
|
259
297
|
this.tagName=tagName.toUpperCase();
|
|
260
298
|
this.attributes=attributes;
|
|
261
299
|
this.childNodes=[];
|
|
262
300
|
if (parent!==null) parent.childNodes.push(this)
|
|
263
301
|
this.parent=parent;
|
|
264
302
|
this._classList=null;
|
|
265
303
|
this.__style=null;
|
|
266
304
|
this._dataset=null
|
|
267
305
|
}
|
|
268
306
|
get id(){ return this.attributes.id ? this.attributes.id : null; }
|
|
269
307
|
set id(newValue){ this.attributes.id=newValue; }
|
|
270
308
|
get className(){ return this.attributes.class || null }
|
|
271
309
|
get parentNode(){ return this.parent }
|
|
272
310
|
get ancestors(){
|
|
273
311
|
if (!this.parent) return []
|
|
274
312
|
const ancestors=[]
|
|
275
313
|
let element=this.parent
|
|
276
314
|
while (element.tagName!=='ROOT'){
|
|
277
315
|
ancestors.push(element)
|
|
278
316
|
element=element.parent
|
|
279
317
|
}
|
|
280
318
|
return ancestors.reverse()
|
|
281
319
|
}
|
|
282
320
|
get childNodeIndex(){
|
|
283
321
|
if (!this.parent) return null
|
|
284
322
|
return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
|
|
285
323
|
}
|
|
286
324
|
get childIndex(){
|
|
287
325
|
if (!this.parent) return null
|
|
288
326
|
return this.parent.children ? this.parent.children.indexOf(this) : null
|
|
289
327
|
}
|
|
290
328
|
get previousElementSibling(){ return this.prev }
|
|
291
329
|
get prev(){
|
|
292
330
|
if (this.childIndex===null) return null
|
|
293
331
|
return this.parent.children[this.childIndex-1]
|
|
294
332
|
}
|
|
295
333
|
get nextElementSibling(){ return this.next }
|
|
296
334
|
get next(){
|
|
297
335
|
if (this.childIndex===null) return null
|
|
298
336
|
return this.parent.children[this.childIndex+1] || null
|
|
299
337
|
}
|
|
300
338
|
get dataset(){
|
|
301
339
|
if (!this._dataset) this._dataset=getDataset(this);
|
|
302
340
|
return this._dataset;
|
|
303
341
|
}
|
|
304
342
|
get classList(){
|
|
305
343
|
if (!this._classList) this._classList=new NodeClassList(this);
|
|
306
344
|
return this._classList;
|
|
307
345
|
}
|
|
308
346
|
get style(){
|
|
309
347
|
if (!this.__style) this.__style=buildStyle(this.attributes)
|
|
310
348
|
return this.__style
|
|
311
349
|
}
|
|
312
350
|
get outerHTML(){
|
|
313
351
|
const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
|
|
314
352
|
return `<${this._tagName}${attrs ? ' '+attrs : ''}>${this.innerHTML}</${this._tagName}>`;
|
|
315
353
|
}
|
|
316
354
|
getAttribute(attrName){ return this.attributes[attrName]!==undefined ? this.attributes[attrName] : null }
|
|
317
355
|
setAttribute(attrName,value=''){ this.attributes[attrName]=value }
|
|
318
356
|
removeAttribute(attrName){ delete this.attributes[attrName] }
|
|
319
357
|
remove(){
|
|
320
358
|
if (!this.parent) return
|
|
321
359
|
const index=this.childNodeIndex;
|
|
322
360
|
if (index!==null) this.parent.childNodes.splice(index,1);
|
|
323
361
|
}
|
|
324
362
|
get innerHTML(){
|
|
325
363
|
return this.childNodes.map(child=>{
|
|
326
364
|
if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
|
|
327
365
|
else if (child instanceof TextNode) return child.textContent;
|
|
328
366
|
else return child
|
|
329
367
|
}).join("");
|
|
330
368
|
}
|
|
331
369
|
get innerText(){
|
|
332
370
|
return this.childNodes.map(child=>{
|
|
333
371
|
if (child instanceof Node || child instanceof SingleNode) return child.innerText;
|
|
334
372
|
else if (child instanceof TextNode) return child.textContent;
|
|
335
373
|
else return child
|
|
336
374
|
}).join("");
|
|
337
375
|
}
|
|
338
376
|
set innerText(value){
|
|
339
377
|
this.childNodes=[new TextNode(value)]
|
|
340
378
|
return value
|
|
341
379
|
}
|
|
342
380
|
$$(query){ return this.querySelectorAll(query) }
|
|
343
381
|
querySelectorAll(query){
|
|
344
382
|
const selectors=Query.get(query)
|
|
345
383
|
return find(selectors,this,new Set())
|
|
346
384
|
}
|
|
347
385
|
$(query){ return this.querySelector(query) }
|
|
348
386
|
querySelector(query){
|
|
349
387
|
const selectors=Query.get(query)
|
|
350
388
|
return find(selectors,this,new Set(),true)[0] || null
|
|
351
389
|
}
|
|
352
390
|
getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
|
|
353
391
|
getElementsByTagName(query){ return this.querySelectorAll(query) }
|
|
354
392
|
getElementById(query){ return this.querySelector('#'+query) }
|
|
355
393
|
get children(){
|
|
356
394
|
return this.childNodes.filter(child=>{
|
|
357
395
|
if (!(child instanceof Node)) return false
|
|
358
396
|
if (child._tagName==='#comment') return false
|
|
359
397
|
return true
|
|
360
398
|
});
|
|
361
399
|
}
|
|
362
400
|
insertAdjacentElement(position,newElement){
|
|
363
401
|
if (newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
|
|
364
402
|
const pos=position.toLowerCase();
|
|
365
403
|
if(newElement.parentNode){
|
|
366
404
|
newElement.parentNode.childNodes=newElement.parentNode.childNodes.filter(el=>el!==newElement)
|
|
367
405
|
}
|
|
368
406
|
if (pos==='afterbegin' || pos==='beforeend'){
|
|
369
407
|
if (pos==="afterbegin") this.childNodes.unshift(newElement);
|
|
370
408
|
else if (pos==="beforeend") this.childNodes.push(newElement);
|
|
371
409
|
newElement.parent=this
|
|
372
410
|
return newElement
|
|
373
411
|
}
|
|
374
412
|
if (!this.parent) throw new Error("Can't insert element to element without parent")
|
|
375
413
|
if (pos==="beforebegin") insertBefore(this.parent.childNodes,this.childNodeIndex,newElement)
|
|
376
414
|
else if (pos==="afterend") this.parent.childNodes.splice(this.childNodeIndex+1,0,newElement);
|
|
377
415
|
newElement.parent=this.parent
|
|
378
416
|
return newElement
|
|
379
417
|
}
|
|
380
418
|
insertAdjacentHTML(position,html){
|
|
381
419
|
const newNode=parseHTML(html);
|
|
382
420
|
const nodes=[...newNode.childNodes];
|
|
383
421
|
const pos=position.toLowerCase();
|
|
384
422
|
if (pos==='afterbegin' || pos==='afterend') nodes.reverse();
|
|
385
423
|
nodes.forEach(node=>{
|
|
386
424
|
this.insertAdjacentElement(position,node);
|
|
387
425
|
});
|
|
388
426
|
return newNode
|
|
389
427
|
}
|
|
390
428
|
insertAdjacentText(position,text){
|
|
391
429
|
return this.insertAdjacentElement(position,new TextNode(text));
|
|
392
430
|
}
|
|
393
431
|
insert(position,element){
|
|
394
432
|
const positions=['beforebegin','afterbegin','beforeend','afterend']
|
|
395
433
|
if (positions[position]) position=positions[position]
|
|
396
434
|
if (typeof element==='string'){
|
|
397
435
|
element=element.trim()
|
|
398
436
|
if (element.startsWith('<') && element.endsWith('>')){
|
|
399
437
|
return this.insertAdjacentHTML(position,element)
|
|
400
438
|
}
|
|
401
439
|
return this.insertAdjacentText(position,element)
|
|
402
440
|
}
|
|
403
441
|
return this.insertAdjacentElement(position,element)
|
|
404
442
|
}
|
|
405
443
|
set innerHTML(html){
|
|
406
444
|
this.childNodes=html.trim().startsWith('<') ? parseHTML(html).childNodes : [html]
|
|
407
445
|
this.children.forEach(child=>child.parent=this);
|
|
408
446
|
}
|
|
409
447
|
set outerHTML(html){
|
|
410
448
|
const parsed=parseHTML(html);
|
|
411
449
|
if (!this.parent) throw new Error('element has no parent node')
|
|
412
450
|
const index=this.childNodeIndex
|
|
413
451
|
if (index!==null){
|
|
414
452
|
const nodes=parsed.childNodes
|
|
415
453
|
nodes.forEach(node=>node.parent=this.parent)
|
|
416
454
|
this.parent.childNodes.splice(index,1,...nodes)
|
|
417
455
|
}
|
|
418
456
|
}
|
|
419
457
|
appendChild(newChild){
|
|
420
458
|
if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
|
|
421
459
|
if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
|
|
422
460
|
} else if (typeof newChild==='string') newChild=new TextNode(newChild)
|
|
423
461
|
else return newChild
|
|
424
462
|
this.childNodes.push(newChild);
|
|
425
463
|
newChild.parent=this;
|
|
426
464
|
return newChild;
|
|
427
465
|
}
|
|
428
466
|
get textContent(){
|
|
429
467
|
if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
|
|
430
468
|
return this.childNodes.map(child=>{
|
|
431
469
|
if (child instanceof SingleNode) return ''
|
|
432
470
|
if (child instanceof TextNode) return child.nodeValue
|
|
433
471
|
if (child instanceof Node) return child.textContent;
|
|
434
472
|
else return child;
|
|
435
473
|
}).join('');
|
|
436
474
|
}
|
|
437
475
|
set textContent(value){
|
|
438
476
|
this.childNodes=[];
|
|
439
477
|
if (value!==null && value!==undefined){
|
|
440
478
|
this.childNodes.push(value.toString());
|
|
441
479
|
}
|
|
442
480
|
}
|
|
443
481
|
}
|
|
444
482
|
class SingleNode extends Node{
|
|
445
483
|
constructor(tagName,attributes={},parent=null){
|
|
446
484
|
if(attributes['?'] && tagName==='?xml') delete attributes['?']
|
|
447
485
|
super(tagName,attributes,parent);
|
|
448
486
|
this.isSingle=true
|
|
449
487
|
}
|
|
450
488
|
get outerHTML(){
|
|
451
489
|
if (this._tagName==="#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
|
|
452
490
|
const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
|
|
453
491
|
return `<${this._tagName} ${attrs}${this._tagName==='?xml' ? '?' : ''}>`;
|
|
454
492
|
}
|
|
455
493
|
get innerHTML(){ return ""; }
|
|
456
494
|
set innerHTML(_){ }
|
|
457
495
|
$(_){return null}
|
|
458
496
|
$$(_){return []}
|
|
459
497
|
querySelectorAll(_){ return []; }
|
|
460
498
|
querySelector(_){ return null; }
|
|
461
499
|
getElementsByClassName(_){ return []; }
|
|
462
500
|
getElementsByTagName(_){ return []; }
|
|
463
501
|
getElementById(_){ return null; }
|
|
464
502
|
get children(){ return []; }
|
|
465
503
|
insertAdjacentElement(position,newElement){
|
|
466
504
|
if(position==='afterbegin') position='beforebegin'
|
|
467
505
|
if(position==='beforeend') position='afterend'
|
|
468
506
|
return super.insertAdjacentElement(position,newElement)
|
|
469
507
|
}
|
|
470
508
|
insertAdjacentHTML(position,html){
|
|
471
509
|
if(position==='afterbegin') position='beforebegin'
|
|
472
510
|
if(position==='beforeend') position='afterend'
|
|
473
511
|
return super.insertAdjacentHTML(position,html)
|
|
474
512
|
}
|
|
475
513
|
insertAdjacentText(position,text){
|
|
476
514
|
if(position==='afterbegin') position='beforebegin'
|
|
477
515
|
if(position==='beforeend') position='afterend'
|
|
478
516
|
return super.insertAdjacentText(position,text)
|
|
479
517
|
}
|
|
480
518
|
insert(position,element){
|
|
481
519
|
if(position===1) position=0
|
|
482
520
|
if(position===2) position=3
|
|
483
521
|
return super.insert(position,element)
|
|
484
522
|
}
|
|
485
523
|
appendChild(_){ }
|
|
486
524
|
get textContent(){ return ""; }
|
|
487
525
|
set textContent(_){ }
|
|
488
526
|
}
|
|
489
527
|
|
|
490
528
|
class Root extends Node{
|
|
491
529
|
constructor(){
|
|
492
530
|
super('ROOT',{},null);
|
|
493
531
|
this.isSingle=false
|
|
494
532
|
}
|
|
495
533
|
}
|
|
496
|
-
class Document extends Node{
|
|
497
534
|
constructor(html,url){
|
|
498
535
|
super('ROOT',{},null);
|
|
499
536
|
this.isSingle=false
|
|
500
537
|
this.URL=url
|
|
501
538
|
if(html instanceof Document) this.childNodes=[...buildFromCache(cacheDoc(html)).childNodes]
|
|
502
539
|
else if(typeof html==='string') this.innerHTML=html
|
|
503
540
|
else this.html
|
|
504
541
|
}
|
|
505
542
|
get documentElement(){return this.html}
|
|
506
543
|
get html(){
|
|
507
544
|
const html=this.$('html')
|
|
508
545
|
if(html===null) this.innerHTML=/*html*/`<html lang="en"><head><meta charset="UTF-8"><title></title></head><body></body></html>`
|
|
509
546
|
return this.$('html')
|
|
510
547
|
}
|
|
511
548
|
get innerHTML(){
|
|
512
549
|
const inner=super.innerHTML
|
|
513
550
|
return '<!DOCTYPE html>'+inner
|
|
514
551
|
}
|
|
515
552
|
set innerHTML(html){return super.innerHTML=html}
|
|
516
553
|
get head(){
|
|
517
554
|
const head=this.html.$('head')
|
|
518
555
|
if(head===null) this.html.insert(1,'<head></head>')
|
|
519
556
|
return this.$('head')
|
|
520
557
|
}
|
|
521
558
|
get body(){
|
|
522
559
|
const body=this.html.$('body')
|
|
523
560
|
if(body===null) this.head.insert(3,'<body></body>')
|
|
524
561
|
return this.$('body')
|
|
525
562
|
}
|
|
526
563
|
get title(){
|
|
527
564
|
const title=this.head.$('title')
|
|
528
565
|
if(title===null) this.head.insert(1,'<title></title>')
|
|
529
566
|
return this.$('title').innerText
|
|
530
567
|
}
|
|
531
568
|
get charset(){return this.$('meta[charset]')}
|
|
532
569
|
set title(title){return this.head.$('title').innerText=title}
|
|
533
570
|
get clone(){return new Document(this)}
|
|
571
|
+
class Document extends Node{
|
|
534
572
|
constructor(html,url){
|
|
535
573
|
super('ROOT',{},null);
|
|
536
574
|
this.isSingle=false
|
|
537
575
|
this.URL=url
|
|
538
576
|
if(html instanceof Document){
|
|
539
577
|
this.childNodes=[...buildFromCache(cacheDoc(html)).childNodes]
|
|
540
578
|
this.childNodes.forEach(child=>{ if(child instanceof Node || child instanceof TextNode) child.parent=this })
|
|
541
579
|
}
|
|
542
580
|
else if(typeof html==='string') this.innerHTML=html
|
|
543
581
|
else this.html
|
|
544
582
|
}
|
|
545
583
|
get documentElement(){return this.html}
|
|
546
584
|
get html(){
|
|
547
585
|
let html=this.$('html')
|
|
548
586
|
if(html===null){
|
|
549
587
|
const existing=this.childNodes
|
|
550
588
|
this.innerHTML=/*html*/`<html lang="en"><head><meta charset="UTF-8"><title></title></head><body></body></html>`
|
|
551
589
|
html=this.$('html')
|
|
552
590
|
if(existing.length){
|
|
553
591
|
const body=this.$('body')
|
|
554
592
|
existing.forEach(child=>{
|
|
555
593
|
body.childNodes.push(child)
|
|
556
594
|
if(child instanceof Node || child instanceof TextNode) child.parent=body
|
|
557
595
|
})
|
|
558
596
|
}
|
|
559
597
|
}
|
|
560
598
|
return html
|
|
561
599
|
}
|
|
562
600
|
get innerHTML(){
|
|
563
601
|
const inner=super.innerHTML
|
|
564
602
|
return '<!DOCTYPE html>'+inner
|
|
565
603
|
}
|
|
566
604
|
set innerHTML(html){return super.innerHTML=html}
|
|
567
605
|
get head(){
|
|
568
606
|
const head=this.html.$('head')
|
|
569
607
|
if(head===null) this.html.insert(1,'<head></head>')
|
|
570
608
|
return this.$('head')
|
|
571
609
|
}
|
|
572
610
|
get body(){
|
|
573
611
|
const body=this.html.$('body')
|
|
574
612
|
if(body===null) this.head.insert(3,'<body></body>')
|
|
575
613
|
return this.$('body')
|
|
576
614
|
}
|
|
577
615
|
get title(){
|
|
578
616
|
const title=this.head.$('title')
|
|
579
617
|
if(title===null) this.head.insert(1,'<title></title>')
|
|
580
618
|
return this.$('title').innerText
|
|
581
619
|
}
|
|
582
620
|
get charset(){return this.$('meta[charset]')}
|
|
583
621
|
set title(title){
|
|
584
622
|
if(this.head.$('title')===null) this.head.insert(1,'<title></title>')
|
|
585
623
|
this.head.$('title').innerText=title
|
|
586
624
|
}
|
|
587
625
|
get clone(){return new Document(this,this.URL)}
|
|
588
626
|
}
|
|
589
|
-
function parseAttributes(str){
|
|
590
627
|
const attrs={};
|
|
591
628
|
let key="";
|
|
592
629
|
let value="";
|
|
593
630
|
let isKey=true;
|
|
594
631
|
let quoteChar=null;
|
|
595
632
|
for (let i=0; i< str.length; i++){
|
|
596
633
|
const char=str[i];
|
|
597
634
|
if (isKey && (char==='=' || char===' ')){
|
|
598
635
|
if (char==='=') isKey=false;
|
|
599
636
|
else if (key.trim()){
|
|
600
637
|
attrs[key.trim()]=true;
|
|
601
638
|
key="";
|
|
602
639
|
}
|
|
603
640
|
continue;
|
|
604
641
|
}
|
|
605
642
|
if (!quoteChar && (char==='"' || char==="'")){
|
|
606
643
|
quoteChar=char;
|
|
607
644
|
continue;
|
|
608
645
|
} else if (quoteChar && char===quoteChar){
|
|
609
646
|
quoteChar=null;
|
|
610
647
|
attrs[key.trim()]=value.trim();
|
|
611
648
|
key=""; value=""; isKey=true;
|
|
612
649
|
continue;
|
|
613
650
|
}
|
|
614
651
|
if (isKey) key+=char;
|
|
615
652
|
else value+=char;
|
|
616
653
|
}
|
|
617
654
|
if (key.trim() &&!value) attrs[key.trim()]='';
|
|
618
655
|
return attrs;
|
|
656
|
+
function parseAttributes(str){
|
|
657
|
+
const attrs={};
|
|
658
|
+
const len=str.length;
|
|
659
|
+
const isWS=c=>c===' ' || c==='\t' || c==='\n' || c==='\r' || c==='\f';
|
|
660
|
+
let i=0;
|
|
661
|
+
while (i< len){
|
|
662
|
+
while (i< len && isWS(str[i])) i++;
|
|
663
|
+
if (i>=len) break;
|
|
664
|
+
let name="";
|
|
665
|
+
while (i< len &&!isWS(str[i]) && str[i]!=='='){ name+=str[i]; i++; }
|
|
666
|
+
if (!name){ i++; continue; }
|
|
667
|
+
while (i< len && isWS(str[i])) i++;
|
|
668
|
+
if (str[i]==='='){
|
|
669
|
+
i++;
|
|
670
|
+
while (i< len && isWS(str[i])) i++;
|
|
671
|
+
let value="";
|
|
672
|
+
const quote=str[i];
|
|
673
|
+
if (quote==='"' || quote==="'"){
|
|
674
|
+
i++;
|
|
675
|
+
while (i< len && str[i]!==quote){ value+=str[i]; i++; }
|
|
676
|
+
i++;
|
|
677
|
+
} else{
|
|
678
|
+
while (i< len &&!isWS(str[i])){ value+=str[i]; i++; }
|
|
679
|
+
}
|
|
680
|
+
attrs[name]=value.trim();
|
|
681
|
+
} else{
|
|
682
|
+
attrs[name]='';
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return attrs;
|
|
619
686
|
}
|
|
687
|
+
|
|
620
688
|
const VOID_TAGS=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","!doctype",'?xml']);
|
|
621
|
-
function parseHTML(html){
|
|
622
689
|
const root=new Root();
|
|
623
690
|
const stack=[root];
|
|
624
691
|
let currentText="",i=0;
|
|
625
692
|
let max=0
|
|
626
693
|
function parseScript(){
|
|
627
694
|
if (!html.startsWith("<script",i)) return false;
|
|
628
695
|
const openTagEnd=html.indexOf(">",i);
|
|
629
696
|
if (openTagEnd===-1) return false;
|
|
630
697
|
const attributesString=html.substring(i+7,openTagEnd).trim();
|
|
631
698
|
const attributes=parseAttributes(attributesString);
|
|
632
699
|
let closeTagStart=html.indexOf("</script>",openTagEnd);
|
|
633
700
|
if (closeTagStart===-1) return false;
|
|
634
701
|
const content=html.substring(openTagEnd+1,closeTagStart);
|
|
635
702
|
const scriptNode=new Node('script',attributes,stack[stack.length-1]);
|
|
636
703
|
if(content.length>0) scriptNode.childNodes.push(content);
|
|
637
704
|
i=closeTagStart+9;
|
|
638
705
|
return true;
|
|
639
706
|
}
|
|
640
707
|
function parseSpecial(startStr,endStr,n1,n2,tag){
|
|
641
708
|
if (!html.startsWith(startStr,i)) return false
|
|
642
709
|
if(currentText.length) stack[stack.length-1].childNodes.push(new TextNode(currentText));
|
|
643
710
|
const end=html.indexOf(endStr,i+n1);
|
|
644
711
|
const strNode=new Node(tag,{},stack[stack.length-1]);
|
|
645
712
|
strNode.childNodes.push(html.substring(i+n1,end));
|
|
646
713
|
i=end+n2;
|
|
647
714
|
return true
|
|
648
715
|
}
|
|
649
716
|
while (i< html.length){
|
|
650
717
|
if(i>=max) max=i;
|
|
651
718
|
else break;
|
|
652
719
|
if (parseScript()) continue
|
|
653
720
|
if (parseSpecial("<!--","-->",4,3,'#comment')) continue
|
|
654
721
|
if (parseSpecial("<style","</style>",7,8,'style')) continue
|
|
655
722
|
if (html.startsWith("<![CDATA[",i)){
|
|
656
723
|
const end=html.indexOf("]]>",i+9);
|
|
657
724
|
if (end===-1) break;
|
|
658
725
|
const content=html.substring(i+9,end);
|
|
659
726
|
const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
|
|
660
727
|
cdataNode.nodeValue=content;
|
|
661
728
|
i=end+3;
|
|
662
729
|
continue;
|
|
663
730
|
}
|
|
664
731
|
if (html.startsWith("<",i)){
|
|
665
732
|
if (currentText && stack[stack.length-1]){
|
|
666
733
|
const textNode=new TextNode(currentText)
|
|
667
734
|
stack[stack.length-1].childNodes.push(textNode);
|
|
668
735
|
textNode.parent=stack[stack.length-1]
|
|
669
736
|
currentText="";
|
|
670
737
|
}
|
|
671
738
|
let tagEnd=i+1;
|
|
672
739
|
let insideQuotes=false;
|
|
673
740
|
let quoteChar=null;
|
|
674
741
|
while (tagEnd< html.length){
|
|
675
742
|
const char=html[tagEnd];
|
|
676
743
|
if (!insideQuotes && (char==='"' || char==="'")){
|
|
677
744
|
insideQuotes=true;
|
|
678
745
|
quoteChar=char;
|
|
679
746
|
} else if (insideQuotes && char===quoteChar){
|
|
680
747
|
insideQuotes=false;
|
|
681
748
|
quoteChar=null;
|
|
682
749
|
}
|
|
683
750
|
if (!insideQuotes && char==='>') break;
|
|
684
751
|
tagEnd++;
|
|
685
752
|
}
|
|
686
753
|
const tagContent=html.substring(i+1,tagEnd);
|
|
687
754
|
if (tagContent.startsWith("/")) stack.pop();
|
|
688
755
|
else{
|
|
689
756
|
let isSelfClosing=tagContent.endsWith('/');
|
|
690
757
|
const tagNameEnd=tagContent.search(/\s|>|\//);
|
|
691
758
|
const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
|
|
692
759
|
const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
|
|
693
760
|
const attributes=parseAttributes(attributesString);
|
|
694
761
|
if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
|
|
695
762
|
else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
|
|
696
763
|
}
|
|
697
764
|
i=tagEnd+1;
|
|
698
765
|
} else{
|
|
699
766
|
currentText+=html[i];
|
|
700
767
|
i++;
|
|
701
768
|
}
|
|
702
769
|
}
|
|
703
770
|
if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
|
|
704
771
|
return root;
|
|
772
|
+
function parseHTML(html){
|
|
773
|
+
const root=new Root();
|
|
774
|
+
const stack=[root];
|
|
775
|
+
let currentText="",i=0;
|
|
776
|
+
let max=0
|
|
777
|
+
const isWS=c=>c===' ' || c==='\t' || c==='\n' || c==='\r' || c==='\f';
|
|
778
|
+
function flushText(){
|
|
779
|
+
if (currentText){
|
|
780
|
+
const textNode=new TextNode(currentText)
|
|
781
|
+
stack[stack.length-1].childNodes.push(textNode)
|
|
782
|
+
textNode.parent=stack[stack.length-1]
|
|
783
|
+
currentText=""
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function startsWithCI(str,pos){
|
|
787
|
+
return html.substr(pos,str.length).toLowerCase()===str;
|
|
788
|
+
}
|
|
789
|
+
function indexOfCI(needle,from){
|
|
790
|
+
const nlen=needle.length
|
|
791
|
+
const limit=html.length-nlen
|
|
792
|
+
for (let j=from; j<=limit; j++){
|
|
793
|
+
let ok=true
|
|
794
|
+
for (let k=0; k< nlen; k++){
|
|
795
|
+
let c=html[j+k]
|
|
796
|
+
if (c>='A' && c<='Z') c=c.toLowerCase()
|
|
797
|
+
if (c!==needle[k]){ ok=false; break }
|
|
798
|
+
}
|
|
799
|
+
if (ok) return j
|
|
800
|
+
}
|
|
801
|
+
return-1
|
|
802
|
+
}
|
|
803
|
+
function scanTagEnd(from){
|
|
804
|
+
let j=from+1
|
|
805
|
+
let inQuotes=false,q=null
|
|
806
|
+
while (j< html.length){
|
|
807
|
+
const c=html[j]
|
|
808
|
+
if (!inQuotes && (c==='"' || c==="'")){ inQuotes=true; q=c }
|
|
809
|
+
else if (inQuotes && c===q){ inQuotes=false; q=null }
|
|
810
|
+
if (!inQuotes && c==='>') return j
|
|
811
|
+
j++
|
|
812
|
+
}
|
|
813
|
+
return-1
|
|
814
|
+
}
|
|
815
|
+
function parseRawText(tagName){
|
|
816
|
+
if (html[i]!=='<') return false
|
|
817
|
+
const open="<"+tagName
|
|
818
|
+
if (!startsWithCI(open,i)) return false
|
|
819
|
+
const after=html[i+open.length]
|
|
820
|
+
if (after!==undefined &&!isWS(after) && after!=='>' && after!=='/') return false
|
|
821
|
+
const openTagEnd=scanTagEnd(i)
|
|
822
|
+
if (openTagEnd===-1) return false
|
|
823
|
+
flushText()
|
|
824
|
+
let attributesString=html.substring(i+open.length,openTagEnd).trim()
|
|
825
|
+
if (attributesString.endsWith('/')) attributesString=attributesString.slice(0,-1).trim()
|
|
826
|
+
const attributes=parseAttributes(attributesString)
|
|
827
|
+
const closeTag="</"+tagName+">"
|
|
828
|
+
const closeStart=indexOfCI(closeTag,openTagEnd+1)
|
|
829
|
+
const missing=closeStart===-1
|
|
830
|
+
const content=html.substring(openTagEnd+1,missing ? html.length : closeStart)
|
|
831
|
+
const node=new Node(tagName,attributes,stack[stack.length-1]);
|
|
832
|
+
if (content.length>0) node.childNodes.push(content);
|
|
833
|
+
i=missing ? html.length : closeStart+closeTag.length
|
|
834
|
+
return true
|
|
835
|
+
}
|
|
836
|
+
function parseSpecial(startStr,endStr,n1,n2,tag){
|
|
837
|
+
if (!html.startsWith(startStr,i)) return false
|
|
838
|
+
flushText()
|
|
839
|
+
let end=html.indexOf(endStr,i+n1);
|
|
840
|
+
const missing=end===-1
|
|
841
|
+
if (missing) end=html.length
|
|
842
|
+
const strNode=new Node(tag,{},stack[stack.length-1]);
|
|
843
|
+
strNode.childNodes.push(html.substring(i+n1,end));
|
|
844
|
+
i=missing ? end : end+n2;
|
|
845
|
+
return true
|
|
846
|
+
}
|
|
847
|
+
while (i< html.length){
|
|
848
|
+
if (i>=max) max=i;
|
|
849
|
+
else break;
|
|
850
|
+
if (parseRawText('script')) continue
|
|
851
|
+
if (parseSpecial("<!--","-->",4,3,'#comment')) continue
|
|
852
|
+
if (parseRawText('style')) continue
|
|
853
|
+
if (html.startsWith("<![CDATA[",i)){
|
|
854
|
+
if (currentText.trim()) flushText()
|
|
855
|
+
let end=html.indexOf("]]>",i+9);
|
|
856
|
+
const missing=end===-1
|
|
857
|
+
if (missing) end=html.length
|
|
858
|
+
const content=html.substring(i+9,end);
|
|
859
|
+
const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
|
|
860
|
+
cdataNode.nodeValue=content;
|
|
861
|
+
i=missing ? end : end+3;
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
if (html.startsWith("<",i)){
|
|
865
|
+
flushText()
|
|
866
|
+
const tagEnd=scanTagEnd(i);
|
|
867
|
+
const realEnd=tagEnd===-1 ? html.length : tagEnd;
|
|
868
|
+
const tagContent=html.substring(i+1,realEnd);
|
|
869
|
+
if (tagContent.startsWith("/")){
|
|
870
|
+
const closeName=tagContent.slice(1).trim().toLowerCase();
|
|
871
|
+
let idx=-1;
|
|
872
|
+
for (let s=stack.length-1; s>=1; s--){
|
|
873
|
+
if (stack[s]._tagName===closeName){ idx=s; break }
|
|
874
|
+
}
|
|
875
|
+
if (idx!==-1) stack.length=idx;
|
|
876
|
+
} else{
|
|
877
|
+
let isSelfClosing=tagContent.endsWith('/');
|
|
878
|
+
const tagNameEnd=tagContent.search(/\s|>|\//);
|
|
879
|
+
const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : realEnd-i-1);
|
|
880
|
+
const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
|
|
881
|
+
const attributes=parseAttributes(attributesString);
|
|
882
|
+
if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
|
|
883
|
+
else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
|
|
884
|
+
}
|
|
885
|
+
i=realEnd+1;
|
|
886
|
+
} else{
|
|
887
|
+
currentText+=html[i];
|
|
888
|
+
i++;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
|
|
892
|
+
return root;
|
|
705
893
|
}
|
|
706
|
-
function buildFromCache(cached){
|
|
707
894
|
function buildNode(cache,parent=null){
|
|
708
895
|
if(typeof cache==='string') return parent.childNodes.push(cache)
|
|
709
896
|
const{isSingle,tagName,attributes,childNodes,textContent}=cache
|
|
710
897
|
if(textContent) return parent.childNodes.push(new TextNode(textContent))
|
|
711
898
|
if(isSingle && parent) return parent.childNodes.push(new SingleNode(tagName,attributes))
|
|
712
899
|
const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
|
|
713
900
|
childNodes.forEach(childNode=>{
|
|
714
901
|
buildNode(childNode,newDoc)
|
|
715
902
|
});
|
|
716
903
|
return newDoc
|
|
717
904
|
}
|
|
718
905
|
return buildNode(cached)
|
|
906
|
+
|
|
907
|
+
function buildFromCache(cached){
|
|
719
908
|
function buildNode(cache,parent=null){
|
|
720
909
|
if(typeof cache==='string') return parent.childNodes.push(cache)
|
|
721
910
|
const{isSingle,tagName,attributes,childNodes,textContent}=cache
|
|
722
911
|
if(textContent!==undefined){
|
|
723
912
|
const textNode=new TextNode(textContent)
|
|
724
913
|
textNode.parent=parent
|
|
725
914
|
return parent.childNodes.push(textNode)
|
|
726
915
|
}
|
|
727
916
|
if(isSingle && parent) return new SingleNode(tagName,attributes,parent)
|
|
728
917
|
const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
|
|
729
918
|
childNodes.forEach(childNode=>{
|
|
730
919
|
buildNode(childNode,newDoc)
|
|
731
920
|
});
|
|
732
921
|
return newDoc
|
|
733
922
|
}
|
|
734
923
|
return buildNode(cached)
|
|
735
924
|
}
|
|
736
925
|
|
|
737
926
|
function cacheDoc(doc){
|
|
738
927
|
const props=['isSingle','tagName','attributes']
|
|
739
928
|
function addToCache(element,cache={}){
|
|
740
929
|
if(typeof element==='string') return element
|
|
741
930
|
if(element.nodeName==='#text') return{textContent:element.textContent}
|
|
742
931
|
props.forEach(prop=>{
|
|
743
932
|
if(element[prop]){
|
|
744
933
|
cache[prop]=typeof element[prop]==='object' ?{...element[prop]} : element[prop]
|
|
745
934
|
}
|
|
746
935
|
});
|
|
747
936
|
if(!element.childNodes) return cache
|
|
748
937
|
cache.childNodes=[]
|
|
749
938
|
element.childNodes.forEach(childNode=>{
|
|
750
939
|
cache.childNodes.push(addToCache(childNode))
|
|
751
940
|
});
|
|
752
941
|
return cache
|
|
753
942
|
}
|
|
754
943
|
return addToCache(doc)
|
|
755
944
|
}
|
|
756
|
-
module.exports = { parseHTML,
|
|
945
|
+
module.exports = { parseHTML,Node,Query,TextNode,SingleNode,buildFromCache,cacheDoc,Root,Document }
|