als-document 1.0.2-alpha → 1.0.2

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