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