als-document 1.0.1-alpha → 1.0.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 +15 -7
- package/index.js +15 -7
- package/index.mjs +15 -7
- package/package.json +2 -5
- package/readme.md +32 -3
- package/src/build.js +5 -4
- package/src/node/node.js +51 -14
- package/src/node/root.js +11 -0
- package/src/node/single-node.js +6 -5
- package/src/parse/cache.js +33 -0
- package/src/parse/parse-atts.js +1 -1
- package/src/parse/parser.js +30 -6
- package/src/query/check-element.js +2 -3
- package/src/query/query.js +13 -6
- package/tests/cache.js +19 -0
- package/tests/index.html +6 -5
- package/tests/node.js +1 -1
- package/tests/parse-real.js +3 -2
- package/tests/parser.js +6 -6
- package/tests/test.js +169 -0
package/document.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const alsDocument = (function(){
|
|
2
|
-
class Query{
|
|
3
2
|
static get(query){
|
|
4
3
|
let q=new Query(query)
|
|
5
4
|
return q.selectors
|
|
6
5
|
}
|
|
7
6
|
constructor(query){
|
|
8
7
|
this.query=query
|
|
9
8
|
this.selectors=[]
|
|
10
9
|
this.stringValues=[];
|
|
11
10
|
this.parseSelectors(query.split(','))
|
|
12
11
|
}
|
|
13
12
|
parseSelectors(selectors){
|
|
14
13
|
selectors.forEach(selector=>{
|
|
15
14
|
let originalSelector=selector.trim()
|
|
16
15
|
selector=this.removeSpaces(selector)
|
|
17
16
|
this.stringValues=[]
|
|
18
17
|
selector=selector.replace(/\[.*?\]/g,(value)=>{
|
|
19
18
|
this.stringValues.push(value)
|
|
20
19
|
return `[${this.stringValues.length-1}]`
|
|
21
20
|
})
|
|
22
21
|
let [element,ancestors]=this.splitAndCutLast(selector,' ')
|
|
23
22
|
element=this.getFamily(element)
|
|
24
23
|
if (ancestors.length>0)
|
|
25
24
|
element.ancestors=ancestors.map(ancestor=>this.getFamily(ancestor))
|
|
26
25
|
element.group=originalSelector
|
|
27
26
|
this.selectors.push(element)
|
|
28
27
|
});
|
|
29
28
|
}
|
|
30
29
|
splitAndCutLast(string,splitBy){
|
|
31
30
|
const array=string.split(splitBy);
|
|
32
31
|
const last=array.pop();
|
|
33
32
|
return [last,array];
|
|
34
33
|
}
|
|
35
34
|
getFamily(group,element,prev,prevAny,sign){
|
|
36
35
|
if (group.match(/\~|\+/)!==null){
|
|
37
36
|
let [last,prevBrothers]=this.splitAndCutLast(group,/\~|\+/)
|
|
38
37
|
let signs=group.replace(last,'')
|
|
39
38
|
prevBrothers.forEach(el=>signs=signs.replace(el,''))
|
|
40
39
|
signs=signs.match(/\~|\+/g)
|
|
41
40
|
if (signs.length==1){
|
|
42
41
|
sign=signs[0]
|
|
43
42
|
} else if (signs.length>1){
|
|
44
43
|
sign=signs.splice(signs.length-1,signs.length-1)[0]
|
|
45
44
|
prevBrothers[0]=prevBrothers.map((b,i)=>{
|
|
46
45
|
if (i< prevBrothers.length-1) b+=signs[i]
|
|
47
46
|
return b
|
|
48
47
|
}).join('')
|
|
49
48
|
prevBrothers[0]=this.getFamily(prevBrothers[0])
|
|
50
49
|
}
|
|
51
50
|
if (sign=='~') prevAny=prevBrothers[0]
|
|
52
51
|
else if (sign=='+') prev=prevBrothers[0]
|
|
53
52
|
element=last
|
|
54
53
|
} else element=group
|
|
55
54
|
let family
|
|
56
55
|
if (prev || prevAny){
|
|
57
56
|
family=this.getParents(element)
|
|
58
57
|
if (prev) family.prev=this.getParents(prev)
|
|
59
58
|
if (prevAny) family.prevAny=this.getParents(prevAny)
|
|
60
59
|
} else family=this.getParents(element)
|
|
61
60
|
if (family.query!==group) family.group=group
|
|
62
61
|
return family
|
|
63
62
|
}
|
|
64
63
|
getParents(selector){
|
|
65
64
|
if (typeof selector=='string'){
|
|
66
65
|
let [element,parents]=this.splitAndCutLast(selector,'>')
|
|
67
66
|
element=this.buildElement(element)
|
|
68
67
|
parents=parents.map(parent=>this.buildElement(parent))
|
|
69
68
|
if (parents.length>0) element.parents=parents
|
|
70
69
|
return element
|
|
71
70
|
} else return selector
|
|
72
71
|
}
|
|
73
72
|
buildElement(element,id=null,tag=null,classList=[]){
|
|
74
73
|
let query=element
|
|
75
74
|
element=element.replace(/\#(\w-?)*/,$id=>{
|
|
76
75
|
id=$id.replace(/^\#/,''); return ''
|
|
77
76
|
})
|
|
78
77
|
element=element.replace(/\.(\w-?)*/,$class=>{
|
|
79
78
|
classList.push($class.replace(/^\./,'')); return ''
|
|
80
79
|
})
|
|
81
80
|
element=element.replace(/(\w\:?-?)*/,$tag=>{
|
|
82
81
|
tag=$tag=='' ? null : $tag; return ''
|
|
83
82
|
})
|
|
84
83
|
let attribs=this.getAttributes(element)
|
|
85
84
|
element={ query }
|
|
86
85
|
if (id) element.id=id
|
|
87
86
|
if (tag) element.tag=tag
|
|
88
87
|
if (classList.length>0) element.classList=classList
|
|
89
88
|
if (attribs.length>0) element.attribs=attribs
|
|
90
89
|
return element
|
|
91
90
|
}
|
|
92
91
|
getAttributes(element){
|
|
93
92
|
let attribs=this.stringValues.filter((value,index)=>{
|
|
94
93
|
let searchValue=`[${index}]`
|
|
95
94
|
if (element.match(searchValue)) return true
|
|
96
95
|
else return false
|
|
97
96
|
})
|
|
98
97
|
attribs=attribs.map(attrib=>{
|
|
99
98
|
let query=attrib
|
|
100
99
|
attrib=attrib.replace('[','').replace(']','')
|
|
101
100
|
let [name,value]=attrib.split(/[\~\|\^\$\*]?\=/)
|
|
102
101
|
let sign=attrib.replace(name,'').replace(value,'')
|
|
103
102
|
attrib={ query }
|
|
104
103
|
if (name) attrib.name=name
|
|
105
104
|
if (value) attrib.value=value.trim().replace(/^\"/,'').replace(/\"$/,'')
|
|
106
105
|
if (sign){
|
|
107
106
|
attrib.sign=sign
|
|
108
107
|
attrib.check=this.getAttribFn(sign).bind(attrib)
|
|
109
108
|
}
|
|
110
109
|
return attrib
|
|
111
110
|
});
|
|
112
111
|
return attribs
|
|
113
112
|
}
|
|
114
113
|
getAttribFn(sign){
|
|
115
114
|
if (sign=='=') return function (value){ return value===this.value }
|
|
116
115
|
if (sign=='*=') return function (value){ return value.includes(this.value) }
|
|
117
116
|
if (sign=='^=') return function (value){ return value.startsWith(this.value) }
|
|
118
117
|
if (sign=='$=') return function (value){ return value.endsWith(this.value) }
|
|
119
118
|
if (sign=='|=') return function (value){
|
|
120
119
|
return value.trim().split(' ').length==1
|
|
121
120
|
&& (value.startsWith(this.value) || value.startsWith(this.value+'-'))
|
|
122
121
|
? true : false
|
|
123
122
|
}
|
|
124
123
|
if (sign=='~=') return function (value){
|
|
125
124
|
return this.value.trim().split(' ').length==1 && value.includes(this.value) ? true : false
|
|
126
125
|
}
|
|
127
126
|
}
|
|
128
127
|
removeSpaces(selector){
|
|
129
128
|
selector=selector.replace(/\s{2}/g,' ')
|
|
130
129
|
selector=selector.replace(/\s?\^?\$?\|?\~?\*?\=\s*/g,(m)=>m.trim())
|
|
131
130
|
selector=selector.replace(/\s?(\+|\~|\>)\s?/g,(m)=>m.trim())
|
|
132
131
|
return selector
|
|
133
132
|
}
|
|
133
|
+
class Query{
|
|
134
134
|
static get(query){
|
|
135
135
|
let q=new Query(query)
|
|
136
136
|
return q.selectors
|
|
137
137
|
}
|
|
138
138
|
constructor(query){
|
|
139
139
|
this.query=query
|
|
140
140
|
this.selectors=[]
|
|
141
141
|
this.stringValues=[];
|
|
142
142
|
this.parseSelectors(query.split(','))
|
|
143
143
|
}
|
|
144
144
|
parseSelectors(selectors){
|
|
145
145
|
selectors.forEach(selector=>{
|
|
146
146
|
let originalSelector=selector.trim()
|
|
147
147
|
selector=this.removeSpaces(selector)
|
|
148
148
|
this.stringValues=[]
|
|
149
149
|
selector=selector.replace(/\[.*?\]/g,(value)=>{
|
|
150
150
|
this.stringValues.push(value)
|
|
151
151
|
return `[${this.stringValues.length-1}]`
|
|
152
152
|
})
|
|
153
153
|
let [element,ancestors]=this.splitAndCutLast(selector,' ')
|
|
154
154
|
element=this.getFamily(element)
|
|
155
155
|
if (ancestors.length>0)
|
|
156
156
|
element.ancestors=ancestors.map(ancestor=>this.getFamily(ancestor))
|
|
157
157
|
element.group=originalSelector
|
|
158
158
|
this.selectors.push(element)
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
161
|
splitAndCutLast(string,splitBy){
|
|
162
162
|
const array=string.split(splitBy);
|
|
163
163
|
const last=array.pop();
|
|
164
164
|
return [last,array];
|
|
165
165
|
}
|
|
166
166
|
getFamily(group,element,prev,prevAny,sign){
|
|
167
167
|
if (group.match(/\~|\+/)!==null){
|
|
168
168
|
let [last,prevBrothers]=this.splitAndCutLast(group,/\~|\+/)
|
|
169
169
|
let signs=group.replace(last,'')
|
|
170
170
|
prevBrothers.forEach(el=>signs=signs.replace(el,''))
|
|
171
171
|
signs=signs.match(/\~|\+/g)
|
|
172
172
|
if (signs.length==1){
|
|
173
173
|
sign=signs[0]
|
|
174
174
|
} else if (signs.length>1){
|
|
175
175
|
sign=signs.splice(signs.length-1,signs.length-1)[0]
|
|
176
176
|
prevBrothers[0]=prevBrothers.map((b,i)=>{
|
|
177
177
|
if (i< prevBrothers.length-1) b+=signs[i]
|
|
178
178
|
return b
|
|
179
179
|
}).join('')
|
|
180
180
|
prevBrothers[0]=this.getFamily(prevBrothers[0])
|
|
181
181
|
}
|
|
182
182
|
if (sign=='~') prevAny=prevBrothers[0]
|
|
183
183
|
else if (sign=='+') prev=prevBrothers[0]
|
|
184
184
|
element=last
|
|
185
185
|
} else element=group
|
|
186
186
|
let family
|
|
187
187
|
if (prev || prevAny){
|
|
188
188
|
family=this.getParents(element)
|
|
189
189
|
if (prev) family.prev=this.getParents(prev)
|
|
190
190
|
if (prevAny) family.prevAny=this.getParents(prevAny)
|
|
191
191
|
} else family=this.getParents(element)
|
|
192
192
|
if (family.query!==group) family.group=group
|
|
193
193
|
return family
|
|
194
194
|
}
|
|
195
195
|
getParents(selector){
|
|
196
196
|
if (typeof selector=='string'){
|
|
197
197
|
let [element,parents]=this.splitAndCutLast(selector,'>')
|
|
198
198
|
element=this.buildElement(element)
|
|
199
199
|
parents=parents.map(parent=>this.buildElement(parent))
|
|
200
200
|
if (parents.length>0) element.parents=parents
|
|
201
201
|
return element
|
|
202
202
|
} else return selector
|
|
203
203
|
}
|
|
204
204
|
buildElement(element,id=null,tag=null,classList=[]){
|
|
205
205
|
let query=element
|
|
206
206
|
element=element.replace(/\#(\w-?)*/,$id=>{
|
|
207
207
|
id=$id.replace(/^\#/,''); return ''
|
|
208
208
|
})
|
|
209
209
|
element=element.replace(/\.(\w-?)*/,$class=>{
|
|
210
210
|
classList.push($class.replace(/^\./,'')); return ''
|
|
211
211
|
})
|
|
212
212
|
element=element.replace(/(\w\:?-?)*/,$tag=>{
|
|
213
213
|
tag=$tag=='' ? null : $tag; return ''
|
|
214
214
|
})
|
|
215
215
|
let attribs=this.getAttributes(element)
|
|
216
216
|
element={ query }
|
|
217
217
|
if (id) element.id=id
|
|
218
218
|
if (tag) element.tag=tag
|
|
219
219
|
if (classList.length>0) element.classList=classList
|
|
220
220
|
if (attribs.length>0) element.attribs=attribs
|
|
221
221
|
return element
|
|
222
222
|
}
|
|
223
223
|
getAttributes(element){
|
|
224
224
|
let attribs=this.stringValues.filter((value,index)=>{
|
|
225
225
|
let searchValue=`[${index}]`
|
|
226
226
|
if (element.match(searchValue)) return true
|
|
227
227
|
else return false
|
|
228
228
|
})
|
|
229
229
|
attribs=attribs.map(attrib=>{
|
|
230
230
|
let query=attrib
|
|
231
231
|
attrib=attrib.replace('[','').replace(']','')
|
|
232
232
|
let [name,...values]=attrib.split('=')
|
|
233
233
|
const value=values.join('=').trim().replace(/^\"/,'').replace(/\"$/,'')
|
|
234
234
|
let sign
|
|
235
235
|
attrib={query,name}
|
|
236
236
|
if(value){
|
|
237
237
|
sign='='
|
|
238
238
|
name=name.replace(/[\~\|\^\$\*]$/,(match=>{
|
|
239
239
|
sign=match+sign
|
|
240
240
|
return ''
|
|
241
241
|
}))
|
|
242
242
|
attrib.value=value
|
|
243
243
|
}
|
|
244
244
|
if (sign){
|
|
245
245
|
attrib.sign=sign
|
|
246
246
|
attrib.check=this.getAttribFn(sign).bind(attrib)
|
|
247
247
|
}
|
|
248
248
|
return attrib
|
|
249
249
|
});
|
|
250
250
|
return attribs
|
|
251
251
|
}
|
|
252
252
|
getAttribFn(sign){
|
|
253
253
|
if (sign=='=') return function (value){ return value===this.value }
|
|
254
254
|
if (sign=='*=') return function (value){ return value.includes(this.value) }
|
|
255
255
|
if (sign=='^=') return function (value){ return value.startsWith(this.value) }
|
|
256
256
|
if (sign=='$=') return function (value){ return value.endsWith(this.value) }
|
|
257
257
|
if (sign=='|=') return function (value){
|
|
258
258
|
return value.trim().split(' ').length==1
|
|
259
259
|
&& (value.startsWith(this.value) || value.startsWith(this.value+'-'))
|
|
260
260
|
? true : false
|
|
261
261
|
}
|
|
262
262
|
if (sign=='~=') return function (value){
|
|
263
263
|
return this.value.trim().split(' ').length==1 && value.includes(this.value) ? true : false
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
removeSpaces(selector){
|
|
267
267
|
selector=selector.replace(/\s{2}/g,' ')
|
|
268
268
|
selector=selector.replace(/\s?\^?\$?\|?\~?\*?\=\s*/g,(m)=>m.trim())
|
|
269
269
|
selector=selector.replace(/\s?(\+|\~|\>)\s?/g,(m)=>m.trim())
|
|
270
270
|
return selector
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
|
-
function checkElement(el,selector){
|
|
274
273
|
if(selector==undefined) return true
|
|
275
274
|
if(el==null) return false
|
|
276
275
|
let{tag,classList,attributes,id,prev,ancestors,parents,prevAny}=selector
|
|
277
276
|
if(typeof el==='string') return false
|
|
278
277
|
if(el.isSpecial) return false
|
|
279
278
|
if(id!==undefined && el.id===null) return false
|
|
280
279
|
if(id && id!==el.id) return false
|
|
281
280
|
if(tag && el.tagName===undefined) return false
|
|
282
281
|
else if(tag && tag!==el.tagName) return false
|
|
283
282
|
const clas=el.attributes.class
|
|
284
283
|
if(classList!==undefined && (clas===undefined || clas==='')) return false
|
|
285
284
|
else if(classList!==undefined){
|
|
286
285
|
if(classList.every(e=>el.classList.contains(e))===false) return false
|
|
287
286
|
}
|
|
288
287
|
if(checkattributes(attributes,el)===false) return false
|
|
289
288
|
if(checkElement(el.prev,prev)===false) return false
|
|
290
289
|
if(checkAncestors(el.ancestors,ancestors)===false) return false
|
|
291
290
|
if(checkParents(el.ancestors,parents)===false) return false
|
|
292
291
|
if(el.parent){
|
|
293
292
|
if(checkPrevAny(el.parent.children,el.childIndex,prevAny)==false) return false
|
|
294
293
|
}
|
|
295
294
|
return true
|
|
295
|
+
function checkElement(el,selector){
|
|
296
296
|
if(selector==undefined) return true
|
|
297
297
|
if(el==null) return false
|
|
298
298
|
let{tag,classList,attribs:attributes,id,prev,ancestors,parents,prevAny}=selector
|
|
299
299
|
if(typeof el==='string') return false
|
|
300
300
|
if(id!==undefined && el.id===null) return false
|
|
301
301
|
if(id && id!==el.id) return false
|
|
302
302
|
if(tag && el.tagName===undefined) return false
|
|
303
303
|
else if(tag && tag!==el.tagName) return false
|
|
304
304
|
const clas=el.attributes.class
|
|
305
305
|
if(classList!==undefined && (clas===undefined || clas==='')) return false
|
|
306
306
|
else if(classList!==undefined){
|
|
307
307
|
if(classList.every(e=>el.classList.contains(e))===false) return false
|
|
308
308
|
}
|
|
309
309
|
if(checkattributes(attributes,el)===false) return false
|
|
310
310
|
if(checkElement(el.prev,prev)===false) return false
|
|
311
311
|
if(checkAncestors(el.ancestors,ancestors)===false) return false
|
|
312
312
|
if(checkParents(el.ancestors,parents)===false) return false
|
|
313
313
|
if(el.parent){
|
|
314
314
|
if(checkPrevAny(el.parent.children,el.childIndex,prevAny)==false) return false
|
|
315
315
|
}
|
|
316
316
|
return true
|
|
317
317
|
}
|
|
318
318
|
function checkattributes(attributes=[],el){
|
|
319
319
|
let elattributes=el.attributes
|
|
320
320
|
let names=Object.keys(elattributes)
|
|
321
321
|
let passedTests=0
|
|
322
322
|
if(attributes) for(let i=0; i<attributes.length; i++){
|
|
323
323
|
let{name,value,check}=attributes[i]
|
|
324
324
|
if(name=='inner' && value!==undefined && check && el.inner){
|
|
325
325
|
if(check(el.inner)) passedTests++
|
|
326
326
|
}
|
|
327
327
|
if(!names.includes(name)) continue
|
|
328
328
|
else if(value==undefined) passedTests++
|
|
329
329
|
else if(value && elattributes[name]){
|
|
330
330
|
if(check(elattributes[name])==false) continue
|
|
331
331
|
else passedTests++
|
|
332
332
|
}
|
|
333
333
|
}
|
|
334
334
|
if(passedTests==attributes.length) return true
|
|
335
335
|
else return false
|
|
336
336
|
}
|
|
337
337
|
function checkPrevAny(children=[],index,prevAny){
|
|
338
338
|
let size=children.length
|
|
339
339
|
if((size==0 || index==0) && prevAny) return false
|
|
340
340
|
for(let i=index; i>=0; i--){
|
|
341
341
|
if(checkElement(children[i],prevAny)) return true
|
|
342
342
|
}
|
|
343
343
|
return false
|
|
344
344
|
}
|
|
345
345
|
function checkAncestors(ancestors=[],selectorAncestors=[]){
|
|
346
346
|
let count=0
|
|
347
347
|
if(selectorAncestors.length==0) return true
|
|
348
348
|
let endIndex=ancestors.length-1
|
|
349
349
|
let selectorIndex=selectorAncestors.length-1
|
|
350
350
|
while(selectorIndex>=0){
|
|
351
351
|
for(let i=endIndex; i>=0; i--){
|
|
352
352
|
endIndex=i-1
|
|
353
353
|
if(checkElement(ancestors[i],selectorAncestors[selectorIndex])==true){
|
|
354
354
|
count++
|
|
355
355
|
break
|
|
356
356
|
}
|
|
357
357
|
}
|
|
358
358
|
selectorIndex--
|
|
359
359
|
}
|
|
360
360
|
if(count==selectorAncestors.length) return true
|
|
361
361
|
else return false
|
|
@@ -21,14 +21,22 @@ function buildStyle(attributes){
|
|
|
21
21
|
const styles=attributes.style || "";
|
|
22
22
|
con
|
|
23
23
|
|
|
24
24
|
class NodeClassList{
|
|
25
25
|
constructor(node){ this.node=node }
|
|
26
26
|
get classes(){ return (this.node.attributes.class || "").split(" ").filter(Boolean) }
|
|
27
27
|
set classes(val){ this.node.attributes.class=val.join(" ") }
|
|
28
28
|
contains(className){ return this.classes.includes(className) }
|
|
29
29
|
add(className){
|
|
30
30
|
const currentClasses=this.classes;
|
|
31
31
|
if (!currentClasses.includes(className)) this.classes=[...currentClasses,className];
|
|
32
32
|
}
|
|
33
33
|
remove(className){ this.classes=this.classes.filter(cls=>cls!==className); }
|
|
34
34
|
toggle(className){
|
|
35
35
|
if (this.classes.includes(className)) this.remove(className);
|
|
36
36
|
else this.add(className);
|
|
37
37
|
}
|
|
38
38
|
replace(oldClass,newClass){
|
|
39
39
|
if (this.classes.includes(oldClass)){
|
|
40
40
|
this.remove(oldClass);
|
|
41
41
|
this.add(newClass);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
class Node{
|
|
46
45
|
constructor(tagName,attributes={},parent=null){
|
|
47
46
|
this.isSingle=false;
|
|
48
47
|
this.tagName=tagName;
|
|
49
48
|
this.attributes=attributes;
|
|
50
49
|
this.childNodes=[];
|
|
51
50
|
if (parent!==null) parent.childNodes.push(this)
|
|
52
51
|
this.parent=parent;
|
|
53
52
|
this._classList=null;
|
|
54
53
|
this.__style=null;
|
|
55
54
|
this._dataset=null
|
|
56
55
|
}
|
|
57
56
|
get id(){ return this.attributes.id || null; }
|
|
58
57
|
get className(){return this.attributes.class || null}
|
|
59
58
|
get parentNode(){ return this.parent }
|
|
60
59
|
get ancestors(){
|
|
61
60
|
const ancestors=[]
|
|
62
61
|
let element=this.parent
|
|
63
62
|
while (element.tagName!=='ROOT'){
|
|
64
63
|
ancestors.push(element)
|
|
65
64
|
element=element.parent
|
|
66
65
|
}
|
|
67
66
|
return ancestors.reverse()
|
|
68
67
|
}
|
|
69
68
|
get childIndex(){ return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null }
|
|
70
69
|
get previousElementSibling(){ return this.prev }
|
|
71
70
|
get prev(){
|
|
72
71
|
if (!this.childIndex) return null
|
|
73
72
|
return this.parent.childNodes[this.childIndex-1]
|
|
74
73
|
}
|
|
75
74
|
get nextElementSibling(){ return this.next }
|
|
76
75
|
get next(){
|
|
77
76
|
if (!this.childIndex) return null
|
|
78
77
|
return this.parent.childNodes[this.childIndex+1] || null
|
|
79
78
|
}
|
|
80
79
|
get dataset(){
|
|
81
80
|
if (!this._dataset) this._dataset=getDataset(this);
|
|
82
81
|
return this._dataset;
|
|
83
82
|
}
|
|
84
83
|
get classList(){
|
|
85
84
|
if (!this._classList) this._classList=new NodeClassList(this);
|
|
86
85
|
return this._classList;
|
|
87
86
|
}
|
|
88
87
|
get style(){
|
|
89
88
|
if (!this.__style) this.__style=buildStyle(this.attributes)
|
|
90
89
|
return this.__style
|
|
91
90
|
}
|
|
92
91
|
get outerHTML(){
|
|
93
92
|
const attrs=Object.entries(this.attributes).map(([key,val])=>`${key}="${val}"`).join(" ");
|
|
94
93
|
return `<${this.tagName} ${attrs}>${this.innerHTML}</${this.tagName}>`;
|
|
95
94
|
}
|
|
96
95
|
getAttribute(attrName){ return this.attributes[attrName] || null }
|
|
97
96
|
setAttribute(attrName,value){ this.attributes[attrName]=value }
|
|
98
97
|
removeAttribute(attrName){ delete this.attributes[attrName] }
|
|
99
98
|
remove(){
|
|
100
99
|
if (!this.parent) return
|
|
101
100
|
const index=this.childIndex;
|
|
102
101
|
if (index!==null) this.parent.childNodes.splice(index,1);
|
|
103
102
|
}
|
|
104
103
|
get innerHTML(){
|
|
105
104
|
return this.childNodes.map(child=>{
|
|
106
105
|
if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
|
|
107
106
|
else if (child instanceof TextNode) return child.textContent;
|
|
108
107
|
else return child
|
|
109
108
|
}).join("");
|
|
110
109
|
}
|
|
111
110
|
$$(query){return this.querySelectorAll(query)}
|
|
112
111
|
querySelectorAll(query){
|
|
113
112
|
const selectors=Query.get(query)
|
|
114
113
|
return find(selectors,this,new Set())
|
|
115
114
|
}
|
|
116
115
|
$(query){return this.querySelector(query)}
|
|
117
116
|
querySelector(query){
|
|
118
117
|
const selectors=Query.get(query)
|
|
119
118
|
return find(selectors,this,new Set(),true)[0]
|
|
120
119
|
}
|
|
121
120
|
getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
|
|
122
121
|
getElementsByTagName(query){ return this.querySelectorAll(query) }
|
|
123
122
|
getElementById(query){ return this.querySelector('#'+query) }
|
|
124
123
|
get children(){
|
|
125
124
|
return this.childNodes.filter(child=>{
|
|
126
125
|
if (!(child instanceof Node)) return false
|
|
127
126
|
if (child.tagName==='#comment') return false
|
|
128
127
|
return true
|
|
129
128
|
});
|
|
130
129
|
}
|
|
131
130
|
insertAdjacentElement(position,newElement){
|
|
132
131
|
if(newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
|
|
133
132
|
const pos=position.toLowerCase();
|
|
134
133
|
if (pos==="afterbegin") this.childNodes.unshift(newElement);
|
|
135
134
|
else if (pos==="beforeend") this.childNodes.push(newElement);
|
|
136
135
|
if (!this.parent) return newElement
|
|
137
136
|
if (pos==="beforebegin") this.parent.childNodes.unshift(newElement);
|
|
138
137
|
else if (pos==="afterend") this.parent.childNodes.splice(this.childIndex+1,0,newElement);
|
|
139
138
|
return newElement
|
|
140
139
|
}
|
|
141
140
|
insertAdjacentHTML(position,html){
|
|
142
141
|
const newNode=parseHTML(html);
|
|
143
142
|
return this.insertAdjacentElement(position,newNode);
|
|
144
143
|
}
|
|
145
144
|
insertAdjacentText(position,text){
|
|
146
145
|
return this.insertAdjacentElement(position,new TextNode(text));
|
|
147
146
|
}
|
|
148
147
|
set innerHTML(html){
|
|
149
148
|
const parsed=parseHTML(html);
|
|
150
149
|
this.childNodes=parsed.childNodes;
|
|
151
150
|
}
|
|
152
151
|
set outerHTML(html){
|
|
153
152
|
const parsed=parseHTML(html);
|
|
154
153
|
if (!this.parent) return console.log('element has no parent node')
|
|
155
154
|
const index=this.childIndex
|
|
156
155
|
if (index!==null) this.parent.childNodes.splice(index,1,...parsed.childNodes);
|
|
157
156
|
}
|
|
158
157
|
appendChild(newChild){
|
|
159
158
|
if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
|
|
160
159
|
if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
|
|
161
160
|
} else if(typeof newChild==='string') newChild=new TextNode(newChild)
|
|
162
161
|
else return newChild
|
|
163
162
|
this.childNodes.push(newChild);
|
|
164
163
|
newChild.parent=this;
|
|
165
164
|
return newChild;
|
|
166
165
|
}
|
|
167
166
|
get textContent(){
|
|
168
167
|
if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
|
|
169
168
|
return this.childNodes.map(child=>{
|
|
170
169
|
if(child instanceof SingleNode) return ''
|
|
171
170
|
if(child instanceof TextNode) return child.nodeValue
|
|
172
171
|
if(child instanceof Node) return child.textContent;
|
|
173
172
|
else return child;
|
|
174
173
|
}).join(" ");
|
|
175
174
|
}
|
|
176
175
|
set textContent(value){
|
|
177
176
|
this.childNodes=[];
|
|
178
177
|
if (value!==null && value!==undefined){
|
|
179
178
|
this.childNodes.push(value.toString());
|
|
180
179
|
}
|
|
181
180
|
}
|
|
181
|
+
function insertBefore(arr,index,newItem){
|
|
182
182
|
const existingIndex=arr.indexOf(newItem);
|
|
183
183
|
if (existingIndex!==-1) arr.splice(existingIndex,1);
|
|
184
184
|
arr.splice(index,0,newItem);
|
|
185
|
+
}
|
|
185
186
|
class Node{
|
|
186
187
|
constructor(tagName,attributes={},parent=null){
|
|
187
188
|
this.isSingle=false;
|
|
188
189
|
this.tagName=tagName;
|
|
189
190
|
this.attributes=attributes;
|
|
190
191
|
this.childNodes=[];
|
|
191
192
|
if (parent!==null) parent.childNodes.push(this)
|
|
192
193
|
this.parent=parent;
|
|
193
194
|
this._classList=null;
|
|
194
195
|
this.__style=null;
|
|
195
196
|
this._dataset=null
|
|
196
197
|
}
|
|
197
198
|
get id(){ return this.attributes.id ? this.attributes.id : null; }
|
|
198
199
|
set id(newValue){ this.attributes.id=newValue; }
|
|
199
200
|
get className(){return this.attributes.class || null}
|
|
200
201
|
get parentNode(){ return this.parent }
|
|
201
202
|
get ancestors(){
|
|
202
203
|
if(!this.parent) return []
|
|
203
204
|
const ancestors=[]
|
|
204
205
|
let element=this.parent
|
|
205
206
|
while (element.tagName!=='ROOT'){
|
|
206
207
|
ancestors.push(element)
|
|
207
208
|
element=element.parent
|
|
208
209
|
}
|
|
209
210
|
return ancestors.reverse()
|
|
210
211
|
}
|
|
211
212
|
get childNodeIndex(){
|
|
212
213
|
if(!this.parent) return null
|
|
213
214
|
return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
|
|
214
215
|
}
|
|
215
216
|
get childIndex(){
|
|
216
217
|
if(!this.parent) return null
|
|
217
218
|
return this.parent.children ? this.parent.children.indexOf(this) : null
|
|
218
219
|
}
|
|
219
220
|
get previousElementSibling(){ return this.prev }
|
|
220
221
|
get prev(){
|
|
221
222
|
if (!this.childIndex) return null
|
|
222
223
|
return this.parent.children[this.childIndex-1]
|
|
223
224
|
}
|
|
224
225
|
get nextElementSibling(){ return this.next }
|
|
225
226
|
get next(){
|
|
226
227
|
if (!this.childIndex) return null
|
|
227
228
|
return this.parent.children[this.childIndex+1] || null
|
|
228
229
|
}
|
|
229
230
|
get dataset(){
|
|
230
231
|
if (!this._dataset) this._dataset=getDataset(this);
|
|
231
232
|
return this._dataset;
|
|
232
233
|
}
|
|
233
234
|
get classList(){
|
|
234
235
|
if (!this._classList) this._classList=new NodeClassList(this);
|
|
235
236
|
return this._classList;
|
|
236
237
|
}
|
|
237
238
|
get style(){
|
|
238
239
|
if (!this.__style) this.__style=buildStyle(this.attributes)
|
|
239
240
|
return this.__style
|
|
240
241
|
}
|
|
241
242
|
get outerHTML(){
|
|
242
243
|
const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
|
|
243
244
|
return `<${this.tagName}${attrs ? ' '+attrs : ''}>${this.innerHTML}</${this.tagName}>`;
|
|
244
245
|
}
|
|
245
246
|
getAttribute(attrName){ return this.attributes[attrName] || null }
|
|
246
247
|
setAttribute(attrName,value){ this.attributes[attrName]=value }
|
|
247
248
|
removeAttribute(attrName){ delete this.attributes[attrName] }
|
|
248
249
|
remove(){
|
|
249
250
|
if (!this.parent) return
|
|
250
251
|
const index=this.childNodeIndex;
|
|
251
252
|
if (index!==null) this.parent.childNodes.splice(index,1);
|
|
252
253
|
}
|
|
253
254
|
get innerHTML(){
|
|
254
255
|
return this.childNodes.map(child=>{
|
|
255
256
|
if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
|
|
256
257
|
else if (child instanceof TextNode) return child.textContent;
|
|
257
258
|
else return child
|
|
258
259
|
}).join("");
|
|
259
260
|
}
|
|
260
261
|
$$(query){return this.querySelectorAll(query)}
|
|
261
262
|
querySelectorAll(query){
|
|
262
263
|
const selectors=Query.get(query)
|
|
263
264
|
return find(selectors,this,new Set())
|
|
264
265
|
}
|
|
265
266
|
$(query){return this.querySelector(query)}
|
|
266
267
|
querySelector(query){
|
|
267
268
|
const selectors=Query.get(query)
|
|
268
269
|
return find(selectors,this,new Set(),true)[0] || null
|
|
269
270
|
}
|
|
270
271
|
getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
|
|
271
272
|
getElementsByTagName(query){ return this.querySelectorAll(query) }
|
|
272
273
|
getElementById(query){ return this.querySelector('#'+query) }
|
|
273
274
|
get children(){
|
|
274
275
|
return this.childNodes.filter(child=>{
|
|
275
276
|
if (!(child instanceof Node)) return false
|
|
276
277
|
if (child.tagName==='#comment') return false
|
|
277
278
|
return true
|
|
278
279
|
});
|
|
279
280
|
}
|
|
280
281
|
insertAdjacentElement(position,newElement){
|
|
281
282
|
if(newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
|
|
282
283
|
const pos=position.toLowerCase();
|
|
283
284
|
if(pos==='afterbegin' || pos==='beforeend'){
|
|
284
285
|
if (pos==="afterbegin") this.childNodes.unshift(newElement);
|
|
285
286
|
else if (pos==="beforeend") this.childNodes.push(newElement);
|
|
286
287
|
newElement.parent=this
|
|
287
288
|
return newElement
|
|
288
289
|
}
|
|
289
290
|
if (!this.parent) throw new Error("Can't insert element to element without parent")
|
|
290
291
|
if (pos==="beforebegin") insertBefore(this.parent.childNodes,this.childNodeIndex,newElement)
|
|
291
292
|
else if (pos==="afterend") this.parent.childNodes.splice(this.childNodeIndex+1,0,newElement);
|
|
292
293
|
newElement.parent=this.parent
|
|
293
294
|
return newElement
|
|
294
295
|
}
|
|
295
296
|
insertAdjacentHTML(position,html){
|
|
296
297
|
const newNode=parseHTML(html);
|
|
297
298
|
newNode.childNodes.reverse().forEach(node=>{
|
|
298
299
|
this.insertAdjacentElement(position,node);
|
|
299
300
|
});
|
|
300
301
|
return newNode
|
|
301
302
|
}
|
|
302
303
|
insertAdjacentText(position,text){
|
|
303
304
|
return this.insertAdjacentElement(position,new TextNode(text));
|
|
304
305
|
}
|
|
305
306
|
insert(position,element){
|
|
306
307
|
const positions=['beforebegin','afterbegin','beforeend','afterend']
|
|
307
308
|
if(positions[position]) position=positions[position]
|
|
308
309
|
if(typeof element==='string'){
|
|
309
310
|
element=element.trim()
|
|
310
311
|
if(element.startsWith('<') && element.endsWith('>')){
|
|
311
312
|
return this.insertAdjacentHTML(position,element)
|
|
312
313
|
}
|
|
313
314
|
return this.insertAdjacentText(position,element)
|
|
314
315
|
}
|
|
315
316
|
return this.insertAdjacentElement(position,element)
|
|
316
317
|
}
|
|
317
318
|
set innerHTML(html){
|
|
318
319
|
const parsed=parseHTML(html);
|
|
319
320
|
this.childNodes=parsed.childNodes;
|
|
320
321
|
}
|
|
321
322
|
set outerHTML(html){
|
|
322
323
|
const parsed=parseHTML(html);
|
|
323
324
|
if (!this.parent) return console.log('element has no parent node')
|
|
324
325
|
const index=this.childIndex
|
|
325
326
|
if (index!==null) this.parent.childNodes.splice(index,1,...parsed.childNodes);
|
|
326
327
|
}
|
|
327
328
|
appendChild(newChild){
|
|
328
329
|
if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
|
|
329
330
|
if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
|
|
330
331
|
} else if(typeof newChild==='string') newChild=new TextNode(newChild)
|
|
331
332
|
else return newChild
|
|
332
333
|
this.childNodes.push(newChild);
|
|
333
334
|
newChild.parent=this;
|
|
334
335
|
return newChild;
|
|
335
336
|
}
|
|
336
337
|
get textContent(){
|
|
337
338
|
if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
|
|
338
339
|
return this.childNodes.map(child=>{
|
|
339
340
|
if(child instanceof SingleNode) return ''
|
|
340
341
|
if(child instanceof TextNode) return child.nodeValue
|
|
341
342
|
if(child instanceof Node) return child.textContent;
|
|
342
343
|
else return child;
|
|
343
344
|
}).join(" ");
|
|
344
345
|
}
|
|
345
346
|
set textContent(value){
|
|
346
347
|
this.childNodes=[];
|
|
347
348
|
if (value!==null && value!==undefined){
|
|
348
349
|
this.childNodes.push(value.toString());
|
|
349
350
|
}
|
|
350
351
|
}
|
|
351
352
|
}
|
|
352
|
-
class SingleNode extends Node{
|
|
353
353
|
constructor(tagName,attributes={},parent=null){
|
|
354
354
|
if(attributes['?'] && tagName==='?xml') delete attributes['?']
|
|
355
355
|
super(tagName,attributes,parent);
|
|
356
356
|
this.isSingle=true
|
|
357
357
|
}
|
|
358
358
|
get outerHTML(){
|
|
359
359
|
if (this.tagName==="#cdata-section") return `<![CDATA[${this.textContent}]]>`;
|
|
360
360
|
const attrs=Object.entries(this.attributes).map(([key,val])=>`${key}="${val}"`).join(" ");
|
|
361
361
|
return `<${this.tagName} ${attrs}${this.tagName==='?xml' ? '?' : ''}>`;
|
|
362
362
|
}
|
|
363
363
|
get innerHTML(){ return ""; }
|
|
364
364
|
set innerHTML(_){ }
|
|
365
365
|
$(_){return null}
|
|
366
366
|
$$(_){return []}
|
|
367
367
|
querySelectorAll(_){ return []; }
|
|
368
368
|
querySelector(_){ return null; }
|
|
369
369
|
getElementsByClassName(_){ return []; }
|
|
370
370
|
getElementsByTagName(_){ return []; }
|
|
371
371
|
getElementById(_){ return null; }
|
|
372
372
|
get children(){ return []; }
|
|
373
373
|
insertAdjacentElement(_,__){ }
|
|
374
374
|
insertAdjacentHTML(_,__){ }
|
|
375
375
|
insertAdjacentText(_,__){ }
|
|
376
376
|
appendChild(_){ }
|
|
377
377
|
get textContent(){ return ""; }
|
|
378
378
|
set textContent(_){ }
|
|
379
|
+
class SingleNode extends Node{
|
|
379
380
|
constructor(tagName,attributes={},parent=null){
|
|
380
381
|
if(attributes['?'] && tagName==='?xml') delete attributes['?']
|
|
381
382
|
super(tagName,attributes,parent);
|
|
382
383
|
this.isSingle=true
|
|
383
384
|
}
|
|
384
385
|
get outerHTML(){
|
|
385
386
|
if (this.tagName==="#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
|
|
386
387
|
const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
|
|
387
388
|
return `<${this.tagName} ${attrs}${this.tagName==='?xml' ? '?' : ''}>`;
|
|
388
389
|
}
|
|
389
390
|
get innerHTML(){ return ""; }
|
|
390
391
|
set innerHTML(_){ }
|
|
391
392
|
$(_){return null}
|
|
392
393
|
$$(_){return []}
|
|
393
394
|
querySelectorAll(_){ return []; }
|
|
394
395
|
querySelector(_){ return null; }
|
|
395
396
|
getElementsByClassName(_){ return []; }
|
|
396
397
|
getElementsByTagName(_){ return []; }
|
|
397
398
|
getElementById(_){ return null; }
|
|
398
399
|
get children(){ return []; }
|
|
399
400
|
insertAdjacentElement(_,__){ }
|
|
400
401
|
insertAdjacentHTML(_,__){ }
|
|
401
402
|
insertAdjacentText(_,__){ }
|
|
402
403
|
appendChild(_){ }
|
|
403
404
|
insert(_,__){ }
|
|
404
405
|
get textContent(){ return ""; }
|
|
405
406
|
set textContent(_){ }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
class Root extends Node{
|
|
406
410
|
constructor(){
|
|
407
411
|
super('ROOT',{},null);
|
|
408
412
|
this.isSingle=false
|
|
409
413
|
}
|
|
410
414
|
get body(){return this.$('body')}
|
|
411
415
|
get head(){return this.$('head')}
|
|
412
416
|
get title(){return this.$('title')}
|
|
413
417
|
set title(title){return this.$('title').innerHTML=title}
|
|
414
418
|
}
|
|
415
|
-
function parseAttributes(str){
|
|
416
419
|
const attrs={};
|
|
417
420
|
let key="";
|
|
418
421
|
let value="";
|
|
419
422
|
let isKey=true;
|
|
420
423
|
let quoteChar=null;
|
|
421
424
|
for (let i=0; i< str.length; i++){
|
|
422
425
|
const char=str[i];
|
|
423
426
|
if (isKey && (char==='=' || char===' ')){
|
|
424
427
|
if (char==='=') isKey=false;
|
|
425
428
|
else if (key.trim()){
|
|
426
429
|
attrs[key.trim()]=true;
|
|
427
430
|
key="";
|
|
428
431
|
}
|
|
429
432
|
continue;
|
|
430
433
|
}
|
|
431
434
|
if (!quoteChar && (char==='"' || char==="'")){
|
|
432
435
|
quoteChar=char;
|
|
433
436
|
continue;
|
|
434
437
|
} else if (quoteChar && char===quoteChar){
|
|
435
438
|
quoteChar=null;
|
|
436
439
|
attrs[key.trim()]=value.trim();
|
|
437
440
|
key=""; value=""; isKey=true;
|
|
438
441
|
continue;
|
|
439
442
|
}
|
|
440
443
|
if (isKey) key+=char;
|
|
441
444
|
else value+=char;
|
|
442
445
|
}
|
|
443
446
|
if (key.trim() &&!value) attrs[key.trim()]=true;
|
|
444
447
|
return attrs;
|
|
448
|
+
function parseAttributes(str){
|
|
445
449
|
const attrs={};
|
|
446
450
|
let key="";
|
|
447
451
|
let value="";
|
|
448
452
|
let isKey=true;
|
|
449
453
|
let quoteChar=null;
|
|
450
454
|
for (let i=0; i< str.length; i++){
|
|
451
455
|
const char=str[i];
|
|
452
456
|
if (isKey && (char==='=' || char===' ')){
|
|
453
457
|
if (char==='=') isKey=false;
|
|
454
458
|
else if (key.trim()){
|
|
455
459
|
attrs[key.trim()]=true;
|
|
456
460
|
key="";
|
|
457
461
|
}
|
|
458
462
|
continue;
|
|
459
463
|
}
|
|
460
464
|
if (!quoteChar && (char==='"' || char==="'")){
|
|
461
465
|
quoteChar=char;
|
|
462
466
|
continue;
|
|
463
467
|
} else if (quoteChar && char===quoteChar){
|
|
464
468
|
quoteChar=null;
|
|
465
469
|
attrs[key.trim()]=value.trim();
|
|
466
470
|
key=""; value=""; isKey=true;
|
|
467
471
|
continue;
|
|
468
472
|
}
|
|
469
473
|
if (isKey) key+=char;
|
|
470
474
|
else value+=char;
|
|
471
475
|
}
|
|
472
476
|
if (key.trim() &&!value) attrs[key.trim()]='';
|
|
473
477
|
return attrs;
|
|
474
478
|
}
|
|
475
479
|
const VOID_TAGS=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","!doctype",'?xml']);
|
|
476
|
-
function parseHTML(html){
|
|
477
480
|
const root=new Node("ROOT");
|
|
478
481
|
const stack=[root];
|
|
479
482
|
let currentText="",i=0;
|
|
480
483
|
function parseSpecial(startStr,endStr,n1,n2,tag){
|
|
481
484
|
if (!html.startsWith(startStr,i)) return false
|
|
482
485
|
const end=html.indexOf(endStr,i+n1);
|
|
483
486
|
const strNode=new Node(tag,{},stack[stack.length-1]);
|
|
484
487
|
strNode.childNodes.push(html.substring(i+n1,end));
|
|
485
488
|
i=end+n2;
|
|
486
489
|
return true
|
|
487
490
|
}
|
|
488
491
|
while (i< html.length){
|
|
489
492
|
if (parseSpecial("<!--","-->",4,3,'#comment')) continue
|
|
490
493
|
if (parseSpecial("<script","</script>",8,9,'script')) continue
|
|
491
494
|
if (parseSpecial("<style","</style>",7,8,'style')) continue
|
|
492
495
|
if (html.startsWith("<![CDATA[",i)){
|
|
493
496
|
const end=html.indexOf("]]>",i+9);
|
|
494
497
|
if (end===-1) break;
|
|
495
498
|
const content=html.substring(i+9,end);
|
|
496
499
|
const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
|
|
497
500
|
cdataNode.nodeValue=content;
|
|
498
501
|
i=end+3;
|
|
499
502
|
continue;
|
|
500
503
|
}
|
|
501
504
|
if (html.startsWith("<",i)){
|
|
502
505
|
if (currentText.trim()){
|
|
503
506
|
stack[stack.length-1].childNodes.push(new TextNode(currentText.trim()));
|
|
504
507
|
currentText="";
|
|
505
508
|
}
|
|
506
509
|
let tagEnd=i+1;
|
|
507
510
|
let insideQuotes=false;
|
|
508
511
|
let quoteChar=null;
|
|
509
512
|
while (tagEnd< html.length){
|
|
510
513
|
const char=html[tagEnd];
|
|
511
514
|
if (!insideQuotes && (char==='"' || char==="'")){
|
|
512
515
|
insideQuotes=true;
|
|
513
516
|
quoteChar=char;
|
|
514
517
|
} else if (insideQuotes && char===quoteChar){
|
|
515
518
|
insideQuotes=false;
|
|
516
519
|
quoteChar=null;
|
|
517
520
|
}
|
|
518
521
|
if (!insideQuotes && char==='>') break;
|
|
519
522
|
tagEnd++;
|
|
520
523
|
}
|
|
521
524
|
const tagContent=html.substring(i+1,tagEnd);
|
|
522
525
|
if (tagContent.startsWith("/")) stack.pop();
|
|
523
526
|
else{
|
|
524
527
|
let isSelfClosing=tagContent.endsWith('/');
|
|
525
528
|
const tagNameEnd=tagContent.search(/\s|>|\//);
|
|
526
529
|
const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
|
|
527
530
|
const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
|
|
528
531
|
const attributes=parseAttributes(attributesString);
|
|
529
532
|
if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
|
|
530
533
|
else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
|
|
531
534
|
}
|
|
532
535
|
i=tagEnd+1;
|
|
533
536
|
} else{
|
|
534
537
|
currentText+=html[i];
|
|
535
538
|
i++;
|
|
536
539
|
}
|
|
537
540
|
}
|
|
538
541
|
if (currentText.trim()) stack[stack.length-1].childNodes.push(new TextNode(currentText.trim()));
|
|
539
542
|
return root;
|
|
543
|
+
function parseHTML(html){
|
|
540
544
|
const root=new Root();
|
|
541
545
|
const stack=[root];
|
|
542
546
|
let currentText="",i=0;
|
|
543
547
|
let max=0
|
|
544
548
|
function parseScript(){
|
|
545
549
|
if (!html.startsWith("<script",i)) return false;
|
|
546
550
|
const openTagEnd=html.indexOf(">",i);
|
|
547
551
|
if (openTagEnd===-1) return false;
|
|
548
552
|
const attributesString=html.substring(i+7,openTagEnd).trim();
|
|
549
553
|
const attributes=parseAttributes(attributesString);
|
|
550
554
|
let closeTagStart=html.indexOf("</script>",openTagEnd);
|
|
551
555
|
if (closeTagStart===-1) return false;
|
|
552
556
|
const content=html.substring(openTagEnd+1,closeTagStart);
|
|
553
557
|
const scriptNode=new Node('script',attributes,stack[stack.length-1]);
|
|
554
558
|
if(content.length>0) scriptNode.childNodes.push(content);
|
|
555
559
|
i=closeTagStart+9;
|
|
556
560
|
return true;
|
|
557
561
|
}
|
|
558
562
|
function parseSpecial(startStr,endStr,n1,n2,tag){
|
|
559
563
|
if (!html.startsWith(startStr,i)) return false
|
|
560
564
|
const end=html.indexOf(endStr,i+n1);
|
|
561
565
|
const strNode=new Node(tag,{},stack[stack.length-1]);
|
|
562
566
|
strNode.childNodes.push(html.substring(i+n1,end));
|
|
563
567
|
i=end+n2;
|
|
564
568
|
return true
|
|
565
569
|
}
|
|
566
570
|
while (i< html.length){
|
|
567
571
|
if(i>=max) max=i;
|
|
568
572
|
else break;
|
|
569
573
|
if (parseScript()) continue
|
|
570
574
|
if (parseSpecial("<!--","-->",4,3,'#comment')) continue
|
|
571
575
|
if (parseSpecial("<style","</style>",7,8,'style')) continue
|
|
572
576
|
if (html.startsWith("<![CDATA[",i)){
|
|
573
577
|
const end=html.indexOf("]]>",i+9);
|
|
574
578
|
if (end===-1) break;
|
|
575
579
|
const content=html.substring(i+9,end);
|
|
576
580
|
const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
|
|
577
581
|
cdataNode.nodeValue=content;
|
|
578
582
|
i=end+3;
|
|
579
583
|
continue;
|
|
580
584
|
}
|
|
581
585
|
if (html.startsWith("<",i)){
|
|
582
586
|
if (currentText && stack[stack.length-1]){
|
|
583
587
|
const textNode=new TextNode(currentText)
|
|
584
588
|
stack[stack.length-1].childNodes.push(textNode);
|
|
585
589
|
textNode.parent=stack[stack.length-1]
|
|
586
590
|
currentText="";
|
|
587
591
|
}
|
|
588
592
|
let tagEnd=i+1;
|
|
589
593
|
let insideQuotes=false;
|
|
590
594
|
let quoteChar=null;
|
|
591
595
|
while (tagEnd< html.length){
|
|
592
596
|
const char=html[tagEnd];
|
|
593
597
|
if (!insideQuotes && (char==='"' || char==="'")){
|
|
594
598
|
insideQuotes=true;
|
|
595
599
|
quoteChar=char;
|
|
596
600
|
} else if (insideQuotes && char===quoteChar){
|
|
597
601
|
insideQuotes=false;
|
|
598
602
|
quoteChar=null;
|
|
599
603
|
}
|
|
600
604
|
if (!insideQuotes && char==='>') break;
|
|
601
605
|
tagEnd++;
|
|
602
606
|
}
|
|
603
607
|
const tagContent=html.substring(i+1,tagEnd);
|
|
604
608
|
if (tagContent.startsWith("/")) stack.pop();
|
|
605
609
|
else{
|
|
606
610
|
let isSelfClosing=tagContent.endsWith('/');
|
|
607
611
|
const tagNameEnd=tagContent.search(/\s|>|\//);
|
|
608
612
|
const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
|
|
609
613
|
const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
|
|
610
614
|
const attributes=parseAttributes(attributesString);
|
|
611
615
|
if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
|
|
612
616
|
else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
|
|
613
617
|
}
|
|
614
618
|
i=tagEnd+1;
|
|
615
619
|
} else{
|
|
616
620
|
currentText+=html[i];
|
|
617
621
|
i++;
|
|
618
622
|
}
|
|
619
623
|
}
|
|
620
624
|
if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
|
|
621
625
|
return root;
|
|
626
|
+
}
|
|
627
|
+
function buildFromCache(cached){
|
|
622
628
|
function buildNode(cache,parent=null){
|
|
623
629
|
if(typeof cache==='string') return parent.childNodes.push(cache)
|
|
624
630
|
const{isSingle,tagName,attributes,childNodes,textContent}=cache
|
|
625
631
|
if(textContent) return parent.childNodes.push(new TextNode(textContent))
|
|
626
632
|
if(isSingle) return parent.childNodes.push(new SingleNode(tagName,attributes))
|
|
627
633
|
const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
|
|
628
634
|
childNodes.forEach(childNode=>{
|
|
629
635
|
buildNode(childNode,newDoc)
|
|
630
636
|
});
|
|
631
637
|
return newDoc
|
|
632
638
|
}
|
|
633
639
|
return buildNode(cached)
|
|
640
|
+
}
|
|
634
641
|
|
|
642
|
+
function cacheDoc(doc){
|
|
635
643
|
const props=['isSingle','tagName','attributes']
|
|
636
644
|
function addToCache(element,cache={}){
|
|
637
645
|
if(typeof element==='string') return element
|
|
638
646
|
if(element.nodeName==='#text') return{textContent:element.textContent}
|
|
639
647
|
props.forEach(prop=>{
|
|
640
648
|
if(element[prop]) cache[prop]=element[prop]
|
|
641
649
|
});
|
|
642
650
|
if(!element.childNodes) return cache
|
|
643
651
|
cache.childNodes=[]
|
|
644
652
|
element.childNodes.forEach(childNode=>{
|
|
645
653
|
cache.childNodes.push(addToCache(childNode))
|
|
646
654
|
});
|
|
647
655
|
return cache
|
|
648
656
|
}
|
|
649
657
|
return addToCache(doc)
|
|
650
658
|
}
|
|
651
|
-
return { parseHTML, Node, Query, TextNode, SingleNode }
|
|
659
|
+
return { parseHTML, Node, Query, TextNode, SingleNode, buildFromCache, cacheDoc, Root }
|
|
652
660
|
})()
|