als-document 1.3.1 → 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){
544
732
  this.childNodes=[...buildFromCache(cacheDoc(html)).childNodes]
545
733
  }
546
734
  else if(typeof html==='string') this.innerHTML=html
547
735
  else this.body
548
736
  }
549
737
  get documentElement(){return this.html}
550
738
  get html(){
551
739
  const html=this.$('html')
552
740
  if(html===null) this.innerHTML=/*html*/`<html lang="en"><head><meta charset="UTF-8"><title></title></head><body></body></html>`
553
741
  return this.$('html')
554
742
  }
555
743
  get innerHTML(){
556
744
  const inner=super.innerHTML
557
745
  return '<!DOCTYPE html>'+inner
558
746
  }
559
747
  set innerHTML(html){return super.innerHTML=html}
560
748
  get head(){
561
749
  const head=this.$('head')
562
750
  if(head===null) this.html.insert(1,'<head></head>')
563
751
  return this.$('head')
564
752
  }
565
753
  get body(){
566
754
  const body=this.$('body')
567
755
  if(body===null) this.head.insert(3,'<body></body>')
568
756
  return this.$('body')
569
757
  }
570
758
  get title(){
571
759
  const title=this.$('title')
572
760
  if(title===null) this.head.insert(1,'<title></title>')
573
761
  return this.$('title').innerText
574
762
  }
575
763
  get charset(){return this.$('meta[charset]')}
576
764
  set title(title){return this.$('title').innerText=title}
577
765
  get clone(){return new Document(this)}
578
- }
579
- function parseAttributes(str){
580
766
  const attrs={};
581
767
  let key="";
582
768
  let value="";
583
769
  let isKey=true;
584
770
  let quoteChar=null;
585
771
  for (let i=0; i< str.length; i++){
586
772
  const char=str[i];
587
773
  if (isKey && (char==='=' || char===' ')){
588
774
  if (char==='=') isKey=false;
589
775
  else if (key.trim()){
590
776
  attrs[key.trim()]=true;
591
777
  key="";
592
778
  }
593
779
  continue;
594
780
  }
595
781
  if (!quoteChar && (char==='"' || char==="'")){
596
782
  quoteChar=char;
597
783
  continue;
598
784
  } else if (quoteChar && char===quoteChar){
599
785
  quoteChar=null;
600
786
  attrs[key.trim()]=value.trim();
601
787
  key=""; value=""; isKey=true;
602
788
  continue;
603
789
  }
604
790
  if (isKey) key+=char;
605
791
  else value+=char;
606
792
  }
607
793
  if (key.trim() &&!value) attrs[key.trim()]='';
608
794
  return attrs;
609
- }
610
- const VOID_TAGS=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","!doctype",'?xml']);
611
- function parseHTML(html){
612
795
  const root=new Root();
613
796
  const stack=[root];
614
797
  let currentText="",i=0;
615
798
  let max=0
616
799
  function parseScript(){
617
800
  if (!html.startsWith("<script",i)) return false;
618
801
  const openTagEnd=html.indexOf(">",i);
619
802
  if (openTagEnd===-1) return false;
620
803
  const attributesString=html.substring(i+7,openTagEnd).trim();
621
804
  const attributes=parseAttributes(attributesString);
622
805
  let closeTagStart=html.indexOf("</script>",openTagEnd);
623
806
  if (closeTagStart===-1) return false;
624
807
  const content=html.substring(openTagEnd+1,closeTagStart);
625
808
  const scriptNode=new Node('script',attributes,stack[stack.length-1]);
626
809
  if(content.length>0) scriptNode.childNodes.push(content);
627
810
  i=closeTagStart+9;
628
811
  return true;
629
812
  }
630
813
  function parseSpecial(startStr,endStr,n1,n2,tag){
631
814
  if (!html.startsWith(startStr,i)) return false
632
815
  const end=html.indexOf(endStr,i+n1);
633
816
  const strNode=new Node(tag,{},stack[stack.length-1]);
634
817
  strNode.childNodes.push(html.substring(i+n1,end));
635
818
  i=end+n2;
636
819
  return true
637
820
  }
638
821
  while (i< html.length){
639
822
  if(i>=max) max=i;
640
823
  else break;
641
824
  if (parseScript()) continue
642
825
  if (parseSpecial("<!--","-->",4,3,'#comment')) continue
643
826
  if (parseSpecial("<style","</style>",7,8,'style')) continue
644
827
  if (html.startsWith("<![CDATA[",i)){
645
828
  const end=html.indexOf("]]>",i+9);
646
829
  if (end===-1) break;
647
830
  const content=html.substring(i+9,end);
648
831
  const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
649
832
  cdataNode.nodeValue=content;
650
833
  i=end+3;
651
834
  continue;
652
835
  }
653
836
  if (html.startsWith("<",i)){
654
837
  if (currentText && stack[stack.length-1]){
655
838
  const textNode=new TextNode(currentText)
656
839
  stack[stack.length-1].childNodes.push(textNode);
657
840
  textNode.parent=stack[stack.length-1]
658
841
  currentText="";
659
842
  }
660
843
  let tagEnd=i+1;
661
844
  let insideQuotes=false;
662
845
  let quoteChar=null;
663
846
  while (tagEnd< html.length){
664
847
  const char=html[tagEnd];
665
848
  if (!insideQuotes && (char==='"' || char==="'")){
666
849
  insideQuotes=true;
667
850
  quoteChar=char;
668
851
  } else if (insideQuotes && char===quoteChar){
669
852
  insideQuotes=false;
670
853
  quoteChar=null;
671
854
  }
672
855
  if (!insideQuotes && char==='>') break;
673
856
  tagEnd++;
674
857
  }
675
858
  const tagContent=html.substring(i+1,tagEnd);
676
859
  if (tagContent.startsWith("/")) stack.pop();
677
860
  else{
678
861
  let isSelfClosing=tagContent.endsWith('/');
679
862
  const tagNameEnd=tagContent.search(/\s|>|\//);
680
863
  const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
681
864
  const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
682
865
  const attributes=parseAttributes(attributesString);
683
866
  if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
684
867
  else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
685
868
  }
686
869
  i=tagEnd+1;
687
870
  } else{
688
871
  currentText+=html[i];
689
872
  i++;
690
873
  }
691
874
  }
692
875
  if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
693
876
  return root;
694
- }
695
- function buildFromCache(cached){
696
877
  function buildNode(cache,parent=null){
697
878
  if(typeof cache==='string') return parent.childNodes.push(cache)
698
879
  const{isSingle,tagName,attributes,childNodes,textContent}=cache
699
880
  if(textContent) return parent.childNodes.push(new TextNode(textContent))
700
881
  if(isSingle && parent) return parent.childNodes.push(new SingleNode(tagName,attributes))
701
882
  const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
702
883
  childNodes.forEach(childNode=>{
703
884
  buildNode(childNode,newDoc)
704
885
  });
705
886
  return newDoc
706
887
  }
707
888
  return buildNode(cached)
708
- }
709
889
 
710
- function cacheDoc(doc){
711
890
  const props=['isSingle','tagName','attributes']
712
891
  function addToCache(element,cache={}){
713
892
  if(typeof element==='string') return element
714
893
  if(element.nodeName==='#text') return{textContent:element.textContent}
715
894
  props.forEach(prop=>{
716
895
  if(element[prop]){
717
896
  cache[prop]=typeof element[prop]==='object' ?{...element[prop]} : element[prop]
718
897
  }
719
898
  });
720
899
  if(!element.childNodes) return cache
721
900
  cache.childNodes=[]
722
901
  element.childNodes.forEach(childNode=>{
723
902
  cache.childNodes.push(addToCache(childNode))
724
903
  });
725
904
  return cache
726
905
  }
727
906
  return addToCache(doc)
728
- }
729
- return { parseHTML, Node, Query, TextNode, SingleNode, buildFromCache, cacheDoc, Root, Document }
907
+ function getDataset(element){
908
+ return new Proxy(element.attributes,{
909
+ get: (target,prop)=>{return target[getDataName(prop)]},set: (target,prop,value)=>{target[getDataName(prop)]=value; return true},deleteProperty: (target,prop)=>{
910
+ const dataAttr=getDataName(prop)
911
+ if (dataAttr in target){
912
+ delete target[dataAttr];
913
+ return true;
914
+ }
915
+ return false;
916
+ }
917
+ });
918
+ }
919
+
920
+ function find(selectors,element,collection,first=false,firstTime=true){
921
+ if(!firstTime)
922
+ for(let selector of selectors){
923
+ if(checkElement(element,selector)) collection.add(element)
924
+ }
925
+ if(element.children)
926
+ element.children.forEach(child=>{
927
+ if(first && collection.size>0) return
928
+ find(selectors,child,collection,first,false)
929
+ })
930
+ return firstTime ? [...collection] : collection
931
+ }
932
+ class TextNode{
933
+ constructor(data){
934
+ this.nodeName='#text';
935
+ this.parent=null;
936
+ this.textContent=data;
937
+ }
938
+ get nodeValue(){return this.textContent}
939
+ get parentNode(){return this.parent}
940
+ }
941
+
942
+ function buildStyle(attributes){
943
+ const styles=attributes.style || "";
944
+ const camelToKebab=str=>str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,'$1-$2').toLowerCase();
945
+ const kebabToCamel=str=>str.replace(/-([a-z])/g,g=>g[1].toUpperCase());
946
+ const baseStyleObj=styles.split(";").reduce((acc,style)=>{
947
+ const [key,value]=style.split(":").map(s=>s.trim());
948
+ if (key && value) acc[kebabToCamel(key)]=value;
949
+ return acc;
950
+ },{});
951
+ return new Proxy(baseStyleObj,{
952
+ get: (obj,prop)=>obj[camelToKebab(prop)] || obj[prop],set: (obj,prop,value)=>{
953
+ obj[camelToKebab(prop)]=value;
954
+ attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
955
+ return true;
956
+ },deleteProperty: (obj,prop)=>{
957
+ delete obj[camelToKebab(prop)];
958
+ attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
959
+ return true;
960
+ }
961
+ });
962
+ }
963
+
964
+ class NodeClassList{
965
+ constructor(node){ this.node=node }
966
+ get classes(){ return (this.node.attributes.class || "").split(" ").filter(Boolean) }
967
+ set classes(val){ this.node.attributes.class=val.join(" ") }
968
+ contains(className){ return this.classes.includes(className) }
969
+ add(className){
970
+ const currentClasses=this.classes;
971
+ if (!currentClasses.includes(className)) this.classes=[...currentClasses,className];
972
+ }
973
+ remove(className){ this.classes=this.classes.filter(cls=>cls!==className); }
974
+ toggle(className){
975
+ if (this.classes.includes(className)) this.remove(className);
976
+ else this.add(className);
977
+ }
978
+ replace(oldClass,newClass){
979
+ if (this.classes.includes(oldClass)){
980
+ this.remove(oldClass);
981
+ this.add(newClass);
982
+ }
983
+ }
984
+ }
985
+ function insertBefore(arr,index,newItem){
986
+ const existingIndex=arr.indexOf(newItem);
987
+ if (existingIndex!==-1) arr.splice(existingIndex,1);
988
+ arr.splice(index,0,newItem);
989
+ }
990
+ class Node{
991
+ constructor(tagName,attributes={},parent=null){
992
+ this.isSingle=false;
993
+ this._tagName=tagName.toLowerCase();
994
+ this.tagName=tagName.toUpperCase();
995
+ this.attributes=attributes;
996
+ this.childNodes=[];
997
+ if (parent!==null) parent.childNodes.push(this)
998
+ this.parent=parent;
999
+ this._classList=null;
1000
+ this.__style=null;
1001
+ this._dataset=null
1002
+ }
1003
+ get id(){ return this.attributes.id ? this.attributes.id : null; }
1004
+ set id(newValue){ this.attributes.id=newValue; }
1005
+ get className(){ return this.attributes.class || null }
1006
+ get parentNode(){ return this.parent }
1007
+ get ancestors(){
1008
+ if (!this.parent) return []
1009
+ const ancestors=[]
1010
+ let element=this.parent
1011
+ while (element.tagName!=='ROOT'){
1012
+ ancestors.push(element)
1013
+ element=element.parent
1014
+ }
1015
+ return ancestors.reverse()
1016
+ }
1017
+ get childNodeIndex(){
1018
+ if (!this.parent) return null
1019
+ return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
1020
+ }
1021
+ get childIndex(){
1022
+ if (!this.parent) return null
1023
+ return this.parent.children ? this.parent.children.indexOf(this) : null
1024
+ }
1025
+ get previousElementSibling(){ return this.prev }
1026
+ get prev(){
1027
+ if (this.childIndex===null) return null
1028
+ return this.parent.children[this.childIndex-1]
1029
+ }
1030
+ get nextElementSibling(){ return this.next }
1031
+ get next(){
1032
+ if (this.childIndex===null) return null
1033
+ return this.parent.children[this.childIndex+1] || null
1034
+ }
1035
+ get dataset(){
1036
+ if (!this._dataset) this._dataset=getDataset(this);
1037
+ return this._dataset;
1038
+ }
1039
+ get classList(){
1040
+ if (!this._classList) this._classList=new NodeClassList(this);
1041
+ return this._classList;
1042
+ }
1043
+ get style(){
1044
+ if (!this.__style) this.__style=buildStyle(this.attributes)
1045
+ return this.__style
1046
+ }
1047
+ get outerHTML(){
1048
+ const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
1049
+ return `<${this._tagName}${attrs ? ' '+attrs : ''}>${this.innerHTML}</${this._tagName}>`;
1050
+ }
1051
+ getAttribute(attrName){ return this.attributes[attrName]!==undefined ? this.attributes[attrName] : null }
1052
+ setAttribute(attrName,value=''){ this.attributes[attrName]=value }
1053
+ removeAttribute(attrName){ delete this.attributes[attrName] }
1054
+ remove(){
1055
+ if (!this.parent) return
1056
+ const index=this.childNodeIndex;
1057
+ if (index!==null) this.parent.childNodes.splice(index,1);
1058
+ }
1059
+ get innerHTML(){
1060
+ return this.childNodes.map(child=>{
1061
+ if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
1062
+ else if (child instanceof TextNode) return child.textContent;
1063
+ else return child
1064
+ }).join("");
1065
+ }
1066
+ get innerText(){
1067
+ return this.childNodes.map(child=>{
1068
+ if (child instanceof Node || child instanceof SingleNode) return child.innerText;
1069
+ else if (child instanceof TextNode) return child.textContent;
1070
+ else return child
1071
+ }).join("");
1072
+ }
1073
+ set innerText(value){
1074
+ this.childNodes=[new TextNode(value)]
1075
+ return value
1076
+ }
1077
+ $$(query){ return this.querySelectorAll(query) }
1078
+ querySelectorAll(query){
1079
+ const selectors=Query.get(query)
1080
+ return find(selectors,this,new Set())
1081
+ }
1082
+ $(query){ return this.querySelector(query) }
1083
+ querySelector(query){
1084
+ const selectors=Query.get(query)
1085
+ return find(selectors,this,new Set(),true)[0] || null
1086
+ }
1087
+ getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
1088
+ getElementsByTagName(query){ return this.querySelectorAll(query) }
1089
+ getElementById(query){ return this.querySelector('#'+query) }
1090
+ get children(){
1091
+ return this.childNodes.filter(child=>{
1092
+ if (!(child instanceof Node)) return false
1093
+ if (child._tagName==='#comment') return false
1094
+ return true
1095
+ });
1096
+ }
1097
+ insertAdjacentElement(position,newElement){
1098
+ if (newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
1099
+ const pos=position.toLowerCase();
1100
+ if(newElement.parentNode){
1101
+ newElement.parentNode.childNodes=newElement.parentNode.childNodes.filter(el=>el!==newElement)
1102
+ }
1103
+ if (pos==='afterbegin' || pos==='beforeend'){
1104
+ if (pos==="afterbegin") this.childNodes.unshift(newElement);
1105
+ else if (pos==="beforeend") this.childNodes.push(newElement);
1106
+ newElement.parent=this
1107
+ return newElement
1108
+ }
1109
+ if (!this.parent) throw new Error("Can't insert element to element without parent")
1110
+ if (pos==="beforebegin") insertBefore(this.parent.childNodes,this.childNodeIndex,newElement)
1111
+ else if (pos==="afterend") this.parent.childNodes.splice(this.childNodeIndex+1,0,newElement);
1112
+ newElement.parent=this.parent
1113
+ return newElement
1114
+ }
1115
+ insertAdjacentHTML(position,html){
1116
+ const newNode=parseHTML(html);
1117
+ newNode.childNodes.forEach(node=>{
1118
+ this.insertAdjacentElement(position,node);
1119
+ });
1120
+ return newNode
1121
+ }
1122
+ insertAdjacentText(position,text){
1123
+ return this.insertAdjacentElement(position,new TextNode(text));
1124
+ }
1125
+ insert(position,element){
1126
+ const positions=['beforebegin','afterbegin','beforeend','afterend']
1127
+ if (positions[position]) position=positions[position]
1128
+ if (typeof element==='string'){
1129
+ element=element.trim()
1130
+ if (element.startsWith('<') && element.endsWith('>')){
1131
+ return this.insertAdjacentHTML(position,element)
1132
+ }
1133
+ return this.insertAdjacentText(position,element)
1134
+ }
1135
+ return this.insertAdjacentElement(position,element)
1136
+ }
1137
+ set innerHTML(html){
1138
+ this.childNodes=html.trim().startsWith('<') ? parseHTML(html).childNodes : [html]
1139
+ this.children.forEach(child=>child.parent=this);
1140
+ }
1141
+ set outerHTML(html){
1142
+ const parsed=parseHTML(html);
1143
+ if (!this.parent) throw new Error('element has no parent node')
1144
+ const index=this.childIndex
1145
+ if (index!==null) this.parent.childNodes.splice(index,1,...parsed.childNodes);
1146
+ }
1147
+ appendChild(newChild){
1148
+ if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
1149
+ if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
1150
+ } else if (typeof newChild==='string') newChild=new TextNode(newChild)
1151
+ else return newChild
1152
+ this.childNodes.push(newChild);
1153
+ newChild.parent=this;
1154
+ return newChild;
1155
+ }
1156
+ get textContent(){
1157
+ if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
1158
+ return this.childNodes.map(child=>{
1159
+ if (child instanceof SingleNode) return ''
1160
+ if (child instanceof TextNode) return child.nodeValue
1161
+ if (child instanceof Node) return child.textContent;
1162
+ else return child;
1163
+ }).join('');
1164
+ }
1165
+ set textContent(value){
1166
+ this.childNodes=[];
1167
+ if (value!==null && value!==undefined){
1168
+ this.childNodes.push(value.toString());
1169
+ }
1170
+ }
1171
+ }
1172
+ class SingleNode extends Node{
1173
+ constructor(tagName,attributes={},parent=null){
1174
+ if(attributes['?'] && tagName==='?xml') delete attributes['?']
1175
+ super(tagName,attributes,parent);
1176
+ this.isSingle=true
1177
+ }
1178
+ get outerHTML(){
1179
+ if (this._tagName==="#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
1180
+ const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
1181
+ return `<${this._tagName} ${attrs}${this._tagName==='?xml' ? '?' : ''}>`;
1182
+ }
1183
+ get innerHTML(){ return ""; }
1184
+ set innerHTML(_){ }
1185
+ $(_){return null}
1186
+ $$(_){return []}
1187
+ querySelectorAll(_){ return []; }
1188
+ querySelector(_){ return null; }
1189
+ getElementsByClassName(_){ return []; }
1190
+ getElementsByTagName(_){ return []; }
1191
+ getElementById(_){ return null; }
1192
+ get children(){ return []; }
1193
+ insertAdjacentElement(position,newElement){
1194
+ if(position==='afterbegin') position='beforebegin'
1195
+ if(position==='beforeend') position='afterend'
1196
+ super.insertAdjacentElement(position,newElement)
1197
+ }
1198
+ insertAdjacentHTML(position,html){
1199
+ if(position==='afterbegin') position='beforebegin'
1200
+ if(position==='beforeend') position='afterend'
1201
+ super.insertAdjacentHTML(position,html)
1202
+ }
1203
+ insertAdjacentText(position,text){
1204
+ if(position==='afterbegin') position='beforebegin'
1205
+ if(position==='beforeend') position='afterend'
1206
+ super.insertAdjacentText(position,text)
1207
+ }
1208
+ insert(position,element){
1209
+ if(position===1) position=0
1210
+ if(position===2) position=3
1211
+ super.insert(position,element)
1212
+ }
1213
+ appendChild(_){ }
1214
+ get textContent(){ return ""; }
1215
+ set textContent(_){ }
1216
+ }
1217
+
1218
+ class Root extends Node{
1219
+ constructor(){
1220
+ super('ROOT',{},null);
1221
+ this.isSingle=false
1222
+ }
1223
+ }
1224
+ class Document extends Node{
1225
+ constructor(html,url){
1226
+ super('ROOT',{},null);
1227
+ this.isSingle=false
1228
+ this.URL=url
1229
+ if(html instanceof Document) this.childNodes=[...buildFromCache(cacheDoc(html)).childNodes]
1230
+ else if(typeof html==='string') this.innerHTML=html
1231
+ else this.html
1232
+ }
1233
+ get documentElement(){return this.html}
1234
+ get html(){
1235
+ const html=this.$('html')
1236
+ if(html===null) this.innerHTML=/*html*/`<html lang="en"><head><meta charset="UTF-8"><title></title></head><body></body></html>`
1237
+ return this.$('html')
1238
+ }
1239
+ get innerHTML(){
1240
+ const inner=super.innerHTML
1241
+ return '<!DOCTYPE html>'+inner
1242
+ }
1243
+ set innerHTML(html){return super.innerHTML=html}
1244
+ get head(){
1245
+ const head=this.html.$('head')
1246
+ if(head===null) this.html.insert(1,'<head></head>')
1247
+ return this.$('head')
1248
+ }
1249
+ get body(){
1250
+ const body=this.html.$('body')
1251
+ if(body===null) this.head.insert(3,'<body></body>')
1252
+ return this.$('body')
1253
+ }
1254
+ get title(){
1255
+ const title=this.head.$('title')
1256
+ if(title===null) this.head.insert(1,'<title></title>')
1257
+ return this.$('title').innerText
1258
+ }
1259
+ get charset(){return this.$('meta[charset]')}
1260
+ set title(title){return this.head.$('title').innerText=title}
1261
+ get clone(){return new Document(this)}
1262
+ }
1263
+ function parseAttributes(str){
1264
+ const attrs={};
1265
+ let key="";
1266
+ let value="";
1267
+ let isKey=true;
1268
+ let quoteChar=null;
1269
+ for (let i=0; i< str.length; i++){
1270
+ const char=str[i];
1271
+ if (isKey && (char==='=' || char===' ')){
1272
+ if (char==='=') isKey=false;
1273
+ else if (key.trim()){
1274
+ attrs[key.trim()]=true;
1275
+ key="";
1276
+ }
1277
+ continue;
1278
+ }
1279
+ if (!quoteChar && (char==='"' || char==="'")){
1280
+ quoteChar=char;
1281
+ continue;
1282
+ } else if (quoteChar && char===quoteChar){
1283
+ quoteChar=null;
1284
+ attrs[key.trim()]=value.trim();
1285
+ key=""; value=""; isKey=true;
1286
+ continue;
1287
+ }
1288
+ if (isKey) key+=char;
1289
+ else value+=char;
1290
+ }
1291
+ if (key.trim() &&!value) attrs[key.trim()]='';
1292
+ return attrs;
1293
+ }
1294
+ const VOID_TAGS=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","!doctype",'?xml']);
1295
+ function parseHTML(html){
1296
+ const root=new Root();
1297
+ const stack=[root];
1298
+ let currentText="",i=0;
1299
+ let max=0
1300
+ function parseScript(){
1301
+ if (!html.startsWith("<script",i)) return false;
1302
+ const openTagEnd=html.indexOf(">",i);
1303
+ if (openTagEnd===-1) return false;
1304
+ const attributesString=html.substring(i+7,openTagEnd).trim();
1305
+ const attributes=parseAttributes(attributesString);
1306
+ let closeTagStart=html.indexOf("</script>",openTagEnd);
1307
+ if (closeTagStart===-1) return false;
1308
+ const content=html.substring(openTagEnd+1,closeTagStart);
1309
+ const scriptNode=new Node('script',attributes,stack[stack.length-1]);
1310
+ if(content.length>0) scriptNode.childNodes.push(content);
1311
+ i=closeTagStart+9;
1312
+ return true;
1313
+ }
1314
+ function parseSpecial(startStr,endStr,n1,n2,tag){
1315
+ if (!html.startsWith(startStr,i)) return false
1316
+ if(currentText.length) stack[stack.length - 1].childNodes.push(new TextNode(currentText)); // new!
1317
+ const end=html.indexOf(endStr,i+n1);
1318
+ const strNode=new Node(tag,{},stack[stack.length-1]);
1319
+ strNode.childNodes.push(html.substring(i+n1,end));
1320
+ i=end+n2;
1321
+ return true
1322
+ }
1323
+ while (i< html.length){
1324
+ if(i>=max) max=i;
1325
+ else break;
1326
+ if (parseScript()) continue
1327
+ if (parseSpecial("<!--","-->",4,3,'#comment')) continue
1328
+ if (parseSpecial("<style","</style>",7,8,'style')) continue
1329
+ if (html.startsWith("<![CDATA[",i)){
1330
+ const end=html.indexOf("]]>",i+9);
1331
+ if (end===-1) break;
1332
+ const content=html.substring(i+9,end);
1333
+ const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
1334
+ cdataNode.nodeValue=content;
1335
+ i=end+3;
1336
+ continue;
1337
+ }
1338
+ if (html.startsWith("<",i)){
1339
+ if (currentText && stack[stack.length-1]){
1340
+ const textNode=new TextNode(currentText)
1341
+ stack[stack.length-1].childNodes.push(textNode);
1342
+ textNode.parent=stack[stack.length-1]
1343
+ currentText="";
1344
+ }
1345
+ let tagEnd=i+1;
1346
+ let insideQuotes=false;
1347
+ let quoteChar=null;
1348
+ while (tagEnd< html.length){
1349
+ const char=html[tagEnd];
1350
+ if (!insideQuotes && (char==='"' || char==="'")){
1351
+ insideQuotes=true;
1352
+ quoteChar=char;
1353
+ } else if (insideQuotes && char===quoteChar){
1354
+ insideQuotes=false;
1355
+ quoteChar=null;
1356
+ }
1357
+ if (!insideQuotes && char==='>') break;
1358
+ tagEnd++;
1359
+ }
1360
+ const tagContent=html.substring(i+1,tagEnd);
1361
+ if (tagContent.startsWith("/")) stack.pop();
1362
+ else{
1363
+ let isSelfClosing=tagContent.endsWith('/');
1364
+ const tagNameEnd=tagContent.search(/\s|>|\//);
1365
+ const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
1366
+ const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
1367
+ const attributes=parseAttributes(attributesString);
1368
+ if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
1369
+ else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
1370
+ }
1371
+ i=tagEnd+1;
1372
+ } else{
1373
+ currentText+=html[i];
1374
+ i++;
1375
+ }
1376
+ }
1377
+ if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
1378
+ return root;
1379
+ }
1380
+ function buildFromCache(cached){
1381
+ function buildNode(cache,parent=null){
1382
+ if(typeof cache==='string') return parent.childNodes.push(cache)
1383
+ const{isSingle,tagName,attributes,childNodes,textContent}=cache
1384
+ if(textContent) return parent.childNodes.push(new TextNode(textContent))
1385
+ if(isSingle && parent) return parent.childNodes.push(new SingleNode(tagName,attributes))
1386
+ const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
1387
+ childNodes.forEach(childNode=>{
1388
+ buildNode(childNode,newDoc)
1389
+ });
1390
+ return newDoc
1391
+ }
1392
+ return buildNode(cached)
1393
+ }
1394
+
1395
+ function cacheDoc(doc){
1396
+ const props=['isSingle','tagName','attributes']
1397
+ function addToCache(element,cache={}){
1398
+ if(typeof element==='string') return element
1399
+ if(element.nodeName==='#text') return{textContent:element.textContent}
1400
+ props.forEach(prop=>{
1401
+ if(element[prop]){
1402
+ cache[prop]=typeof element[prop]==='object' ?{...element[prop]} : element[prop]
1403
+ }
1404
+ });
1405
+ if(!element.childNodes) return cache
1406
+ cache.childNodes=[]
1407
+ element.childNodes.forEach(childNode=>{
1408
+ cache.childNodes.push(addToCache(childNode))
1409
+ });
1410
+ return cache
1411
+ }
1412
+ return addToCache(doc)
1413
+ }
1414
+ return { parseHTML, Node, Query, TextNode, SingleNode, buildFromCache, cacheDoc, Root, Document }
730
1415
  })()