als-document 1.4.0 → 1.4.1

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