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