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