als-document 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/document.js +727 -42
- package/index.js +725 -40
- package/index.mjs +725 -40
- package/lib/parse/parser.js +1 -0
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1,42 +1,727 @@
|
|
|
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
|
}
|
|
140
|
-
}
|
|
141
|
-
function checkElement(el,selector){
|
|
142
139
|
if(selector==undefined) return true
|
|
143
140
|
if(el==null) return false
|
|
144
141
|
let{tag,classList,attribs:attributes,id,prev,ancestors,parents,prevAny}=selector
|
|
145
142
|
if(typeof el==='string') return false
|
|
146
143
|
if(id!==undefined && el.id===null) return false
|
|
147
144
|
if(id && id!==el.id) return false
|
|
148
145
|
if(tag && el._tagName===undefined) return false
|
|
149
146
|
else if(tag && tag!==el._tagName) return false
|
|
150
147
|
const clas=el.attributes.class
|
|
151
148
|
if(classList!==undefined && (clas===undefined || clas==='')) return false
|
|
152
149
|
else if(classList!==undefined){
|
|
153
150
|
if(classList.every(e=>el.classList.contains(e))===false) return false
|
|
154
151
|
}
|
|
155
152
|
if(checkattributes(attributes,el)===false) return false
|
|
156
153
|
if(checkElement(el.prev,prev)===false) return false
|
|
157
154
|
if(checkAncestors(el.ancestors,ancestors)===false) return false
|
|
158
155
|
if(checkParents(el.ancestors,parents)===false) return false
|
|
159
156
|
if(el.parent){
|
|
160
157
|
if(checkPrevAny(el.parent.children,el.childIndex,prevAny)==false) return false
|
|
161
158
|
}
|
|
162
159
|
return true
|
|
163
|
-
}
|
|
164
160
|
function checkattributes(attributes=[],el){
|
|
165
161
|
let elattributes=el.attributes
|
|
166
162
|
let names=Object.keys(elattributes)
|
|
167
163
|
let passedTests=0
|
|
168
164
|
if(attributes) for(let i=0; i<attributes.length; i++){
|
|
169
165
|
let{name,value,check}=attributes[i]
|
|
170
166
|
if(name=='inner' && value!==undefined && check && el.inner){
|
|
171
167
|
if(check(el.inner)) passedTests++
|
|
172
168
|
}
|
|
173
169
|
if(!names.includes(name)) continue
|
|
174
170
|
else if(value==undefined) passedTests++
|
|
175
171
|
else if(value && elattributes[name]){
|
|
176
172
|
if(check(elattributes[name])==false) continue
|
|
177
173
|
else passedTests++
|
|
178
174
|
}
|
|
179
175
|
}
|
|
180
176
|
if(passedTests==attributes.length) return true
|
|
181
177
|
else return false
|
|
182
|
-
}
|
|
183
178
|
function checkPrevAny(children=[],index,prevAny){
|
|
184
179
|
let size=children.length
|
|
185
180
|
if((size==0 || index==0) && prevAny) return false
|
|
186
181
|
for(let i=index; i>=0; i--){
|
|
187
182
|
if(checkElement(children[i],prevAny)) return true
|
|
188
183
|
}
|
|
189
184
|
return false
|
|
190
|
-
}
|
|
191
185
|
function checkAncestors(ancestors=[],selectorAncestors=[]){
|
|
192
186
|
let count=0
|
|
193
187
|
if(selectorAncestors.length==0) return true
|
|
194
188
|
let endIndex=ancestors.length-1
|
|
195
189
|
let selectorIndex=selectorAncestors.length-1
|
|
196
190
|
while(selectorIndex>=0){
|
|
197
191
|
for(let i=endIndex; i>=0; i--){
|
|
198
192
|
endIndex=i-1
|
|
199
193
|
if(checkElement(ancestors[i],selectorAncestors[selectorIndex])==true){
|
|
200
194
|
count++
|
|
201
195
|
break
|
|
202
196
|
}
|
|
203
197
|
}
|
|
204
198
|
selectorIndex--
|
|
205
199
|
}
|
|
206
200
|
if(count==selectorAncestors.length) return true
|
|
207
201
|
else return false
|
|
208
|
-
}
|
|
209
202
|
function checkParents(ancestors=[],selectorParents=[]){
|
|
210
203
|
if(selectorParents.length===0) return true
|
|
211
204
|
if(ancestors.length< selectorParents.length) return false
|
|
212
205
|
let index=ancestors.length-1
|
|
213
206
|
for(let i=selectorParents.length-1; i>=0; i--){
|
|
214
207
|
if(checkElement(ancestors[index],selectorParents[i])===false) return false
|
|
215
208
|
index--
|
|
216
209
|
}
|
|
217
210
|
return true
|
|
218
|
-
|
|
211
|
+
class Query{
|
|
212
|
+
static get(query){
|
|
213
|
+
let q=new Query(query)
|
|
214
|
+
return q.selectors
|
|
215
|
+
}
|
|
216
|
+
constructor(query){
|
|
217
|
+
this.query=query
|
|
218
|
+
this.selectors=[]
|
|
219
|
+
this.stringValues=[];
|
|
220
|
+
this.parseSelectors(query.split(','))
|
|
221
|
+
}
|
|
222
|
+
parseSelectors(selectors){
|
|
223
|
+
selectors.forEach(selector=>{
|
|
224
|
+
let originalSelector=selector.trim()
|
|
225
|
+
selector=this.removeSpaces(selector)
|
|
226
|
+
this.stringValues=[]
|
|
227
|
+
selector=selector.replace(/\[.*?\]/g,(value)=>{
|
|
228
|
+
this.stringValues.push(value)
|
|
229
|
+
return `[${this.stringValues.length-1}]`
|
|
230
|
+
})
|
|
231
|
+
let [element,ancestors]=this.splitAndCutLast(selector,' ')
|
|
232
|
+
element=this.getFamily(element)
|
|
233
|
+
if (ancestors.length>0)
|
|
234
|
+
element.ancestors=ancestors.map(ancestor=>this.getFamily(ancestor))
|
|
235
|
+
element.group=originalSelector
|
|
236
|
+
this.selectors.push(element)
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
splitAndCutLast(string,splitBy){
|
|
240
|
+
const array=string.split(splitBy);
|
|
241
|
+
const last=array.pop();
|
|
242
|
+
return [last,array];
|
|
243
|
+
}
|
|
244
|
+
getFamily(group,element,prev,prevAny,sign){
|
|
245
|
+
if (group.match(/\~|\+/)!==null){
|
|
246
|
+
let [last,prevBrothers]=this.splitAndCutLast(group,/\~|\+/)
|
|
247
|
+
let signs=group.replace(last,'')
|
|
248
|
+
prevBrothers.forEach(el=>signs=signs.replace(el,''))
|
|
249
|
+
signs=signs.match(/\~|\+/g)
|
|
250
|
+
if (signs.length==1){
|
|
251
|
+
sign=signs[0]
|
|
252
|
+
} else if (signs.length>1){
|
|
253
|
+
sign=signs.splice(signs.length-1,signs.length-1)[0]
|
|
254
|
+
prevBrothers[0]=prevBrothers.map((b,i)=>{
|
|
255
|
+
if (i< prevBrothers.length-1) b+=signs[i]
|
|
256
|
+
return b
|
|
257
|
+
}).join('')
|
|
258
|
+
prevBrothers[0]=this.getFamily(prevBrothers[0])
|
|
259
|
+
}
|
|
260
|
+
if (sign=='~') prevAny=prevBrothers[0]
|
|
261
|
+
else if (sign=='+') prev=prevBrothers[0]
|
|
262
|
+
element=last
|
|
263
|
+
} else element=group
|
|
264
|
+
let family
|
|
265
|
+
if (prev || prevAny){
|
|
266
|
+
family=this.getParents(element)
|
|
267
|
+
if (prev) family.prev=this.getParents(prev)
|
|
268
|
+
if (prevAny) family.prevAny=this.getParents(prevAny)
|
|
269
|
+
} else family=this.getParents(element)
|
|
270
|
+
if (family.query!==group) family.group=group
|
|
271
|
+
return family
|
|
272
|
+
}
|
|
273
|
+
getParents(selector){
|
|
274
|
+
if (typeof selector=='string'){
|
|
275
|
+
let [element,parents]=this.splitAndCutLast(selector,'>')
|
|
276
|
+
element=this.buildElement(element)
|
|
277
|
+
parents=parents.map(parent=>this.buildElement(parent))
|
|
278
|
+
if (parents.length>0) element.parents=parents
|
|
279
|
+
return element
|
|
280
|
+
} else return selector
|
|
281
|
+
}
|
|
282
|
+
buildElement(element,id=null,tag=null,classList=[]){
|
|
283
|
+
let query=element
|
|
284
|
+
element=element.replace(/\#(\w-?)*/,$id=>{
|
|
285
|
+
id=$id.replace(/^\#/,''); return ''
|
|
286
|
+
})
|
|
287
|
+
element=element.replace(/\.(\w-?)*/,$class=>{
|
|
288
|
+
classList.push($class.replace(/^\./,'')); return ''
|
|
289
|
+
})
|
|
290
|
+
element=element.replace(/(\w\:?-?)*/,$tag=>{
|
|
291
|
+
tag=$tag=='' ? null : $tag; return ''
|
|
292
|
+
})
|
|
293
|
+
let attribs=this.getAttributes(element)
|
|
294
|
+
element={ query }
|
|
295
|
+
if (id) element.id=id
|
|
296
|
+
if (tag) element.tag=tag
|
|
297
|
+
if (classList.length>0) element.classList=classList
|
|
298
|
+
if (attribs.length>0) element.attribs=attribs
|
|
299
|
+
return element
|
|
300
|
+
}
|
|
301
|
+
getAttributes(element){
|
|
302
|
+
let attribs=this.stringValues.filter((value,index)=>{
|
|
303
|
+
let searchValue=`[${index}]`
|
|
304
|
+
if (element.match(searchValue)) return true
|
|
305
|
+
else return false
|
|
306
|
+
})
|
|
307
|
+
attribs=attribs.map(attrib=>{
|
|
308
|
+
let query=attrib
|
|
309
|
+
attrib=attrib.replace('[','').replace(']','')
|
|
310
|
+
let [name,...values]=attrib.split('=')
|
|
311
|
+
const value=values.join('=').trim().replace(/^\"/,'').replace(/\"$/,'')
|
|
312
|
+
let sign
|
|
313
|
+
attrib={query,name}
|
|
314
|
+
if(value){
|
|
315
|
+
sign='='
|
|
316
|
+
attrib.name=attrib.name.replace(/[\~\|\^\$\*]$/,(match=>{
|
|
317
|
+
sign=match+sign
|
|
318
|
+
return ''
|
|
319
|
+
}))
|
|
320
|
+
attrib.value=value
|
|
321
|
+
}
|
|
322
|
+
if (sign){
|
|
323
|
+
attrib.sign=sign
|
|
324
|
+
attrib.check=this.getAttribFn(sign).bind(attrib)
|
|
325
|
+
}
|
|
326
|
+
return attrib
|
|
327
|
+
});
|
|
328
|
+
return attribs
|
|
329
|
+
}
|
|
330
|
+
getAttribFn(sign){
|
|
331
|
+
if (sign=='=') return function (value){ return value===this.value }
|
|
332
|
+
if (sign=='*=') return function (value){ return value.includes(this.value) }
|
|
333
|
+
if (sign=='^=') return function (value){ return value.startsWith(this.value) }
|
|
334
|
+
if (sign=='$=') return function (value){ return value.endsWith(this.value) }
|
|
335
|
+
if (sign=='|=') return function (value){
|
|
336
|
+
return value.trim().split(' ').length==1
|
|
337
|
+
&& (value.startsWith(this.value) || value.startsWith(this.value+'-'))
|
|
338
|
+
? true : false
|
|
339
|
+
}
|
|
340
|
+
if (sign=='~=') return function (value){
|
|
341
|
+
return this.value.trim().split(' ').length==1 && value.includes(this.value) ? true : false
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
removeSpaces(selector){
|
|
345
|
+
selector=selector.replace(/\s{2}/g,' ')
|
|
346
|
+
selector=selector.replace(/\s?\^?\$?\|?\~?\*?\=\s*/g,(m)=>m.trim())
|
|
347
|
+
selector=selector.replace(/\s?(\+|\~|\>)\s?/g,(m)=>m.trim())
|
|
348
|
+
return selector
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function checkElement(el,selector){
|
|
352
|
+
if(selector==undefined) return true
|
|
353
|
+
if(el==null) return false
|
|
354
|
+
let{tag,classList,attribs:attributes,id,prev,ancestors,parents,prevAny}=selector
|
|
355
|
+
if(typeof el==='string') return false
|
|
356
|
+
if(id!==undefined && el.id===null) return false
|
|
357
|
+
if(id && id!==el.id) return false
|
|
358
|
+
if(tag && el._tagName===undefined) return false
|
|
359
|
+
else if(tag && tag!==el._tagName) return false
|
|
360
|
+
const clas=el.attributes.class
|
|
361
|
+
if(classList!==undefined && (clas===undefined || clas==='')) return false
|
|
362
|
+
else if(classList!==undefined){
|
|
363
|
+
if(classList.every(e=>el.classList.contains(e))===false) return false
|
|
364
|
+
}
|
|
365
|
+
if(checkattributes(attributes,el)===false) return false
|
|
366
|
+
if(checkElement(el.prev,prev)===false) return false
|
|
367
|
+
if(checkAncestors(el.ancestors,ancestors)===false) return false
|
|
368
|
+
if(checkParents(el.ancestors,parents)===false) return false
|
|
369
|
+
if(el.parent){
|
|
370
|
+
if(checkPrevAny(el.parent.children,el.childIndex,prevAny)==false) return false
|
|
371
|
+
}
|
|
372
|
+
return true
|
|
373
|
+
}
|
|
374
|
+
function checkattributes(attributes=[],el){
|
|
375
|
+
let elattributes=el.attributes
|
|
376
|
+
let names=Object.keys(elattributes)
|
|
377
|
+
let passedTests=0
|
|
378
|
+
if(attributes) for(let i=0; i<attributes.length; i++){
|
|
379
|
+
let{name,value,check}=attributes[i]
|
|
380
|
+
if(name=='inner' && value!==undefined && check && el.inner){
|
|
381
|
+
if(check(el.inner)) passedTests++
|
|
382
|
+
}
|
|
383
|
+
if(!names.includes(name)) continue
|
|
384
|
+
else if(value==undefined) passedTests++
|
|
385
|
+
else if(value && elattributes[name]){
|
|
386
|
+
if(check(elattributes[name])==false) continue
|
|
387
|
+
else passedTests++
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if(passedTests==attributes.length) return true
|
|
391
|
+
else return false
|
|
392
|
+
}
|
|
393
|
+
function checkPrevAny(children=[],index,prevAny){
|
|
394
|
+
let size=children.length
|
|
395
|
+
if((size==0 || index==0) && prevAny) return false
|
|
396
|
+
for(let i=index; i>=0; i--){
|
|
397
|
+
if(checkElement(children[i],prevAny)) return true
|
|
398
|
+
}
|
|
399
|
+
return false
|
|
400
|
+
}
|
|
401
|
+
function checkAncestors(ancestors=[],selectorAncestors=[]){
|
|
402
|
+
let count=0
|
|
403
|
+
if(selectorAncestors.length==0) return true
|
|
404
|
+
let endIndex=ancestors.length-1
|
|
405
|
+
let selectorIndex=selectorAncestors.length-1
|
|
406
|
+
while(selectorIndex>=0){
|
|
407
|
+
for(let i=endIndex; i>=0; i--){
|
|
408
|
+
endIndex=i-1
|
|
409
|
+
if(checkElement(ancestors[i],selectorAncestors[selectorIndex])==true){
|
|
410
|
+
count++
|
|
411
|
+
break
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
selectorIndex--
|
|
415
|
+
}
|
|
416
|
+
if(count==selectorAncestors.length) return true
|
|
417
|
+
else return false
|
|
418
|
+
}
|
|
419
|
+
function checkParents(ancestors=[],selectorParents=[]){
|
|
420
|
+
if(selectorParents.length===0) return true
|
|
421
|
+
if(ancestors.length< selectorParents.length) return false
|
|
422
|
+
let index=ancestors.length-1
|
|
423
|
+
for(let i=selectorParents.length-1; i>=0; i--){
|
|
424
|
+
if(checkElement(ancestors[index],selectorParents[i])===false) return false
|
|
425
|
+
index--
|
|
426
|
+
}
|
|
427
|
+
return true
|
|
428
|
+
}
|
|
219
429
|
const getDataName=prop=>'data-'+prop.toLowerCase()
|
|
220
|
-
function getDataset(element){
|
|
221
430
|
return new Proxy(element.attributes,{
|
|
222
431
|
get: (target,prop)=>{return target[getDataName(prop)]},set: (target,prop,value)=>{target[getDataName(prop)]=value; return true},deleteProperty: (target,prop)=>{
|
|
223
432
|
const dataAttr=getDataName(prop)
|
|
224
433
|
if (dataAttr in target){
|
|
225
434
|
delete target[dataAttr];
|
|
226
435
|
return true;
|
|
227
436
|
}
|
|
228
437
|
return false;
|
|
229
438
|
}
|
|
230
439
|
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function find(selectors,element,collection,first=false,firstTime=true){
|
|
234
440
|
if(!firstTime)
|
|
235
441
|
for(let selector of selectors){
|
|
236
442
|
if(checkElement(element,selector)) collection.add(element)
|
|
237
443
|
}
|
|
238
444
|
if(element.children)
|
|
239
445
|
element.children.forEach(child=>{
|
|
240
446
|
if(first && collection.size>0) return
|
|
241
447
|
find(selectors,child,collection,first,false)
|
|
242
448
|
})
|
|
243
449
|
return firstTime ? [...collection] : collection
|
|
244
|
-
}
|
|
245
|
-
class TextNode{
|
|
246
450
|
constructor(data){
|
|
247
451
|
this.nodeName='#text';
|
|
248
452
|
this.parent=null;
|
|
249
453
|
this.textContent=data;
|
|
250
454
|
}
|
|
251
455
|
get nodeValue(){return this.textContent}
|
|
252
456
|
get parentNode(){return this.parent}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function buildStyle(attributes){
|
|
256
457
|
const styles=attributes.style || "";
|
|
257
458
|
const camelToKebab=str=>str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,'$1-$2').toLowerCase();
|
|
258
459
|
const kebabToCamel=str=>str.replace(/-([a-z])/g,g=>g[1].toUpperCase());
|
|
259
460
|
const baseStyleObj=styles.split(";").reduce((acc,style)=>{
|
|
260
461
|
const [key,value]=style.split(":").map(s=>s.trim());
|
|
261
462
|
if (key && value) acc[kebabToCamel(key)]=value;
|
|
262
463
|
return acc;
|
|
263
464
|
},{});
|
|
264
465
|
return new Proxy(baseStyleObj,{
|
|
265
466
|
get: (obj,prop)=>obj[camelToKebab(prop)] || obj[prop],set: (obj,prop,value)=>{
|
|
266
467
|
obj[camelToKebab(prop)]=value;
|
|
267
468
|
attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
|
|
268
469
|
return true;
|
|
269
470
|
},deleteProperty: (obj,prop)=>{
|
|
270
471
|
delete obj[camelToKebab(prop)];
|
|
271
472
|
attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
|
|
272
473
|
return true;
|
|
273
474
|
}
|
|
274
475
|
});
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
class NodeClassList{
|
|
278
476
|
constructor(node){ this.node=node }
|
|
279
477
|
get classes(){ return (this.node.attributes.class || "").split(" ").filter(Boolean) }
|
|
280
478
|
set classes(val){ this.node.attributes.class=val.join(" ") }
|
|
281
479
|
contains(className){ return this.classes.includes(className) }
|
|
282
480
|
add(className){
|
|
283
481
|
const currentClasses=this.classes;
|
|
284
482
|
if (!currentClasses.includes(className)) this.classes=[...currentClasses,className];
|
|
285
483
|
}
|
|
286
484
|
remove(className){ this.classes=this.classes.filter(cls=>cls!==className); }
|
|
287
485
|
toggle(className){
|
|
288
486
|
if (this.classes.includes(className)) this.remove(className);
|
|
289
487
|
else this.add(className);
|
|
290
488
|
}
|
|
291
489
|
replace(oldClass,newClass){
|
|
292
490
|
if (this.classes.includes(oldClass)){
|
|
293
491
|
this.remove(oldClass);
|
|
294
492
|
this.add(newClass);
|
|
295
493
|
}
|
|
296
494
|
}
|
|
297
|
-
}
|
|
298
|
-
function insertBefore(arr,index,newItem){
|
|
299
495
|
const existingIndex=arr.indexOf(newItem);
|
|
300
496
|
if (existingIndex!==-1) arr.splice(existingIndex,1);
|
|
301
497
|
arr.splice(index,0,newItem);
|
|
302
|
-
}
|
|
303
498
|
class Node{
|
|
304
499
|
constructor(tagName,attributes={},parent=null){
|
|
305
500
|
this.isSingle=false;
|
|
306
501
|
this._tagName=tagName.toLowerCase();
|
|
307
502
|
this.tagName=tagName.toUpperCase();
|
|
308
503
|
this.attributes=attributes;
|
|
309
504
|
this.childNodes=[];
|
|
310
505
|
if (parent!==null) parent.childNodes.push(this)
|
|
311
506
|
this.parent=parent;
|
|
312
507
|
this._classList=null;
|
|
313
508
|
this.__style=null;
|
|
314
509
|
this._dataset=null
|
|
315
510
|
}
|
|
316
511
|
get id(){ return this.attributes.id ? this.attributes.id : null; }
|
|
317
512
|
set id(newValue){ this.attributes.id=newValue; }
|
|
318
513
|
get className(){ return this.attributes.class || null }
|
|
319
514
|
get parentNode(){ return this.parent }
|
|
320
515
|
get ancestors(){
|
|
321
516
|
if (!this.parent) return []
|
|
322
517
|
const ancestors=[]
|
|
323
518
|
let element=this.parent
|
|
324
519
|
while (element.tagName!=='ROOT'){
|
|
325
520
|
ancestors.push(element)
|
|
326
521
|
element=element.parent
|
|
327
522
|
}
|
|
328
523
|
return ancestors.reverse()
|
|
329
524
|
}
|
|
330
525
|
get childNodeIndex(){
|
|
331
526
|
if (!this.parent) return null
|
|
332
527
|
return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
|
|
333
528
|
}
|
|
334
529
|
get childIndex(){
|
|
335
530
|
if (!this.parent) return null
|
|
336
531
|
return this.parent.children ? this.parent.children.indexOf(this) : null
|
|
337
532
|
}
|
|
338
533
|
get previousElementSibling(){ return this.prev }
|
|
339
534
|
get prev(){
|
|
340
535
|
if (this.childIndex===null) return null
|
|
341
536
|
return this.parent.children[this.childIndex-1]
|
|
342
537
|
}
|
|
343
538
|
get nextElementSibling(){ return this.next }
|
|
344
539
|
get next(){
|
|
345
540
|
if (this.childIndex===null) return null
|
|
346
541
|
return this.parent.children[this.childIndex+1] || null
|
|
347
542
|
}
|
|
348
543
|
get dataset(){
|
|
349
544
|
if (!this._dataset) this._dataset=getDataset(this);
|
|
350
545
|
return this._dataset;
|
|
351
546
|
}
|
|
352
547
|
get classList(){
|
|
353
548
|
if (!this._classList) this._classList=new NodeClassList(this);
|
|
354
549
|
return this._classList;
|
|
355
550
|
}
|
|
356
551
|
get style(){
|
|
357
552
|
if (!this.__style) this.__style=buildStyle(this.attributes)
|
|
358
553
|
return this.__style
|
|
359
554
|
}
|
|
360
555
|
get outerHTML(){
|
|
361
556
|
const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
|
|
362
557
|
return `<${this._tagName}${attrs ? ' '+attrs : ''}>${this.innerHTML}</${this._tagName}>`;
|
|
363
558
|
}
|
|
364
559
|
getAttribute(attrName){ return this.attributes[attrName]!==undefined ? this.attributes[attrName] : null }
|
|
365
560
|
setAttribute(attrName,value=''){ this.attributes[attrName]=value }
|
|
366
561
|
removeAttribute(attrName){ delete this.attributes[attrName] }
|
|
367
562
|
remove(){
|
|
368
563
|
if (!this.parent) return
|
|
369
564
|
const index=this.childNodeIndex;
|
|
370
565
|
if (index!==null) this.parent.childNodes.splice(index,1);
|
|
371
566
|
}
|
|
372
567
|
get innerHTML(){
|
|
373
568
|
return this.childNodes.map(child=>{
|
|
374
569
|
if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
|
|
375
570
|
else if (child instanceof TextNode) return child.textContent;
|
|
376
571
|
else return child
|
|
377
572
|
}).join("");
|
|
378
573
|
}
|
|
379
574
|
get innerText(){
|
|
380
575
|
return this.childNodes.map(child=>{
|
|
381
576
|
if (child instanceof Node || child instanceof SingleNode) return child.innerText;
|
|
382
577
|
else if (child instanceof TextNode) return child.textContent;
|
|
383
578
|
else return child
|
|
384
579
|
}).join("");
|
|
385
580
|
}
|
|
386
581
|
set innerText(value){
|
|
387
582
|
this.childNodes=[new TextNode(value)]
|
|
388
583
|
return value
|
|
389
584
|
}
|
|
390
585
|
$$(query){ return this.querySelectorAll(query) }
|
|
391
586
|
querySelectorAll(query){
|
|
392
587
|
const selectors=Query.get(query)
|
|
393
588
|
return find(selectors,this,new Set())
|
|
394
589
|
}
|
|
395
590
|
$(query){ return this.querySelector(query) }
|
|
396
591
|
querySelector(query){
|
|
397
592
|
const selectors=Query.get(query)
|
|
398
593
|
return find(selectors,this,new Set(),true)[0] || null
|
|
399
594
|
}
|
|
400
595
|
getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
|
|
401
596
|
getElementsByTagName(query){ return this.querySelectorAll(query) }
|
|
402
597
|
getElementById(query){ return this.querySelector('#'+query) }
|
|
403
598
|
get children(){
|
|
404
599
|
return this.childNodes.filter(child=>{
|
|
405
600
|
if (!(child instanceof Node)) return false
|
|
406
601
|
if (child._tagName==='#comment') return false
|
|
407
602
|
return true
|
|
408
603
|
});
|
|
409
604
|
}
|
|
410
605
|
insertAdjacentElement(position,newElement){
|
|
411
606
|
if (newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
|
|
412
607
|
const pos=position.toLowerCase();
|
|
413
608
|
if(newElement.parentNode){
|
|
414
609
|
newElement.parentNode.childNodes=newElement.parentNode.childNodes.filter(el=>el!==newElement)
|
|
415
610
|
}
|
|
416
611
|
if (pos==='afterbegin' || pos==='beforeend'){
|
|
417
612
|
if (pos==="afterbegin") this.childNodes.unshift(newElement);
|
|
418
613
|
else if (pos==="beforeend") this.childNodes.push(newElement);
|
|
419
614
|
newElement.parent=this
|
|
420
615
|
return newElement
|
|
421
616
|
}
|
|
422
617
|
if (!this.parent) throw new Error("Can't insert element to element without parent")
|
|
423
618
|
if (pos==="beforebegin") insertBefore(this.parent.childNodes,this.childNodeIndex,newElement)
|
|
424
619
|
else if (pos==="afterend") this.parent.childNodes.splice(this.childNodeIndex+1,0,newElement);
|
|
425
620
|
newElement.parent=this.parent
|
|
426
621
|
return newElement
|
|
427
622
|
}
|
|
428
623
|
insertAdjacentHTML(position,html){
|
|
429
624
|
const newNode=parseHTML(html);
|
|
430
625
|
newNode.childNodes.forEach(node=>{
|
|
431
626
|
this.insertAdjacentElement(position,node);
|
|
432
627
|
});
|
|
433
628
|
return newNode
|
|
434
629
|
}
|
|
435
630
|
insertAdjacentText(position,text){
|
|
436
631
|
return this.insertAdjacentElement(position,new TextNode(text));
|
|
437
632
|
}
|
|
438
633
|
insert(position,element){
|
|
439
634
|
const positions=['beforebegin','afterbegin','beforeend','afterend']
|
|
440
635
|
if (positions[position]) position=positions[position]
|
|
441
636
|
if (typeof element==='string'){
|
|
442
637
|
element=element.trim()
|
|
443
638
|
if (element.startsWith('<') && element.endsWith('>')){
|
|
444
639
|
return this.insertAdjacentHTML(position,element)
|
|
445
640
|
}
|
|
446
641
|
return this.insertAdjacentText(position,element)
|
|
447
642
|
}
|
|
448
643
|
return this.insertAdjacentElement(position,element)
|
|
449
644
|
}
|
|
450
645
|
set innerHTML(html){
|
|
451
646
|
this.childNodes=html.trim().startsWith('<') ? parseHTML(html).childNodes : [html]
|
|
452
647
|
this.children.forEach(child=>child.parent=this);
|
|
453
648
|
}
|
|
454
649
|
set outerHTML(html){
|
|
455
650
|
const parsed=parseHTML(html);
|
|
456
651
|
if (!this.parent) throw new Error('element has no parent node')
|
|
457
652
|
const index=this.childIndex
|
|
458
653
|
if (index!==null) this.parent.childNodes.splice(index,1,...parsed.childNodes);
|
|
459
654
|
}
|
|
460
655
|
appendChild(newChild){
|
|
461
656
|
if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
|
|
462
657
|
if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
|
|
463
658
|
} else if (typeof newChild==='string') newChild=new TextNode(newChild)
|
|
464
659
|
else return newChild
|
|
465
660
|
this.childNodes.push(newChild);
|
|
466
661
|
newChild.parent=this;
|
|
467
662
|
return newChild;
|
|
468
663
|
}
|
|
469
664
|
get textContent(){
|
|
470
665
|
if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
|
|
471
666
|
return this.childNodes.map(child=>{
|
|
472
667
|
if (child instanceof SingleNode) return ''
|
|
473
668
|
if (child instanceof TextNode) return child.nodeValue
|
|
474
669
|
if (child instanceof Node) return child.textContent;
|
|
475
670
|
else return child;
|
|
476
671
|
}).join('');
|
|
477
672
|
}
|
|
478
673
|
set textContent(value){
|
|
479
674
|
this.childNodes=[];
|
|
480
675
|
if (value!==null && value!==undefined){
|
|
481
676
|
this.childNodes.push(value.toString());
|
|
482
677
|
}
|
|
483
678
|
}
|
|
484
|
-
}
|
|
485
|
-
class SingleNode extends Node{
|
|
486
679
|
constructor(tagName,attributes={},parent=null){
|
|
487
680
|
if(attributes['?'] && tagName==='?xml') delete attributes['?']
|
|
488
681
|
super(tagName,attributes,parent);
|
|
489
682
|
this.isSingle=true
|
|
490
683
|
}
|
|
491
684
|
get outerHTML(){
|
|
492
685
|
if (this._tagName==="#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
|
|
493
686
|
const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
|
|
494
687
|
return `<${this._tagName} ${attrs}${this._tagName==='?xml' ? '?' : ''}>`;
|
|
495
688
|
}
|
|
496
689
|
get innerHTML(){ return ""; }
|
|
497
690
|
set innerHTML(_){ }
|
|
498
691
|
$(_){return null}
|
|
499
692
|
$$(_){return []}
|
|
500
693
|
querySelectorAll(_){ return []; }
|
|
501
694
|
querySelector(_){ return null; }
|
|
502
695
|
getElementsByClassName(_){ return []; }
|
|
503
696
|
getElementsByTagName(_){ return []; }
|
|
504
697
|
getElementById(_){ return null; }
|
|
505
698
|
get children(){ return []; }
|
|
506
699
|
insertAdjacentElement(position,newElement){
|
|
507
700
|
if(position==='afterbegin') position='beforebegin'
|
|
508
701
|
if(position==='beforeend') position='afterend'
|
|
509
702
|
super.insertAdjacentElement(position,newElement)
|
|
510
703
|
}
|
|
511
704
|
insertAdjacentHTML(position,html){
|
|
512
705
|
if(position==='afterbegin') position='beforebegin'
|
|
513
706
|
if(position==='beforeend') position='afterend'
|
|
514
707
|
super.insertAdjacentHTML(position,html)
|
|
515
708
|
}
|
|
516
709
|
insertAdjacentText(position,text){
|
|
517
710
|
if(position==='afterbegin') position='beforebegin'
|
|
518
711
|
if(position==='beforeend') position='afterend'
|
|
519
712
|
super.insertAdjacentText(position,text)
|
|
520
713
|
}
|
|
521
714
|
insert(position,element){
|
|
522
715
|
if(position===1) position=0
|
|
523
716
|
if(position===2) position=3
|
|
524
717
|
super.insert(position,element)
|
|
525
718
|
}
|
|
526
719
|
appendChild(_){ }
|
|
527
720
|
get textContent(){ return ""; }
|
|
528
721
|
set textContent(_){ }
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
class Root extends Node{
|
|
532
722
|
constructor(){
|
|
533
723
|
super('ROOT',{},null);
|
|
534
724
|
this.isSingle=false
|
|
535
725
|
}
|
|
536
|
-
}
|
|
537
|
-
class Document extends Node{
|
|
538
726
|
constructor(html,url){
|
|
539
727
|
super('ROOT',{},null);
|
|
540
728
|
this.isSingle=false
|
|
541
729
|
this.URL=url
|
|
542
730
|
if(html instanceof Document) this.childNodes=[...buildFromCache(cacheDoc(html)).childNodes]
|
|
543
731
|
else if(typeof html==='string') this.innerHTML=html
|
|
544
732
|
else this.html
|
|
545
733
|
}
|
|
546
734
|
get documentElement(){return this.html}
|
|
547
735
|
get html(){
|
|
548
736
|
const html=this.$('html')
|
|
549
737
|
if(html===null) this.innerHTML=/*html*/`<html lang="en"><head><meta charset="UTF-8"><title></title></head><body></body></html>`
|
|
550
738
|
return this.$('html')
|
|
551
739
|
}
|
|
552
740
|
get innerHTML(){
|
|
553
741
|
const inner=super.innerHTML
|
|
554
742
|
return '<!DOCTYPE html>'+inner
|
|
555
743
|
}
|
|
556
744
|
set innerHTML(html){return super.innerHTML=html}
|
|
557
745
|
get head(){
|
|
558
746
|
const head=this.html.$('head')
|
|
559
747
|
if(head===null) this.html.insert(1,'<head></head>')
|
|
560
748
|
return this.$('head')
|
|
561
749
|
}
|
|
562
750
|
get body(){
|
|
563
751
|
const body=this.html.$('body')
|
|
564
752
|
if(body===null) this.head.insert(3,'<body></body>')
|
|
565
753
|
return this.$('body')
|
|
566
754
|
}
|
|
567
755
|
get title(){
|
|
568
756
|
const title=this.head.$('title')
|
|
569
757
|
if(title===null) this.head.insert(1,'<title></title>')
|
|
570
758
|
return this.$('title').innerText
|
|
571
759
|
}
|
|
572
760
|
get charset(){return this.$('meta[charset]')}
|
|
573
761
|
set title(title){return this.head.$('title').innerText=title}
|
|
574
762
|
get clone(){return new Document(this)}
|
|
575
|
-
}
|
|
576
|
-
function parseAttributes(str){
|
|
577
763
|
const attrs={};
|
|
578
764
|
let key="";
|
|
579
765
|
let value="";
|
|
580
766
|
let isKey=true;
|
|
581
767
|
let quoteChar=null;
|
|
582
768
|
for (let i=0; i< str.length; i++){
|
|
583
769
|
const char=str[i];
|
|
584
770
|
if (isKey && (char==='=' || char===' ')){
|
|
585
771
|
if (char==='=') isKey=false;
|
|
586
772
|
else if (key.trim()){
|
|
587
773
|
attrs[key.trim()]=true;
|
|
588
774
|
key="";
|
|
589
775
|
}
|
|
590
776
|
continue;
|
|
591
777
|
}
|
|
592
778
|
if (!quoteChar && (char==='"' || char==="'")){
|
|
593
779
|
quoteChar=char;
|
|
594
780
|
continue;
|
|
595
781
|
} else if (quoteChar && char===quoteChar){
|
|
596
782
|
quoteChar=null;
|
|
597
783
|
attrs[key.trim()]=value.trim();
|
|
598
784
|
key=""; value=""; isKey=true;
|
|
599
785
|
continue;
|
|
600
786
|
}
|
|
601
787
|
if (isKey) key+=char;
|
|
602
788
|
else value+=char;
|
|
603
789
|
}
|
|
604
790
|
if (key.trim() &&!value) attrs[key.trim()]='';
|
|
605
791
|
return attrs;
|
|
606
|
-
}
|
|
607
|
-
const VOID_TAGS=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","!doctype",'?xml']);
|
|
608
|
-
function parseHTML(html){
|
|
609
792
|
const root=new Root();
|
|
610
793
|
const stack=[root];
|
|
611
794
|
let currentText="",i=0;
|
|
612
795
|
let max=0
|
|
613
796
|
function parseScript(){
|
|
614
797
|
if (!html.startsWith("<script",i)) return false;
|
|
615
798
|
const openTagEnd=html.indexOf(">",i);
|
|
616
799
|
if (openTagEnd===-1) return false;
|
|
617
800
|
const attributesString=html.substring(i+7,openTagEnd).trim();
|
|
618
801
|
const attributes=parseAttributes(attributesString);
|
|
619
802
|
let closeTagStart=html.indexOf("</script>",openTagEnd);
|
|
620
803
|
if (closeTagStart===-1) return false;
|
|
621
804
|
const content=html.substring(openTagEnd+1,closeTagStart);
|
|
622
805
|
const scriptNode=new Node('script',attributes,stack[stack.length-1]);
|
|
623
806
|
if(content.length>0) scriptNode.childNodes.push(content);
|
|
624
807
|
i=closeTagStart+9;
|
|
625
808
|
return true;
|
|
626
809
|
}
|
|
627
810
|
function parseSpecial(startStr,endStr,n1,n2,tag){
|
|
628
811
|
if (!html.startsWith(startStr,i)) return false
|
|
629
812
|
const end=html.indexOf(endStr,i+n1);
|
|
630
813
|
const strNode=new Node(tag,{},stack[stack.length-1]);
|
|
631
814
|
strNode.childNodes.push(html.substring(i+n1,end));
|
|
632
815
|
i=end+n2;
|
|
633
816
|
return true
|
|
634
817
|
}
|
|
635
818
|
while (i< html.length){
|
|
636
819
|
if(i>=max) max=i;
|
|
637
820
|
else break;
|
|
638
821
|
if (parseScript()) continue
|
|
639
822
|
if (parseSpecial("<!--","-->",4,3,'#comment')) continue
|
|
640
823
|
if (parseSpecial("<style","</style>",7,8,'style')) continue
|
|
641
824
|
if (html.startsWith("<![CDATA[",i)){
|
|
642
825
|
const end=html.indexOf("]]>",i+9);
|
|
643
826
|
if (end===-1) break;
|
|
644
827
|
const content=html.substring(i+9,end);
|
|
645
828
|
const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
|
|
646
829
|
cdataNode.nodeValue=content;
|
|
647
830
|
i=end+3;
|
|
648
831
|
continue;
|
|
649
832
|
}
|
|
650
833
|
if (html.startsWith("<",i)){
|
|
651
834
|
if (currentText && stack[stack.length-1]){
|
|
652
835
|
const textNode=new TextNode(currentText)
|
|
653
836
|
stack[stack.length-1].childNodes.push(textNode);
|
|
654
837
|
textNode.parent=stack[stack.length-1]
|
|
655
838
|
currentText="";
|
|
656
839
|
}
|
|
657
840
|
let tagEnd=i+1;
|
|
658
841
|
let insideQuotes=false;
|
|
659
842
|
let quoteChar=null;
|
|
660
843
|
while (tagEnd< html.length){
|
|
661
844
|
const char=html[tagEnd];
|
|
662
845
|
if (!insideQuotes && (char==='"' || char==="'")){
|
|
663
846
|
insideQuotes=true;
|
|
664
847
|
quoteChar=char;
|
|
665
848
|
} else if (insideQuotes && char===quoteChar){
|
|
666
849
|
insideQuotes=false;
|
|
667
850
|
quoteChar=null;
|
|
668
851
|
}
|
|
669
852
|
if (!insideQuotes && char==='>') break;
|
|
670
853
|
tagEnd++;
|
|
671
854
|
}
|
|
672
855
|
const tagContent=html.substring(i+1,tagEnd);
|
|
673
856
|
if (tagContent.startsWith("/")) stack.pop();
|
|
674
857
|
else{
|
|
675
858
|
let isSelfClosing=tagContent.endsWith('/');
|
|
676
859
|
const tagNameEnd=tagContent.search(/\s|>|\//);
|
|
677
860
|
const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
|
|
678
861
|
const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
|
|
679
862
|
const attributes=parseAttributes(attributesString);
|
|
680
863
|
if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
|
|
681
864
|
else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
|
|
682
865
|
}
|
|
683
866
|
i=tagEnd+1;
|
|
684
867
|
} else{
|
|
685
868
|
currentText+=html[i];
|
|
686
869
|
i++;
|
|
687
870
|
}
|
|
688
871
|
}
|
|
689
872
|
if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
|
|
690
873
|
return root;
|
|
691
|
-
}
|
|
692
|
-
function buildFromCache(cached){
|
|
693
874
|
function buildNode(cache,parent=null){
|
|
694
875
|
if(typeof cache==='string') return parent.childNodes.push(cache)
|
|
695
876
|
const{isSingle,tagName,attributes,childNodes,textContent}=cache
|
|
696
877
|
if(textContent) return parent.childNodes.push(new TextNode(textContent))
|
|
697
878
|
if(isSingle && parent) return parent.childNodes.push(new SingleNode(tagName,attributes))
|
|
698
879
|
const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
|
|
699
880
|
childNodes.forEach(childNode=>{
|
|
700
881
|
buildNode(childNode,newDoc)
|
|
701
882
|
});
|
|
702
883
|
return newDoc
|
|
703
884
|
}
|
|
704
885
|
return buildNode(cached)
|
|
705
|
-
}
|
|
706
886
|
|
|
707
|
-
function cacheDoc(doc){
|
|
708
887
|
const props=['isSingle','tagName','attributes']
|
|
709
888
|
function addToCache(element,cache={}){
|
|
710
889
|
if(typeof element==='string') return element
|
|
711
890
|
if(element.nodeName==='#text') return{textContent:element.textContent}
|
|
712
891
|
props.forEach(prop=>{
|
|
713
892
|
if(element[prop]){
|
|
714
893
|
cache[prop]=typeof element[prop]==='object' ?{...element[prop]} : element[prop]
|
|
715
894
|
}
|
|
716
895
|
});
|
|
717
896
|
if(!element.childNodes) return cache
|
|
718
897
|
cache.childNodes=[]
|
|
719
898
|
element.childNodes.forEach(childNode=>{
|
|
720
899
|
cache.childNodes.push(addToCache(childNode))
|
|
721
900
|
});
|
|
722
901
|
return cache
|
|
723
902
|
}
|
|
724
903
|
return addToCache(doc)
|
|
725
|
-
|
|
904
|
+
function getDataset(element){
|
|
905
|
+
return new Proxy(element.attributes,{
|
|
906
|
+
get: (target,prop)=>{return target[getDataName(prop)]},set: (target,prop,value)=>{target[getDataName(prop)]=value; return true},deleteProperty: (target,prop)=>{
|
|
907
|
+
const dataAttr=getDataName(prop)
|
|
908
|
+
if (dataAttr in target){
|
|
909
|
+
delete target[dataAttr];
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function find(selectors,element,collection,first=false,firstTime=true){
|
|
918
|
+
if(!firstTime)
|
|
919
|
+
for(let selector of selectors){
|
|
920
|
+
if(checkElement(element,selector)) collection.add(element)
|
|
921
|
+
}
|
|
922
|
+
if(element.children)
|
|
923
|
+
element.children.forEach(child=>{
|
|
924
|
+
if(first && collection.size>0) return
|
|
925
|
+
find(selectors,child,collection,first,false)
|
|
926
|
+
})
|
|
927
|
+
return firstTime ? [...collection] : collection
|
|
928
|
+
}
|
|
929
|
+
class TextNode{
|
|
930
|
+
constructor(data){
|
|
931
|
+
this.nodeName='#text';
|
|
932
|
+
this.parent=null;
|
|
933
|
+
this.textContent=data;
|
|
934
|
+
}
|
|
935
|
+
get nodeValue(){return this.textContent}
|
|
936
|
+
get parentNode(){return this.parent}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function buildStyle(attributes){
|
|
940
|
+
const styles=attributes.style || "";
|
|
941
|
+
const camelToKebab=str=>str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,'$1-$2').toLowerCase();
|
|
942
|
+
const kebabToCamel=str=>str.replace(/-([a-z])/g,g=>g[1].toUpperCase());
|
|
943
|
+
const baseStyleObj=styles.split(";").reduce((acc,style)=>{
|
|
944
|
+
const [key,value]=style.split(":").map(s=>s.trim());
|
|
945
|
+
if (key && value) acc[kebabToCamel(key)]=value;
|
|
946
|
+
return acc;
|
|
947
|
+
},{});
|
|
948
|
+
return new Proxy(baseStyleObj,{
|
|
949
|
+
get: (obj,prop)=>obj[camelToKebab(prop)] || obj[prop],set: (obj,prop,value)=>{
|
|
950
|
+
obj[camelToKebab(prop)]=value;
|
|
951
|
+
attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
|
|
952
|
+
return true;
|
|
953
|
+
},deleteProperty: (obj,prop)=>{
|
|
954
|
+
delete obj[camelToKebab(prop)];
|
|
955
|
+
attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
class NodeClassList{
|
|
962
|
+
constructor(node){ this.node=node }
|
|
963
|
+
get classes(){ return (this.node.attributes.class || "").split(" ").filter(Boolean) }
|
|
964
|
+
set classes(val){ this.node.attributes.class=val.join(" ") }
|
|
965
|
+
contains(className){ return this.classes.includes(className) }
|
|
966
|
+
add(className){
|
|
967
|
+
const currentClasses=this.classes;
|
|
968
|
+
if (!currentClasses.includes(className)) this.classes=[...currentClasses,className];
|
|
969
|
+
}
|
|
970
|
+
remove(className){ this.classes=this.classes.filter(cls=>cls!==className); }
|
|
971
|
+
toggle(className){
|
|
972
|
+
if (this.classes.includes(className)) this.remove(className);
|
|
973
|
+
else this.add(className);
|
|
974
|
+
}
|
|
975
|
+
replace(oldClass,newClass){
|
|
976
|
+
if (this.classes.includes(oldClass)){
|
|
977
|
+
this.remove(oldClass);
|
|
978
|
+
this.add(newClass);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
function insertBefore(arr,index,newItem){
|
|
983
|
+
const existingIndex=arr.indexOf(newItem);
|
|
984
|
+
if (existingIndex!==-1) arr.splice(existingIndex,1);
|
|
985
|
+
arr.splice(index,0,newItem);
|
|
986
|
+
}
|
|
987
|
+
class Node{
|
|
988
|
+
constructor(tagName,attributes={},parent=null){
|
|
989
|
+
this.isSingle=false;
|
|
990
|
+
this._tagName=tagName.toLowerCase();
|
|
991
|
+
this.tagName=tagName.toUpperCase();
|
|
992
|
+
this.attributes=attributes;
|
|
993
|
+
this.childNodes=[];
|
|
994
|
+
if (parent!==null) parent.childNodes.push(this)
|
|
995
|
+
this.parent=parent;
|
|
996
|
+
this._classList=null;
|
|
997
|
+
this.__style=null;
|
|
998
|
+
this._dataset=null
|
|
999
|
+
}
|
|
1000
|
+
get id(){ return this.attributes.id ? this.attributes.id : null; }
|
|
1001
|
+
set id(newValue){ this.attributes.id=newValue; }
|
|
1002
|
+
get className(){ return this.attributes.class || null }
|
|
1003
|
+
get parentNode(){ return this.parent }
|
|
1004
|
+
get ancestors(){
|
|
1005
|
+
if (!this.parent) return []
|
|
1006
|
+
const ancestors=[]
|
|
1007
|
+
let element=this.parent
|
|
1008
|
+
while (element.tagName!=='ROOT'){
|
|
1009
|
+
ancestors.push(element)
|
|
1010
|
+
element=element.parent
|
|
1011
|
+
}
|
|
1012
|
+
return ancestors.reverse()
|
|
1013
|
+
}
|
|
1014
|
+
get childNodeIndex(){
|
|
1015
|
+
if (!this.parent) return null
|
|
1016
|
+
return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
|
|
1017
|
+
}
|
|
1018
|
+
get childIndex(){
|
|
1019
|
+
if (!this.parent) return null
|
|
1020
|
+
return this.parent.children ? this.parent.children.indexOf(this) : null
|
|
1021
|
+
}
|
|
1022
|
+
get previousElementSibling(){ return this.prev }
|
|
1023
|
+
get prev(){
|
|
1024
|
+
if (this.childIndex===null) return null
|
|
1025
|
+
return this.parent.children[this.childIndex-1]
|
|
1026
|
+
}
|
|
1027
|
+
get nextElementSibling(){ return this.next }
|
|
1028
|
+
get next(){
|
|
1029
|
+
if (this.childIndex===null) return null
|
|
1030
|
+
return this.parent.children[this.childIndex+1] || null
|
|
1031
|
+
}
|
|
1032
|
+
get dataset(){
|
|
1033
|
+
if (!this._dataset) this._dataset=getDataset(this);
|
|
1034
|
+
return this._dataset;
|
|
1035
|
+
}
|
|
1036
|
+
get classList(){
|
|
1037
|
+
if (!this._classList) this._classList=new NodeClassList(this);
|
|
1038
|
+
return this._classList;
|
|
1039
|
+
}
|
|
1040
|
+
get style(){
|
|
1041
|
+
if (!this.__style) this.__style=buildStyle(this.attributes)
|
|
1042
|
+
return this.__style
|
|
1043
|
+
}
|
|
1044
|
+
get outerHTML(){
|
|
1045
|
+
const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
|
|
1046
|
+
return `<${this._tagName}${attrs ? ' '+attrs : ''}>${this.innerHTML}</${this._tagName}>`;
|
|
1047
|
+
}
|
|
1048
|
+
getAttribute(attrName){ return this.attributes[attrName]!==undefined ? this.attributes[attrName] : null }
|
|
1049
|
+
setAttribute(attrName,value=''){ this.attributes[attrName]=value }
|
|
1050
|
+
removeAttribute(attrName){ delete this.attributes[attrName] }
|
|
1051
|
+
remove(){
|
|
1052
|
+
if (!this.parent) return
|
|
1053
|
+
const index=this.childNodeIndex;
|
|
1054
|
+
if (index!==null) this.parent.childNodes.splice(index,1);
|
|
1055
|
+
}
|
|
1056
|
+
get innerHTML(){
|
|
1057
|
+
return this.childNodes.map(child=>{
|
|
1058
|
+
if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
|
|
1059
|
+
else if (child instanceof TextNode) return child.textContent;
|
|
1060
|
+
else return child
|
|
1061
|
+
}).join("");
|
|
1062
|
+
}
|
|
1063
|
+
get innerText(){
|
|
1064
|
+
return this.childNodes.map(child=>{
|
|
1065
|
+
if (child instanceof Node || child instanceof SingleNode) return child.innerText;
|
|
1066
|
+
else if (child instanceof TextNode) return child.textContent;
|
|
1067
|
+
else return child
|
|
1068
|
+
}).join("");
|
|
1069
|
+
}
|
|
1070
|
+
set innerText(value){
|
|
1071
|
+
this.childNodes=[new TextNode(value)]
|
|
1072
|
+
return value
|
|
1073
|
+
}
|
|
1074
|
+
$$(query){ return this.querySelectorAll(query) }
|
|
1075
|
+
querySelectorAll(query){
|
|
1076
|
+
const selectors=Query.get(query)
|
|
1077
|
+
return find(selectors,this,new Set())
|
|
1078
|
+
}
|
|
1079
|
+
$(query){ return this.querySelector(query) }
|
|
1080
|
+
querySelector(query){
|
|
1081
|
+
const selectors=Query.get(query)
|
|
1082
|
+
return find(selectors,this,new Set(),true)[0] || null
|
|
1083
|
+
}
|
|
1084
|
+
getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
|
|
1085
|
+
getElementsByTagName(query){ return this.querySelectorAll(query) }
|
|
1086
|
+
getElementById(query){ return this.querySelector('#'+query) }
|
|
1087
|
+
get children(){
|
|
1088
|
+
return this.childNodes.filter(child=>{
|
|
1089
|
+
if (!(child instanceof Node)) return false
|
|
1090
|
+
if (child._tagName==='#comment') return false
|
|
1091
|
+
return true
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
insertAdjacentElement(position,newElement){
|
|
1095
|
+
if (newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
|
|
1096
|
+
const pos=position.toLowerCase();
|
|
1097
|
+
if(newElement.parentNode){
|
|
1098
|
+
newElement.parentNode.childNodes=newElement.parentNode.childNodes.filter(el=>el!==newElement)
|
|
1099
|
+
}
|
|
1100
|
+
if (pos==='afterbegin' || pos==='beforeend'){
|
|
1101
|
+
if (pos==="afterbegin") this.childNodes.unshift(newElement);
|
|
1102
|
+
else if (pos==="beforeend") this.childNodes.push(newElement);
|
|
1103
|
+
newElement.parent=this
|
|
1104
|
+
return newElement
|
|
1105
|
+
}
|
|
1106
|
+
if (!this.parent) throw new Error("Can't insert element to element without parent")
|
|
1107
|
+
if (pos==="beforebegin") insertBefore(this.parent.childNodes,this.childNodeIndex,newElement)
|
|
1108
|
+
else if (pos==="afterend") this.parent.childNodes.splice(this.childNodeIndex+1,0,newElement);
|
|
1109
|
+
newElement.parent=this.parent
|
|
1110
|
+
return newElement
|
|
1111
|
+
}
|
|
1112
|
+
insertAdjacentHTML(position,html){
|
|
1113
|
+
const newNode=parseHTML(html);
|
|
1114
|
+
newNode.childNodes.forEach(node=>{
|
|
1115
|
+
this.insertAdjacentElement(position,node);
|
|
1116
|
+
});
|
|
1117
|
+
return newNode
|
|
1118
|
+
}
|
|
1119
|
+
insertAdjacentText(position,text){
|
|
1120
|
+
return this.insertAdjacentElement(position,new TextNode(text));
|
|
1121
|
+
}
|
|
1122
|
+
insert(position,element){
|
|
1123
|
+
const positions=['beforebegin','afterbegin','beforeend','afterend']
|
|
1124
|
+
if (positions[position]) position=positions[position]
|
|
1125
|
+
if (typeof element==='string'){
|
|
1126
|
+
element=element.trim()
|
|
1127
|
+
if (element.startsWith('<') && element.endsWith('>')){
|
|
1128
|
+
return this.insertAdjacentHTML(position,element)
|
|
1129
|
+
}
|
|
1130
|
+
return this.insertAdjacentText(position,element)
|
|
1131
|
+
}
|
|
1132
|
+
return this.insertAdjacentElement(position,element)
|
|
1133
|
+
}
|
|
1134
|
+
set innerHTML(html){
|
|
1135
|
+
this.childNodes=html.trim().startsWith('<') ? parseHTML(html).childNodes : [html]
|
|
1136
|
+
this.children.forEach(child=>child.parent=this);
|
|
1137
|
+
}
|
|
1138
|
+
set outerHTML(html){
|
|
1139
|
+
const parsed=parseHTML(html);
|
|
1140
|
+
if (!this.parent) throw new Error('element has no parent node')
|
|
1141
|
+
const index=this.childIndex
|
|
1142
|
+
if (index!==null) this.parent.childNodes.splice(index,1,...parsed.childNodes);
|
|
1143
|
+
}
|
|
1144
|
+
appendChild(newChild){
|
|
1145
|
+
if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
|
|
1146
|
+
if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
|
|
1147
|
+
} else if (typeof newChild==='string') newChild=new TextNode(newChild)
|
|
1148
|
+
else return newChild
|
|
1149
|
+
this.childNodes.push(newChild);
|
|
1150
|
+
newChild.parent=this;
|
|
1151
|
+
return newChild;
|
|
1152
|
+
}
|
|
1153
|
+
get textContent(){
|
|
1154
|
+
if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
|
|
1155
|
+
return this.childNodes.map(child=>{
|
|
1156
|
+
if (child instanceof SingleNode) return ''
|
|
1157
|
+
if (child instanceof TextNode) return child.nodeValue
|
|
1158
|
+
if (child instanceof Node) return child.textContent;
|
|
1159
|
+
else return child;
|
|
1160
|
+
}).join('');
|
|
1161
|
+
}
|
|
1162
|
+
set textContent(value){
|
|
1163
|
+
this.childNodes=[];
|
|
1164
|
+
if (value!==null && value!==undefined){
|
|
1165
|
+
this.childNodes.push(value.toString());
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
class SingleNode extends Node{
|
|
1170
|
+
constructor(tagName,attributes={},parent=null){
|
|
1171
|
+
if(attributes['?'] && tagName==='?xml') delete attributes['?']
|
|
1172
|
+
super(tagName,attributes,parent);
|
|
1173
|
+
this.isSingle=true
|
|
1174
|
+
}
|
|
1175
|
+
get outerHTML(){
|
|
1176
|
+
if (this._tagName==="#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
|
|
1177
|
+
const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
|
|
1178
|
+
return `<${this._tagName} ${attrs}${this._tagName==='?xml' ? '?' : ''}>`;
|
|
1179
|
+
}
|
|
1180
|
+
get innerHTML(){ return ""; }
|
|
1181
|
+
set innerHTML(_){ }
|
|
1182
|
+
$(_){return null}
|
|
1183
|
+
$$(_){return []}
|
|
1184
|
+
querySelectorAll(_){ return []; }
|
|
1185
|
+
querySelector(_){ return null; }
|
|
1186
|
+
getElementsByClassName(_){ return []; }
|
|
1187
|
+
getElementsByTagName(_){ return []; }
|
|
1188
|
+
getElementById(_){ return null; }
|
|
1189
|
+
get children(){ return []; }
|
|
1190
|
+
insertAdjacentElement(position,newElement){
|
|
1191
|
+
if(position==='afterbegin') position='beforebegin'
|
|
1192
|
+
if(position==='beforeend') position='afterend'
|
|
1193
|
+
super.insertAdjacentElement(position,newElement)
|
|
1194
|
+
}
|
|
1195
|
+
insertAdjacentHTML(position,html){
|
|
1196
|
+
if(position==='afterbegin') position='beforebegin'
|
|
1197
|
+
if(position==='beforeend') position='afterend'
|
|
1198
|
+
super.insertAdjacentHTML(position,html)
|
|
1199
|
+
}
|
|
1200
|
+
insertAdjacentText(position,text){
|
|
1201
|
+
if(position==='afterbegin') position='beforebegin'
|
|
1202
|
+
if(position==='beforeend') position='afterend'
|
|
1203
|
+
super.insertAdjacentText(position,text)
|
|
1204
|
+
}
|
|
1205
|
+
insert(position,element){
|
|
1206
|
+
if(position===1) position=0
|
|
1207
|
+
if(position===2) position=3
|
|
1208
|
+
super.insert(position,element)
|
|
1209
|
+
}
|
|
1210
|
+
appendChild(_){ }
|
|
1211
|
+
get textContent(){ return ""; }
|
|
1212
|
+
set textContent(_){ }
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
class Root extends Node{
|
|
1216
|
+
constructor(){
|
|
1217
|
+
super('ROOT',{},null);
|
|
1218
|
+
this.isSingle=false
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
class Document extends Node{
|
|
1222
|
+
constructor(html,url){
|
|
1223
|
+
super('ROOT',{},null);
|
|
1224
|
+
this.isSingle=false
|
|
1225
|
+
this.URL=url
|
|
1226
|
+
if(html instanceof Document) this.childNodes=[...buildFromCache(cacheDoc(html)).childNodes]
|
|
1227
|
+
else if(typeof html==='string') this.innerHTML=html
|
|
1228
|
+
else this.html
|
|
1229
|
+
}
|
|
1230
|
+
get documentElement(){return this.html}
|
|
1231
|
+
get html(){
|
|
1232
|
+
const html=this.$('html')
|
|
1233
|
+
if(html===null) this.innerHTML=/*html*/`<html lang="en"><head><meta charset="UTF-8"><title></title></head><body></body></html>`
|
|
1234
|
+
return this.$('html')
|
|
1235
|
+
}
|
|
1236
|
+
get innerHTML(){
|
|
1237
|
+
const inner=super.innerHTML
|
|
1238
|
+
return '<!DOCTYPE html>'+inner
|
|
1239
|
+
}
|
|
1240
|
+
set innerHTML(html){return super.innerHTML=html}
|
|
1241
|
+
get head(){
|
|
1242
|
+
const head=this.html.$('head')
|
|
1243
|
+
if(head===null) this.html.insert(1,'<head></head>')
|
|
1244
|
+
return this.$('head')
|
|
1245
|
+
}
|
|
1246
|
+
get body(){
|
|
1247
|
+
const body=this.html.$('body')
|
|
1248
|
+
if(body===null) this.head.insert(3,'<body></body>')
|
|
1249
|
+
return this.$('body')
|
|
1250
|
+
}
|
|
1251
|
+
get title(){
|
|
1252
|
+
const title=this.head.$('title')
|
|
1253
|
+
if(title===null) this.head.insert(1,'<title></title>')
|
|
1254
|
+
return this.$('title').innerText
|
|
1255
|
+
}
|
|
1256
|
+
get charset(){return this.$('meta[charset]')}
|
|
1257
|
+
set title(title){return this.head.$('title').innerText=title}
|
|
1258
|
+
get clone(){return new Document(this)}
|
|
1259
|
+
}
|
|
1260
|
+
function parseAttributes(str){
|
|
1261
|
+
const attrs={};
|
|
1262
|
+
let key="";
|
|
1263
|
+
let value="";
|
|
1264
|
+
let isKey=true;
|
|
1265
|
+
let quoteChar=null;
|
|
1266
|
+
for (let i=0; i< str.length; i++){
|
|
1267
|
+
const char=str[i];
|
|
1268
|
+
if (isKey && (char==='=' || char===' ')){
|
|
1269
|
+
if (char==='=') isKey=false;
|
|
1270
|
+
else if (key.trim()){
|
|
1271
|
+
attrs[key.trim()]=true;
|
|
1272
|
+
key="";
|
|
1273
|
+
}
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
if (!quoteChar && (char==='"' || char==="'")){
|
|
1277
|
+
quoteChar=char;
|
|
1278
|
+
continue;
|
|
1279
|
+
} else if (quoteChar && char===quoteChar){
|
|
1280
|
+
quoteChar=null;
|
|
1281
|
+
attrs[key.trim()]=value.trim();
|
|
1282
|
+
key=""; value=""; isKey=true;
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
if (isKey) key+=char;
|
|
1286
|
+
else value+=char;
|
|
1287
|
+
}
|
|
1288
|
+
if (key.trim() &&!value) attrs[key.trim()]='';
|
|
1289
|
+
return attrs;
|
|
1290
|
+
}
|
|
1291
|
+
const VOID_TAGS=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","!doctype",'?xml']);
|
|
1292
|
+
function parseHTML(html){
|
|
1293
|
+
const root=new Root();
|
|
1294
|
+
const stack=[root];
|
|
1295
|
+
let currentText="",i=0;
|
|
1296
|
+
let max=0
|
|
1297
|
+
function parseScript(){
|
|
1298
|
+
if (!html.startsWith("<script",i)) return false;
|
|
1299
|
+
const openTagEnd=html.indexOf(">",i);
|
|
1300
|
+
if (openTagEnd===-1) return false;
|
|
1301
|
+
const attributesString=html.substring(i+7,openTagEnd).trim();
|
|
1302
|
+
const attributes=parseAttributes(attributesString);
|
|
1303
|
+
let closeTagStart=html.indexOf("</script>",openTagEnd);
|
|
1304
|
+
if (closeTagStart===-1) return false;
|
|
1305
|
+
const content=html.substring(openTagEnd+1,closeTagStart);
|
|
1306
|
+
const scriptNode=new Node('script',attributes,stack[stack.length-1]);
|
|
1307
|
+
if(content.length>0) scriptNode.childNodes.push(content);
|
|
1308
|
+
i=closeTagStart+9;
|
|
1309
|
+
return true;
|
|
1310
|
+
}
|
|
1311
|
+
function parseSpecial(startStr,endStr,n1,n2,tag){
|
|
1312
|
+
if (!html.startsWith(startStr,i)) return false
|
|
1313
|
+
if(currentText.length) stack[stack.length - 1].childNodes.push(new TextNode(currentText)); // new!
|
|
1314
|
+
const end=html.indexOf(endStr,i+n1);
|
|
1315
|
+
const strNode=new Node(tag,{},stack[stack.length-1]);
|
|
1316
|
+
strNode.childNodes.push(html.substring(i+n1,end));
|
|
1317
|
+
i=end+n2;
|
|
1318
|
+
return true
|
|
1319
|
+
}
|
|
1320
|
+
while (i< html.length){
|
|
1321
|
+
if(i>=max) max=i;
|
|
1322
|
+
else break;
|
|
1323
|
+
if (parseScript()) continue
|
|
1324
|
+
if (parseSpecial("<!--","-->",4,3,'#comment')) continue
|
|
1325
|
+
if (parseSpecial("<style","</style>",7,8,'style')) continue
|
|
1326
|
+
if (html.startsWith("<![CDATA[",i)){
|
|
1327
|
+
const end=html.indexOf("]]>",i+9);
|
|
1328
|
+
if (end===-1) break;
|
|
1329
|
+
const content=html.substring(i+9,end);
|
|
1330
|
+
const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
|
|
1331
|
+
cdataNode.nodeValue=content;
|
|
1332
|
+
i=end+3;
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
if (html.startsWith("<",i)){
|
|
1336
|
+
if (currentText && stack[stack.length-1]){
|
|
1337
|
+
const textNode=new TextNode(currentText)
|
|
1338
|
+
stack[stack.length-1].childNodes.push(textNode);
|
|
1339
|
+
textNode.parent=stack[stack.length-1]
|
|
1340
|
+
currentText="";
|
|
1341
|
+
}
|
|
1342
|
+
let tagEnd=i+1;
|
|
1343
|
+
let insideQuotes=false;
|
|
1344
|
+
let quoteChar=null;
|
|
1345
|
+
while (tagEnd< html.length){
|
|
1346
|
+
const char=html[tagEnd];
|
|
1347
|
+
if (!insideQuotes && (char==='"' || char==="'")){
|
|
1348
|
+
insideQuotes=true;
|
|
1349
|
+
quoteChar=char;
|
|
1350
|
+
} else if (insideQuotes && char===quoteChar){
|
|
1351
|
+
insideQuotes=false;
|
|
1352
|
+
quoteChar=null;
|
|
1353
|
+
}
|
|
1354
|
+
if (!insideQuotes && char==='>') break;
|
|
1355
|
+
tagEnd++;
|
|
1356
|
+
}
|
|
1357
|
+
const tagContent=html.substring(i+1,tagEnd);
|
|
1358
|
+
if (tagContent.startsWith("/")) stack.pop();
|
|
1359
|
+
else{
|
|
1360
|
+
let isSelfClosing=tagContent.endsWith('/');
|
|
1361
|
+
const tagNameEnd=tagContent.search(/\s|>|\//);
|
|
1362
|
+
const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
|
|
1363
|
+
const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
|
|
1364
|
+
const attributes=parseAttributes(attributesString);
|
|
1365
|
+
if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
|
|
1366
|
+
else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
|
|
1367
|
+
}
|
|
1368
|
+
i=tagEnd+1;
|
|
1369
|
+
} else{
|
|
1370
|
+
currentText+=html[i];
|
|
1371
|
+
i++;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
|
|
1375
|
+
return root;
|
|
1376
|
+
}
|
|
1377
|
+
function buildFromCache(cached){
|
|
1378
|
+
function buildNode(cache,parent=null){
|
|
1379
|
+
if(typeof cache==='string') return parent.childNodes.push(cache)
|
|
1380
|
+
const{isSingle,tagName,attributes,childNodes,textContent}=cache
|
|
1381
|
+
if(textContent) return parent.childNodes.push(new TextNode(textContent))
|
|
1382
|
+
if(isSingle && parent) return parent.childNodes.push(new SingleNode(tagName,attributes))
|
|
1383
|
+
const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
|
|
1384
|
+
childNodes.forEach(childNode=>{
|
|
1385
|
+
buildNode(childNode,newDoc)
|
|
1386
|
+
});
|
|
1387
|
+
return newDoc
|
|
1388
|
+
}
|
|
1389
|
+
return buildNode(cached)
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function cacheDoc(doc){
|
|
1393
|
+
const props=['isSingle','tagName','attributes']
|
|
1394
|
+
function addToCache(element,cache={}){
|
|
1395
|
+
if(typeof element==='string') return element
|
|
1396
|
+
if(element.nodeName==='#text') return{textContent:element.textContent}
|
|
1397
|
+
props.forEach(prop=>{
|
|
1398
|
+
if(element[prop]){
|
|
1399
|
+
cache[prop]=typeof element[prop]==='object' ?{...element[prop]} : element[prop]
|
|
1400
|
+
}
|
|
1401
|
+
});
|
|
1402
|
+
if(!element.childNodes) return cache
|
|
1403
|
+
cache.childNodes=[]
|
|
1404
|
+
element.childNodes.forEach(childNode=>{
|
|
1405
|
+
cache.childNodes.push(addToCache(childNode))
|
|
1406
|
+
});
|
|
1407
|
+
return cache
|
|
1408
|
+
}
|
|
1409
|
+
return addToCache(doc)
|
|
1410
|
+
}
|
|
726
1411
|
export default { parseHTML, Node, Query, TextNode, SingleNode, buildFromCache, cacheDoc, Root, Document }
|