als-layout 6.1.0 → 7.0.0

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/layout.js ADDED
@@ -0,0 +1,127 @@
1
+ const alsDocument = (function(){
2
+ class Query{
3
  static get(query){
1
4
  let q=new Query(query)
2
5
  return q.selectors
3
6
  }
4
7
  constructor(query){
5
8
  this.query=query
6
9
  this.selectors=[]
7
10
  this.stringValues=[];
8
11
  this.parseSelectors(query.split(','))
9
12
  }
10
13
  parseSelectors(selectors){
11
14
  selectors.forEach(selector=>{
12
15
  let originalSelector=selector.trim()
13
16
  selector=this.removeSpaces(selector)
14
17
  this.stringValues=[]
15
18
  selector=selector.replace(/\[.*?\]/g,(value)=>{
16
19
  this.stringValues.push(value)
17
20
  return `[${this.stringValues.length-1}]`
18
21
  })
19
22
  let [element,ancestors]=this.splitAndCutLast(selector,' ')
20
23
  element=this.getFamily(element)
21
24
  if (ancestors.length>0)
22
25
  element.ancestors=ancestors.map(ancestor=>this.getFamily(ancestor))
23
26
  element.group=originalSelector
24
27
  this.selectors.push(element)
25
28
  });
26
29
  }
27
30
  splitAndCutLast(string,splitBy){
28
31
  const array=string.split(splitBy);
29
32
  const last=array.pop();
30
33
  return [last,array];
31
34
  }
32
35
  getFamily(group,element,prev,prevAny,sign){
33
36
  if (group.match(/\~|\+/)!==null){
34
37
  let [last,prevBrothers]=this.splitAndCutLast(group,/\~|\+/)
35
38
  let signs=group.replace(last,'')
36
39
  prevBrothers.forEach(el=>signs=signs.replace(el,''))
37
40
  signs=signs.match(/\~|\+/g)
38
41
  if (signs.length==1){
39
42
  sign=signs[0]
40
43
  } else if (signs.length>1){
41
44
  sign=signs.splice(signs.length-1,signs.length-1)[0]
42
45
  prevBrothers[0]=prevBrothers.map((b,i)=>{
43
46
  if (i< prevBrothers.length-1) b+=signs[i]
44
47
  return b
45
48
  }).join('')
46
49
  prevBrothers[0]=this.getFamily(prevBrothers[0])
47
50
  }
48
51
  if (sign=='~') prevAny=prevBrothers[0]
49
52
  else if (sign=='+') prev=prevBrothers[0]
50
53
  element=last
51
54
  } else element=group
52
55
  let family
53
56
  if (prev || prevAny){
54
57
  family=this.getParents(element)
55
58
  if (prev) family.prev=this.getParents(prev)
56
59
  if (prevAny) family.prevAny=this.getParents(prevAny)
57
60
  } else family=this.getParents(element)
58
61
  if (family.query!==group) family.group=group
59
62
  return family
60
63
  }
61
64
  getParents(selector){
62
65
  if (typeof selector=='string'){
63
66
  let [element,parents]=this.splitAndCutLast(selector,'>')
64
67
  element=this.buildElement(element)
65
68
  parents=parents.map(parent=>this.buildElement(parent))
66
69
  if (parents.length>0) element.parents=parents
67
70
  return element
68
71
  } else return selector
69
72
  }
70
73
  buildElement(element,id=null,tag=null,classList=[]){
71
74
  let query=element
72
75
  element=element.replace(/\#(\w-?)*/,$id=>{
73
76
  id=$id.replace(/^\#/,''); return ''
74
77
  })
75
78
  element=element.replace(/\.(\w-?)*/,$class=>{
76
79
  classList.push($class.replace(/^\./,'')); return ''
77
80
  })
78
81
  element=element.replace(/(\w\:?-?)*/,$tag=>{
79
82
  tag=$tag=='' ? null : $tag; return ''
80
83
  })
81
84
  let attribs=this.getAttributes(element)
82
85
  element={ query }
83
86
  if (id) element.id=id
84
87
  if (tag) element.tag=tag
85
88
  if (classList.length>0) element.classList=classList
86
89
  if (attribs.length>0) element.attribs=attribs
87
90
  return element
88
91
  }
89
92
  getAttributes(element){
90
93
  let attribs=this.stringValues.filter((value,index)=>{
91
94
  let searchValue=`[${index}]`
92
95
  if (element.match(searchValue)) return true
93
96
  else return false
94
97
  })
95
98
  attribs=attribs.map(attrib=>{
96
99
  let query=attrib
97
100
  attrib=attrib.replace('[','').replace(']','')
98
101
  let [name,...values]=attrib.split('=')
99
102
  const value=values.join('=').trim().replace(/^\"/,'').replace(/\"$/,'')
100
103
  let sign
101
104
  attrib={query,name}
102
105
  if(value){
103
106
  sign='='
104
107
  attrib.name=attrib.name.replace(/[\~\|\^\$\*]$/,(match=>{
105
108
  sign=match+sign
106
109
  return ''
107
110
  }))
108
111
  attrib.value=value
109
112
  }
110
113
  if (sign){
111
114
  attrib.sign=sign
112
115
  attrib.check=this.getAttribFn(sign).bind(attrib)
113
116
  }
114
117
  return attrib
115
118
  });
116
119
  return attribs
117
120
  }
118
121
  getAttribFn(sign){
119
122
  if (sign=='=') return function (value){ return value===this.value }
120
123
  if (sign=='*=') return function (value){ return value.includes(this.value) }
121
124
  if (sign=='^=') return function (value){ return value.startsWith(this.value) }
122
125
  if (sign=='$=') return function (value){ return value.endsWith(this.value) }
123
126
  if (sign=='|=') return function (value){
124
127
  return value.trim().split(' ').length==1
125
128
  && (value.startsWith(this.value) || value.startsWith(this.value+'-'))
126
129
  ? true : false
127
130
  }
128
131
  if (sign=='~=') return function (value){
129
132
  return this.value.trim().split(' ').length==1 && value.includes(this.value) ? true : false
130
133
  }
131
134
  }
132
135
  removeSpaces(selector){
133
136
  selector=selector.replace(/\s{2}/g,' ')
134
137
  selector=selector.replace(/\s?\^?\$?\|?\~?\*?\=\s*/g,(m)=>m.trim())
135
138
  selector=selector.replace(/\s?(\+|\~|\>)\s?/g,(m)=>m.trim())
136
139
  return selector
137
140
  }
141
+ }
142
+ function checkElement(el,selector){
138
143
  if(selector==undefined) return true
139
144
  if(el==null) return false
140
145
  let{tag,classList,attribs:attributes,id,prev,ancestors,parents,prevAny}=selector
141
146
  if(typeof el==='string') return false
142
147
  if(id!==undefined && el.id===null) return false
143
148
  if(id && id!==el.id) return false
144
149
  if(tag && el._tagName===undefined) return false
145
150
  else if(tag && tag!==el._tagName) return false
146
151
  const clas=el.attributes.class
147
152
  if(classList!==undefined && (clas===undefined || clas==='')) return false
148
153
  else if(classList!==undefined){
149
154
  if(classList.every(e=>el.classList.contains(e))===false) return false
150
155
  }
151
156
  if(checkattributes(attributes,el)===false) return false
152
157
  if(checkElement(el.prev,prev)===false) return false
153
158
  if(checkAncestors(el.ancestors,ancestors)===false) return false
154
159
  if(checkParents(el.ancestors,parents)===false) return false
155
160
  if(el.parent){
156
161
  if(checkPrevAny(el.parent.children,el.childIndex,prevAny)==false) return false
157
162
  }
158
163
  return true
164
+ }
159
165
  function checkattributes(attributes=[],el){
160
166
  let elattributes=el.attributes
161
167
  let names=Object.keys(elattributes)
162
168
  let passedTests=0
163
169
  if(attributes) for(let i=0; i<attributes.length; i++){
164
170
  let{name,value,check}=attributes[i]
165
171
  if(name=='inner' && value!==undefined && check && el.inner){
166
172
  if(check(el.inner)) passedTests++
167
173
  }
168
174
  if(!names.includes(name)) continue
169
175
  else if(value==undefined) passedTests++
170
176
  else if(value && elattributes[name]){
171
177
  if(check(elattributes[name])==false) continue
172
178
  else passedTests++
173
179
  }
174
180
  }
175
181
  if(passedTests==attributes.length) return true
176
182
  else return false
183
+ }
177
184
  function checkPrevAny(children=[],index,prevAny){
178
185
  let size=children.length
179
186
  if((size==0 || index==0) && prevAny) return false
180
187
  for(let i=index; i>=0; i--){
181
188
  if(checkElement(children[i],prevAny)) return true
182
189
  }
183
190
  return false
191
+ }
184
192
  function checkAncestors(ancestors=[],selectorAncestors=[]){
185
193
  let count=0
186
194
  if(selectorAncestors.length==0) return true
187
195
  let endIndex=ancestors.length-1
188
196
  let selectorIndex=selectorAncestors.length-1
189
197
  while(selectorIndex>=0){
190
198
  for(let i=endIndex; i>=0; i--){
191
199
  endIndex=i-1
192
200
  if(checkElement(ancestors[i],selectorAncestors[selectorIndex])==true){
193
201
  count++
194
202
  break
195
203
  }
196
204
  }
197
205
  selectorIndex--
198
206
  }
199
207
  if(count==selectorAncestors.length) return true
200
208
  else return false
209
+ }
201
210
  function checkParents(ancestors=[],selectorParents=[]){
202
211
  if(selectorParents.length===0) return true
203
212
  if(ancestors.length< selectorParents.length) return false
204
213
  let index=ancestors.length-1
205
214
  for(let i=selectorParents.length-1; i>=0; i--){
206
215
  if(checkElement(ancestors[index],selectorParents[i])===false) return false
207
216
  index--
208
217
  }
209
218
  return true
219
+ }
220
+ const getDataName=prop=>'data-'+prop.toLowerCase()
221
+ function getDataset(element){
210
222
  return new Proxy(element.attributes,{
211
223
  get: (target,prop)=>{return target[getDataName(prop)]},set: (target,prop,value)=>{target[getDataName(prop)]=value; return true},deleteProperty: (target,prop)=>{
212
224
  const dataAttr=getDataName(prop)
213
225
  if (dataAttr in target){
214
226
  delete target[dataAttr];
215
227
  return true;
216
228
  }
217
229
  return false;
218
230
  }
219
231
  });
232
+ }
233
+
234
+ function find(selectors,element,collection,first=false,firstTime=true){
220
235
  if(!firstTime)
221
236
  for(let selector of selectors){
222
237
  if(checkElement(element,selector)) collection.add(element)
223
238
  }
224
239
  if(element.children)
225
240
  element.children.forEach(child=>{
226
241
  if(first && collection.size>0) return
227
242
  find(selectors,child,collection,first,false)
228
243
  })
229
244
  return firstTime ? [...collection] : collection
245
+ }
246
+ class TextNode{
230
247
  constructor(data){
231
248
  this.nodeName='#text';
232
249
  this.parent=null;
233
250
  this.textContent=data;
234
251
  }
235
252
  get nodeValue(){return this.textContent}
236
253
  get parentNode(){return this.parent}
254
+ }
255
+
256
+ function buildStyle(attributes){
237
257
  const styles=attributes.style || "";
238
258
  const camelToKebab=str=>str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,'$1-$2').toLowerCase();
239
259
  const kebabToCamel=str=>str.replace(/-([a-z])/g,g=>g[1].toUpperCase());
240
260
  const baseStyleObj=styles.split(";").reduce((acc,style)=>{
241
261
  const [key,value]=style.split(":").map(s=>s.trim());
242
262
  if (key && value) acc[kebabToCamel(key)]=value;
243
263
  return acc;
244
264
  },{});
245
265
  return new Proxy(baseStyleObj,{
246
266
  get: (obj,prop)=>obj[camelToKebab(prop)] || obj[prop],set: (obj,prop,value)=>{
247
267
  obj[camelToKebab(prop)]=value;
248
268
  attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
249
269
  return true;
250
270
  },deleteProperty: (obj,prop)=>{
251
271
  delete obj[camelToKebab(prop)];
252
272
  attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
253
273
  return true;
254
274
  }
255
275
  });
276
+ }
277
+
278
+ class NodeClassList{
256
279
  constructor(node){ this.node=node }
257
280
  get classes(){ return (this.node.attributes.class || "").split(" ").filter(Boolean) }
258
281
  set classes(val){ this.node.attributes.class=val.join(" ") }
259
282
  contains(className){ return this.classes.includes(className) }
260
283
  add(className){
261
284
  const currentClasses=this.classes;
262
285
  if (!currentClasses.includes(className)) this.classes=[...currentClasses,className];
263
286
  }
264
287
  remove(className){ this.classes=this.classes.filter(cls=>cls!==className); }
265
288
  toggle(className){
266
289
  if (this.classes.includes(className)) this.remove(className);
267
290
  else this.add(className);
268
291
  }
269
292
  replace(oldClass,newClass){
270
293
  if (this.classes.includes(oldClass)){
271
294
  this.remove(oldClass);
272
295
  this.add(newClass);
273
296
  }
274
297
  }
298
+ }
299
+ function insertBefore(arr,index,newItem){
275
300
  const existingIndex=arr.indexOf(newItem);
276
301
  if (existingIndex!==-1) arr.splice(existingIndex,1);
277
302
  arr.splice(index,0,newItem);
303
+ }
278
304
  class Node{
279
305
  constructor(tagName,attributes={},parent=null){
280
306
  this.isSingle=false;
281
307
  this._tagName=tagName.toLowerCase();
282
308
  this.tagName=tagName.toUpperCase();
283
309
  this.attributes=attributes;
284
310
  this.childNodes=[];
285
311
  if (parent!==null) parent.childNodes.push(this)
286
312
  this.parent=parent;
287
313
  this._classList=null;
288
314
  this.__style=null;
289
315
  this._dataset=null
290
316
  }
291
317
  get id(){ return this.attributes.id ? this.attributes.id : null; }
292
318
  set id(newValue){ this.attributes.id=newValue; }
293
319
  get className(){ return this.attributes.class || null }
294
320
  get parentNode(){ return this.parent }
295
321
  get ancestors(){
296
322
  if (!this.parent) return []
297
323
  const ancestors=[]
298
324
  let element=this.parent
299
325
  while (element.tagName!=='ROOT'){
300
326
  ancestors.push(element)
301
327
  element=element.parent
302
328
  }
303
329
  return ancestors.reverse()
304
330
  }
305
331
  get childNodeIndex(){
306
332
  if (!this.parent) return null
307
333
  return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
308
334
  }
309
335
  get childIndex(){
310
336
  if (!this.parent) return null
311
337
  return this.parent.children ? this.parent.children.indexOf(this) : null
312
338
  }
313
339
  get previousElementSibling(){ return this.prev }
314
340
  get prev(){
315
341
  if (this.childIndex===null) return null
316
342
  return this.parent.children[this.childIndex-1]
317
343
  }
318
344
  get nextElementSibling(){ return this.next }
319
345
  get next(){
320
346
  if (this.childIndex===null) return null
321
347
  return this.parent.children[this.childIndex+1] || null
322
348
  }
323
349
  get dataset(){
324
350
  if (!this._dataset) this._dataset=getDataset(this);
325
351
  return this._dataset;
326
352
  }
327
353
  get classList(){
328
354
  if (!this._classList) this._classList=new NodeClassList(this);
329
355
  return this._classList;
330
356
  }
331
357
  get style(){
332
358
  if (!this.__style) this.__style=buildStyle(this.attributes)
333
359
  return this.__style
334
360
  }
335
361
  get outerHTML(){
336
362
  const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
337
363
  return `<${this._tagName}${attrs ? ' '+attrs : ''}>${this.innerHTML}</${this._tagName}>`;
338
364
  }
339
365
  getAttribute(attrName){ return this.attributes[attrName]!==undefined ? this.attributes[attrName] : null }
340
366
  setAttribute(attrName,value=''){ this.attributes[attrName]=value }
341
367
  removeAttribute(attrName){ delete this.attributes[attrName] }
342
368
  remove(){
343
369
  if (!this.parent) return
344
370
  const index=this.childNodeIndex;
345
371
  if (index!==null) this.parent.childNodes.splice(index,1);
346
372
  }
347
373
  get innerHTML(){
348
374
  return this.childNodes.map(child=>{
349
375
  if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
350
376
  else if (child instanceof TextNode) return child.textContent;
351
377
  else return child
352
378
  }).join("");
353
379
  }
354
380
  get innerText(){
355
381
  return this.childNodes.map(child=>{
356
382
  if (child instanceof Node || child instanceof SingleNode) return child.innerText;
357
383
  else if (child instanceof TextNode) return child.textContent;
358
384
  else return child
359
385
  }).join("");
360
386
  }
361
387
  set innerText(value){
362
388
  this.childNodes=[new TextNode(value)]
363
389
  return value
364
390
  }
365
391
  $$(query){ return this.querySelectorAll(query) }
366
392
  querySelectorAll(query){
367
393
  const selectors=Query.get(query)
368
394
  return find(selectors,this,new Set())
369
395
  }
370
396
  $(query){ return this.querySelector(query) }
371
397
  querySelector(query){
372
398
  const selectors=Query.get(query)
373
399
  return find(selectors,this,new Set(),true)[0] || null
374
400
  }
375
401
  getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
376
402
  getElementsByTagName(query){ return this.querySelectorAll(query) }
377
403
  getElementById(query){ return this.querySelector('#'+query) }
378
404
  get children(){
379
405
  return this.childNodes.filter(child=>{
380
406
  if (!(child instanceof Node)) return false
381
407
  if (child._tagName==='#comment') return false
382
408
  return true
383
409
  });
384
410
  }
385
411
  insertAdjacentElement(position,newElement){
386
412
  if (newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
387
413
  const pos=position.toLowerCase();
388
414
  if(newElement.parentNode){
389
415
  newElement.parentNode.childNodes=newElement.parentNode.childNodes.filter(el=>el!==newElement)
390
416
  }
391
417
  if (pos==='afterbegin' || pos==='beforeend'){
392
418
  if (pos==="afterbegin") this.childNodes.unshift(newElement);
393
419
  else if (pos==="beforeend") this.childNodes.push(newElement);
394
420
  newElement.parent=this
395
421
  return newElement
396
422
  }
397
423
  if (!this.parent) throw new Error("Can't insert element to element without parent")
398
424
  if (pos==="beforebegin") insertBefore(this.parent.childNodes,this.childNodeIndex,newElement)
399
425
  else if (pos==="afterend") this.parent.childNodes.splice(this.childNodeIndex+1,0,newElement);
400
426
  newElement.parent=this.parent
401
427
  return newElement
402
428
  }
403
429
  insertAdjacentHTML(position,html){
404
430
  const newNode=parseHTML(html);
405
431
  newNode.childNodes.forEach(node=>{
406
432
  this.insertAdjacentElement(position,node);
407
433
  });
408
434
  return newNode
409
435
  }
410
436
  insertAdjacentText(position,text){
411
437
  return this.insertAdjacentElement(position,new TextNode(text));
412
438
  }
413
439
  insert(position,element){
414
440
  const positions=['beforebegin','afterbegin','beforeend','afterend']
415
441
  if (positions[position]) position=positions[position]
416
442
  if (typeof element==='string'){
417
443
  element=element.trim()
418
444
  if (element.startsWith('<') && element.endsWith('>')){
419
445
  return this.insertAdjacentHTML(position,element)
420
446
  }
421
447
  return this.insertAdjacentText(position,element)
422
448
  }
423
449
  return this.insertAdjacentElement(position,element)
424
450
  }
425
451
  set innerHTML(html){
426
452
  this.childNodes=html.trim().startsWith('<') ? parseHTML(html).childNodes : [html]
427
453
  this.children.forEach(child=>child.parent=this);
428
454
  }
429
455
  set outerHTML(html){
430
456
  const parsed=parseHTML(html);
431
457
  if (!this.parent) throw new Error('element has no parent node')
432
458
  const index=this.childIndex
433
459
  if (index!==null) this.parent.childNodes.splice(index,1,...parsed.childNodes);
434
460
  }
435
461
  appendChild(newChild){
436
462
  if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
437
463
  if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
438
464
  } else if (typeof newChild==='string') newChild=new TextNode(newChild)
439
465
  else return newChild
440
466
  this.childNodes.push(newChild);
441
467
  newChild.parent=this;
442
468
  return newChild;
443
469
  }
444
470
  get textContent(){
445
471
  if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
446
472
  return this.childNodes.map(child=>{
447
473
  if (child instanceof SingleNode) return ''
448
474
  if (child instanceof TextNode) return child.nodeValue
449
475
  if (child instanceof Node) return child.textContent;
450
476
  else return child;
451
477
  }).join('');
452
478
  }
453
479
  set textContent(value){
454
480
  this.childNodes=[];
455
481
  if (value!==null && value!==undefined){
456
482
  this.childNodes.push(value.toString());
457
483
  }
458
484
  }
485
+ }
486
+ class SingleNode extends Node{
459
487
  constructor(tagName,attributes={},parent=null){
460
488
  if(attributes['?'] && tagName==='?xml') delete attributes['?']
461
489
  super(tagName,attributes,parent);
462
490
  this.isSingle=true
463
491
  }
464
492
  get outerHTML(){
465
493
  if (this._tagName==="#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
466
494
  const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
467
495
  return `<${this._tagName} ${attrs}${this._tagName==='?xml' ? '?' : ''}>`;
468
496
  }
469
497
  get innerHTML(){ return ""; }
470
498
  set innerHTML(_){ }
471
499
  $(_){return null}
472
500
  $$(_){return []}
473
501
  querySelectorAll(_){ return []; }
474
502
  querySelector(_){ return null; }
475
503
  getElementsByClassName(_){ return []; }
476
504
  getElementsByTagName(_){ return []; }
477
505
  getElementById(_){ return null; }
478
506
  get children(){ return []; }
479
507
  insertAdjacentElement(position,newElement){
480
508
  if(position==='afterbegin') position='beforebegin'
481
509
  if(position==='beforeend') position='afterend'
482
510
  return super.insertAdjacentElement(position,newElement)
483
511
  }
484
512
  insertAdjacentHTML(position,html){
485
513
  if(position==='afterbegin') position='beforebegin'
486
514
  if(position==='beforeend') position='afterend'
487
515
  return super.insertAdjacentHTML(position,html)
488
516
  }
489
517
  insertAdjacentText(position,text){
490
518
  if(position==='afterbegin') position='beforebegin'
491
519
  if(position==='beforeend') position='afterend'
492
520
  return super.insertAdjacentText(position,text)
493
521
  }
494
522
  insert(position,element){
495
523
  if(position===1) position=0
496
524
  if(position===2) position=3
497
525
  return super.insert(position,element)
498
526
  }
499
527
  appendChild(_){ }
500
528
  get textContent(){ return ""; }
501
529
  set textContent(_){ }
530
+ }
531
+
532
+ class Root extends Node{
502
533
  constructor(){
503
534
  super('ROOT',{},null);
504
535
  this.isSingle=false
505
536
  }
537
+ }
538
+ class Document extends Node{
506
539
  constructor(html,url){
507
540
  super('ROOT',{},null);
508
541
  this.isSingle=false
509
542
  this.URL=url
510
543
  if(html instanceof Document) this.childNodes=[...buildFromCache(cacheDoc(html)).childNodes]
511
544
  else if(typeof html==='string') this.innerHTML=html
512
545
  else this.html
513
546
  }
514
547
  get documentElement(){return this.html}
515
548
  get html(){
516
549
  const html=this.$('html')
517
550
  if(html===null) this.innerHTML=/*html*/`<html lang="en"><head><meta charset="UTF-8"><title></title></head><body></body></html>`
518
551
  return this.$('html')
519
552
  }
520
553
  get innerHTML(){
521
554
  const inner=super.innerHTML
522
555
  return '<!DOCTYPE html>'+inner
523
556
  }
524
557
  set innerHTML(html){return super.innerHTML=html}
525
558
  get head(){
526
559
  const head=this.html.$('head')
527
560
  if(head===null) this.html.insert(1,'<head></head>')
528
561
  return this.$('head')
529
562
  }
530
563
  get body(){
531
564
  const body=this.html.$('body')
532
565
  if(body===null) this.head.insert(3,'<body></body>')
533
566
  return this.$('body')
534
567
  }
535
568
  get title(){
536
569
  const title=this.head.$('title')
537
570
  if(title===null) this.head.insert(1,'<title></title>')
538
571
  return this.$('title').innerText
539
572
  }
540
573
  get charset(){return this.$('meta[charset]')}
541
574
  set title(title){return this.head.$('title').innerText=title}
542
575
  get clone(){return new Document(this)}
576
+ }
577
+ function parseAttributes(str){
543
578
  const attrs={};
544
579
  let key="";
545
580
  let value="";
546
581
  let isKey=true;
547
582
  let quoteChar=null;
548
583
  for (let i=0; i< str.length; i++){
549
584
  const char=str[i];
550
585
  if (isKey && (char==='=' || char===' ')){
551
586
  if (char==='=') isKey=false;
552
587
  else if (key.trim()){
553
588
  attrs[key.trim()]=true;
554
589
  key="";
555
590
  }
556
591
  continue;
557
592
  }
558
593
  if (!quoteChar && (char==='"' || char==="'")){
559
594
  quoteChar=char;
560
595
  continue;
561
596
  } else if (quoteChar && char===quoteChar){
562
597
  quoteChar=null;
563
598
  attrs[key.trim()]=value.trim();
564
599
  key=""; value=""; isKey=true;
565
600
  continue;
566
601
  }
567
602
  if (isKey) key+=char;
568
603
  else value+=char;
569
604
  }
570
605
  if (key.trim() &&!value) attrs[key.trim()]='';
571
606
  return attrs;
607
+ }
608
+ const VOID_TAGS=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","!doctype",'?xml']);
609
+ function parseHTML(html){
572
610
  const root=new Root();
573
611
  const stack=[root];
574
612
  let currentText="",i=0;
575
613
  let max=0
576
614
  function parseScript(){
577
615
  if (!html.startsWith("<script",i)) return false;
578
616
  const openTagEnd=html.indexOf(">",i);
579
617
  if (openTagEnd===-1) return false;
580
618
  const attributesString=html.substring(i+7,openTagEnd).trim();
581
619
  const attributes=parseAttributes(attributesString);
582
620
  let closeTagStart=html.indexOf("</script>",openTagEnd);
583
621
  if (closeTagStart===-1) return false;
584
622
  const content=html.substring(openTagEnd+1,closeTagStart);
585
623
  const scriptNode=new Node('script',attributes,stack[stack.length-1]);
586
624
  if(content.length>0) scriptNode.childNodes.push(content);
587
625
  i=closeTagStart+9;
588
626
  return true;
589
627
  }
590
628
  function parseSpecial(startStr,endStr,n1,n2,tag){
591
629
  if (!html.startsWith(startStr,i)) return false
592
630
  if(currentText.length) stack[stack.length-1].childNodes.push(new TextNode(currentText));
593
631
  const end=html.indexOf(endStr,i+n1);
594
632
  const strNode=new Node(tag,{},stack[stack.length-1]);
595
633
  strNode.childNodes.push(html.substring(i+n1,end));
596
634
  i=end+n2;
597
635
  return true
598
636
  }
599
637
  while (i< html.length){
600
638
  if(i>=max) max=i;
601
639
  else break;
602
640
  if (parseScript()) continue
603
641
  if (parseSpecial("<!--","-->",4,3,'#comment')) continue
604
642
  if (parseSpecial("<style","</style>",7,8,'style')) continue
605
643
  if (html.startsWith("<![CDATA[",i)){
606
644
  const end=html.indexOf("]]>",i+9);
607
645
  if (end===-1) break;
608
646
  const content=html.substring(i+9,end);
609
647
  const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
610
648
  cdataNode.nodeValue=content;
611
649
  i=end+3;
612
650
  continue;
613
651
  }
614
652
  if (html.startsWith("<",i)){
615
653
  if (currentText && stack[stack.length-1]){
616
654
  const textNode=new TextNode(currentText)
617
655
  stack[stack.length-1].childNodes.push(textNode);
618
656
  textNode.parent=stack[stack.length-1]
619
657
  currentText="";
620
658
  }
621
659
  let tagEnd=i+1;
622
660
  let insideQuotes=false;
623
661
  let quoteChar=null;
624
662
  while (tagEnd< html.length){
625
663
  const char=html[tagEnd];
626
664
  if (!insideQuotes && (char==='"' || char==="'")){
627
665
  insideQuotes=true;
628
666
  quoteChar=char;
629
667
  } else if (insideQuotes && char===quoteChar){
630
668
  insideQuotes=false;
631
669
  quoteChar=null;
632
670
  }
633
671
  if (!insideQuotes && char==='>') break;
634
672
  tagEnd++;
635
673
  }
636
674
  const tagContent=html.substring(i+1,tagEnd);
637
675
  if (tagContent.startsWith("/")) stack.pop();
638
676
  else{
639
677
  let isSelfClosing=tagContent.endsWith('/');
640
678
  const tagNameEnd=tagContent.search(/\s|>|\//);
641
679
  const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
642
680
  const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
643
681
  const attributes=parseAttributes(attributesString);
644
682
  if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
645
683
  else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
646
684
  }
647
685
  i=tagEnd+1;
648
686
  } else{
649
687
  currentText+=html[i];
650
688
  i++;
651
689
  }
652
690
  }
653
691
  if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
654
692
  return root;
693
+ }
694
+ function buildFromCache(cached){
655
695
  function buildNode(cache,parent=null){
656
696
  if(typeof cache==='string') return parent.childNodes.push(cache)
657
697
  const{isSingle,tagName,attributes,childNodes,textContent}=cache
658
698
  if(textContent) return parent.childNodes.push(new TextNode(textContent))
659
699
  if(isSingle && parent) return parent.childNodes.push(new SingleNode(tagName,attributes))
660
700
  const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
661
701
  childNodes.forEach(childNode=>{
662
702
  buildNode(childNode,newDoc)
663
703
  });
664
704
  return newDoc
665
705
  }
666
706
  return buildNode(cached)
707
+ }
667
708
 
709
+ function cacheDoc(doc){
668
710
  const props=['isSingle','tagName','attributes']
669
711
  function addToCache(element,cache={}){
670
712
  if(typeof element==='string') return element
671
713
  if(element.nodeName==='#text') return{textContent:element.textContent}
672
714
  props.forEach(prop=>{
673
715
  if(element[prop]){
674
716
  cache[prop]=typeof element[prop]==='object' ?{...element[prop]} : element[prop]
675
717
  }
676
718
  });
677
719
  if(!element.childNodes) return cache
678
720
  cache.childNodes=[]
679
721
  element.childNodes.forEach(childNode=>{
680
722
  cache.childNodes.push(addToCache(childNode))
681
723
  });
682
724
  return cache
683
725
  }
684
726
  return addToCache(doc)
727
+ }
728
+ return { parseHTML,Node,Query,TextNode,SingleNode,buildFromCache,cacheDoc,Root,Document }
729
+ })()
730
+ const { Document, SingleNode, Node } = alsDocument
731
+ class Layout extends Document {
732
+ constructor(html, host) { super(html, host); this.root = this.html; }
733
+ get rawHtml() { return this.innerHTML }
734
+ get clone() { return new this.constructor(new Document(this, this.URL), this.host) }
735
+ lang(lang) { this.html.setAttribute('lang', lang); return this }
736
+ link(href, attributes = { rel: "stylesheet", type: "text/css" }) {
737
+ if (!href || typeof href !== 'string') throw new Error(`href attribute must be a string`)
738
+ if (!this.root.querySelector(`link[rel=stylesheet][href^="${href}"]`))
739
+ this.head.insert(2, new SingleNode('link', { href, ...attributes }))
740
+ return this
741
+ }
742
+ keywords(keywords = []) {
743
+ let el = this.root.$('meta[name=keywords]') || new SingleNode('meta', { name: 'keywords' })
744
+ keywords = new Set([...(el.getAttribute('content') || '').split(','),...keywords.map(k => k.trim())].filter(Boolean))
745
+ if (keywords.size) el.setAttribute('content', Array.from(keywords).join(','))
746
+ if(keywords.size && !el.parent) this.head.insert(2, el)
747
+ return this
748
+ }
749
+ style(styles) {
750
+ if (typeof styles !== 'string') throw 'styles parameter should be string';
751
+ let el = this.root.$('style') || this.head.insert(2, new Node('style'))
752
+ el.innerHTML = el.innerHTML + '\n' + styles;
753
+ return this
754
+ }
755
+ url(url, host = this.URL) {
756
+ try {
757
+ url = (host ? new URL(url, host) : new URL(url)).href.replace(/\/$/, '')
758
+ this.meta({ property: 'og:url', content: url })
759
+ const el = this.root.$('link[rel="canonical"]') || this.head.insert(2, new SingleNode('link', { rel: 'canonical', href: url }))
760
+ el.setAttribute('href', url)
761
+ } catch (error) { error.info = `url "${url}" with host "${host}" is not valid url`; throw error; }
762
+ return this
763
+ }
764
+ meta(props) {
765
+ const entries = Object.entries(props)
766
+ const [name, value] = entries[0]
767
+ const metaElement = this.root.querySelector(`meta[${name}="${value}"]`) || this.head.insert(2, new SingleNode('meta', props))
768
+ entries.forEach(([name, v]) => metaElement.setAttribute(name, props[name]))
769
+ return this
770
+ }
771
+ script(attrs = {}, innerHTML = '', head = true) {
772
+ if (typeof attrs !== 'object' || attrs === null || Array.isArray(attrs)) attrs = {}
773
+ if (attrs.src && this.root.querySelector(`script[src="${attrs.src}"]`)) return this
774
+ if (Object.keys(attrs).length || innerHTML) {
775
+ const script = new Node('script', attrs)
776
+ if (innerHTML) script.innerHTML = innerHTML
777
+ if (head) this.head.insert(2, script)
778
+ else this.html.insert(2, script)
779
+ }
780
+ return this
781
+ }
782
+ description(description) {
783
+ this.meta({ name: 'description', content: description })
784
+ this.meta({ property: 'og:description', content: description })
785
+ this.meta({ property: 'twitter:description', content: description })
786
+ return this
787
+ }
788
+ favicon(href) {
789
+ const el = this.root.$('link[rel=icon][type=image/x-icon]')
790
+ if (el) el.setAttribute('href', href)
791
+ else this.head.insert(2, new SingleNode('link', { rel: 'icon', href, type: 'image/x-icon' }))
792
+ return this
793
+ }
794
+ viewport(viewport = 'width=device-width, initial-scale=1.0') {
795
+ const el = this.root.$('meta[name="viewport"]')
796
+ if (el) el.setAttribute('content', viewport)
797
+ else this.head.insert(2, new SingleNode('meta', { name: 'viewport', content: viewport }))
798
+ return this
799
+ }
800
+ image(image) {
801
+ this.meta({ property: 'og:image', content: image })
802
+ this.meta({ name: 'twitter:image', content: image })
803
+ this.meta({ name: 'twitter:card', content: 'summary_large_image' })
804
+ return this
805
+ }
806
+ title(title) {
807
+ super.title // create title tag if not exists
808
+ super.title = title
809
+ this.meta({ property: 'og:title', content: title })
810
+ return this
811
+ }
812
+ }