als-document 1.0.0-alpha → 1.0.0-beta

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