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