als-document 1.4.1 → 1.4.3

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