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