als-document 1.0.5-alpha → 1.0.5

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