als-document 0.12.0 → 1.0.0-beta

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