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