als-document 1.0.1-alpha → 1.0.1-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.mjs 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' || pos==='beforeend'){
283
284
  if (pos==="afterbegin") this.childNodes.unshift(newElement);
284
285
  else if (pos==="beforeend") this.childNodes.push(newElement);
285
286
  newElement.parent=this
286
287
  return newElement
287
288
  }
288
289
  if (!this.parent) throw new Error("Can't insert element to element without parent")
289
290
  if (pos==="beforebegin") insertBefore(this.parent.childNodes,this.childNodeIndex,newElement)
290
291
  else if (pos==="afterend") this.parent.childNodes.splice(this.childNodeIndex+1,0,newElement);
291
292
  newElement.parent=this.parent
292
293
  return newElement
293
294
  }
294
295
  insertAdjacentHTML(position,html){
295
296
  const newNode=parseHTML(html);
296
297
  newNode.childNodes.reverse().forEach(node=>{
297
298
  this.insertAdjacentElement(position,node);
298
299
  });
299
300
  return newNode
300
301
  }
301
302
  insertAdjacentText(position,text){
302
303
  return this.insertAdjacentElement(position,new TextNode(text));
303
304
  }
304
305
  insert(position,element){
305
306
  const positions=['beforebegin','afterbegin','beforeend','afterend']
306
307
  if(positions[position]) position=positions[position]
307
308
  if(typeof element==='string'){
308
309
  element=element.trim()
309
310
  if(element.startsWith('<') && element.endsWith('>')){
310
311
  return this.insertAdjacentHTML(position,element)
311
312
  }
312
313
  return this.insertAdjacentText(position,element)
313
314
  }
314
315
  return this.insertAdjacentElement(position,element)
315
316
  }
316
317
  set innerHTML(html){
317
318
  const parsed=parseHTML(html);
318
319
  this.childNodes=parsed.childNodes;
319
320
  }
320
321
  set outerHTML(html){
321
322
  const parsed=parseHTML(html);
322
323
  if (!this.parent) return console.log('element has no parent node')
323
324
  const index=this.childIndex
324
325
  if (index!==null) this.parent.childNodes.splice(index,1,...parsed.childNodes);
325
326
  }
326
327
  appendChild(newChild){
327
328
  if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
328
329
  if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
329
330
  } else if(typeof newChild==='string') newChild=new TextNode(newChild)
330
331
  else return newChild
331
332
  this.childNodes.push(newChild);
332
333
  newChild.parent=this;
333
334
  return newChild;
334
335
  }
335
336
  get textContent(){
336
337
  if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
337
338
  return this.childNodes.map(child=>{
338
339
  if(child instanceof SingleNode) return ''
339
340
  if(child instanceof TextNode) return child.nodeValue
340
341
  if(child instanceof Node) return child.textContent;
341
342
  else return child;
342
343
  }).join(" ");
343
344
  }
344
345
  set textContent(value){
345
346
  this.childNodes=[];
346
347
  if (value!==null && value!==undefined){
347
348
  this.childNodes.push(value.toString());
348
349
  }
349
350
  }
350
351
  }
351
- class SingleNode extends Node{
352
352
  constructor(tagName,attributes={},parent=null){
353
353
  if(attributes['?'] && tagName==='?xml') delete attributes['?']
354
354
  super(tagName,attributes,parent);
355
355
  this.isSingle=true
356
356
  }
357
357
  get outerHTML(){
358
358
  if (this.tagName==="#cdata-section") return `<![CDATA[${this.textContent}]]>`;
359
359
  const attrs=Object.entries(this.attributes).map(([key,val])=>`${key}="${val}"`).join(" ");
360
360
  return `<${this.tagName} ${attrs}${this.tagName==='?xml' ? '?' : ''}>`;
361
361
  }
362
362
  get innerHTML(){ return ""; }
363
363
  set innerHTML(_){ }
364
364
  $(_){return null}
365
365
  $$(_){return []}
366
366
  querySelectorAll(_){ return []; }
367
367
  querySelector(_){ return null; }
368
368
  getElementsByClassName(_){ return []; }
369
369
  getElementsByTagName(_){ return []; }
370
370
  getElementById(_){ return null; }
371
371
  get children(){ return []; }
372
372
  insertAdjacentElement(_,__){ }
373
373
  insertAdjacentHTML(_,__){ }
374
374
  insertAdjacentText(_,__){ }
375
375
  appendChild(_){ }
376
376
  get textContent(){ return ""; }
377
377
  set textContent(_){ }
378
+ class SingleNode extends Node{
378
379
  constructor(tagName,attributes={},parent=null){
379
380
  if(attributes['?'] && tagName==='?xml') delete attributes['?']
380
381
  super(tagName,attributes,parent);
381
382
  this.isSingle=true
382
383
  }
383
384
  get outerHTML(){
384
385
  if (this.tagName==="#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
385
386
  const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
386
387
  return `<${this.tagName} ${attrs}${this.tagName==='?xml' ? '?' : ''}>`;
387
388
  }
388
389
  get innerHTML(){ return ""; }
389
390
  set innerHTML(_){ }
390
391
  $(_){return null}
391
392
  $$(_){return []}
392
393
  querySelectorAll(_){ return []; }
393
394
  querySelector(_){ return null; }
394
395
  getElementsByClassName(_){ return []; }
395
396
  getElementsByTagName(_){ return []; }
396
397
  getElementById(_){ return null; }
397
398
  get children(){ return []; }
398
399
  insertAdjacentElement(_,__){ }
399
400
  insertAdjacentHTML(_,__){ }
400
401
  insertAdjacentText(_,__){ }
401
402
  appendChild(_){ }
402
403
  insert(_,__){ }
403
404
  get textContent(){ return ""; }
404
405
  set textContent(_){ }
406
+ }
407
+
408
+ class Root extends Node{
405
409
  constructor(){
406
410
  super('ROOT',{},null);
407
411
  this.isSingle=false
408
412
  }
409
413
  get body(){return this.$('body')}
410
414
  get head(){return this.$('head')}
411
415
  get title(){return this.$('title')}
412
416
  set title(title){return this.$('title').innerHTML=title}
413
417
  }
414
- function parseAttributes(str){
415
418
  const attrs={};
416
419
  let key="";
417
420
  let value="";
418
421
  let isKey=true;
419
422
  let quoteChar=null;
420
423
  for (let i=0; i< str.length; i++){
421
424
  const char=str[i];
422
425
  if (isKey && (char==='=' || char===' ')){
423
426
  if (char==='=') isKey=false;
424
427
  else if (key.trim()){
425
428
  attrs[key.trim()]=true;
426
429
  key="";
427
430
  }
428
431
  continue;
429
432
  }
430
433
  if (!quoteChar && (char==='"' || char==="'")){
431
434
  quoteChar=char;
432
435
  continue;
433
436
  } else if (quoteChar && char===quoteChar){
434
437
  quoteChar=null;
435
438
  attrs[key.trim()]=value.trim();
436
439
  key=""; value=""; isKey=true;
437
440
  continue;
438
441
  }
439
442
  if (isKey) key+=char;
440
443
  else value+=char;
441
444
  }
442
445
  if (key.trim() &&!value) attrs[key.trim()]=true;
443
446
  return attrs;
447
+ function parseAttributes(str){
444
448
  const attrs={};
445
449
  let key="";
446
450
  let value="";
447
451
  let isKey=true;
448
452
  let quoteChar=null;
449
453
  for (let i=0; i< str.length; i++){
450
454
  const char=str[i];
451
455
  if (isKey && (char==='=' || char===' ')){
452
456
  if (char==='=') isKey=false;
453
457
  else if (key.trim()){
454
458
  attrs[key.trim()]=true;
455
459
  key="";
456
460
  }
457
461
  continue;
458
462
  }
459
463
  if (!quoteChar && (char==='"' || char==="'")){
460
464
  quoteChar=char;
461
465
  continue;
462
466
  } else if (quoteChar && char===quoteChar){
463
467
  quoteChar=null;
464
468
  attrs[key.trim()]=value.trim();
465
469
  key=""; value=""; isKey=true;
466
470
  continue;
467
471
  }
468
472
  if (isKey) key+=char;
469
473
  else value+=char;
470
474
  }
471
475
  if (key.trim() &&!value) attrs[key.trim()]='';
472
476
  return attrs;
473
477
  }
474
478
  const VOID_TAGS=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","!doctype",'?xml']);
475
- function parseHTML(html){
476
479
  const root=new Node("ROOT");
477
480
  const stack=[root];
478
481
  let currentText="",i=0;
479
482
  function parseSpecial(startStr,endStr,n1,n2,tag){
480
483
  if (!html.startsWith(startStr,i)) return false
481
484
  const end=html.indexOf(endStr,i+n1);
482
485
  const strNode=new Node(tag,{},stack[stack.length-1]);
483
486
  strNode.childNodes.push(html.substring(i+n1,end));
484
487
  i=end+n2;
485
488
  return true
486
489
  }
487
490
  while (i< html.length){
488
491
  if (parseSpecial("<!--","-->",4,3,'#comment')) continue
489
492
  if (parseSpecial("<script","</script>",8,9,'script')) continue
490
493
  if (parseSpecial("<style","</style>",7,8,'style')) continue
491
494
  if (html.startsWith("<![CDATA[",i)){
492
495
  const end=html.indexOf("]]>",i+9);
493
496
  if (end===-1) break;
494
497
  const content=html.substring(i+9,end);
495
498
  const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
496
499
  cdataNode.nodeValue=content;
497
500
  i=end+3;
498
501
  continue;
499
502
  }
500
503
  if (html.startsWith("<",i)){
501
504
  if (currentText.trim()){
502
505
  stack[stack.length-1].childNodes.push(new TextNode(currentText.trim()));
503
506
  currentText="";
504
507
  }
505
508
  let tagEnd=i+1;
506
509
  let insideQuotes=false;
507
510
  let quoteChar=null;
508
511
  while (tagEnd< html.length){
509
512
  const char=html[tagEnd];
510
513
  if (!insideQuotes && (char==='"' || char==="'")){
511
514
  insideQuotes=true;
512
515
  quoteChar=char;
513
516
  } else if (insideQuotes && char===quoteChar){
514
517
  insideQuotes=false;
515
518
  quoteChar=null;
516
519
  }
517
520
  if (!insideQuotes && char==='>') break;
518
521
  tagEnd++;
519
522
  }
520
523
  const tagContent=html.substring(i+1,tagEnd);
521
524
  if (tagContent.startsWith("/")) stack.pop();
522
525
  else{
523
526
  let isSelfClosing=tagContent.endsWith('/');
524
527
  const tagNameEnd=tagContent.search(/\s|>|\//);
525
528
  const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
526
529
  const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
527
530
  const attributes=parseAttributes(attributesString);
528
531
  if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
529
532
  else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
530
533
  }
531
534
  i=tagEnd+1;
532
535
  } else{
533
536
  currentText+=html[i];
534
537
  i++;
535
538
  }
536
539
  }
537
540
  if (currentText.trim()) stack[stack.length-1].childNodes.push(new TextNode(currentText.trim()));
538
541
  return root;
542
+ function parseHTML(html){
539
543
  const root=new Root();
540
544
  const stack=[root];
541
545
  let currentText="",i=0;
542
546
  let max=0
543
547
  function parseScript(){
544
548
  if (!html.startsWith("<script",i)) return false;
545
549
  const openTagEnd=html.indexOf(">",i);
546
550
  if (openTagEnd===-1) return false;
547
551
  const attributesString=html.substring(i+7,openTagEnd).trim();
548
552
  const attributes=parseAttributes(attributesString);
549
553
  let closeTagStart=html.indexOf("</script>",openTagEnd);
550
554
  if (closeTagStart===-1) return false;
551
555
  const content=html.substring(openTagEnd+1,closeTagStart);
552
556
  const scriptNode=new Node('script',attributes,stack[stack.length-1]);
553
557
  if(content.length>0) scriptNode.childNodes.push(content);
554
558
  i=closeTagStart+9;
555
559
  return true;
556
560
  }
557
561
  function parseSpecial(startStr,endStr,n1,n2,tag){
558
562
  if (!html.startsWith(startStr,i)) return false
559
563
  const end=html.indexOf(endStr,i+n1);
560
564
  const strNode=new Node(tag,{},stack[stack.length-1]);
561
565
  strNode.childNodes.push(html.substring(i+n1,end));
562
566
  i=end+n2;
563
567
  return true
564
568
  }
565
569
  while (i< html.length){
566
570
  if(i>=max) max=i;
567
571
  else break;
568
572
  if (parseScript()) continue
569
573
  if (parseSpecial("<!--","-->",4,3,'#comment')) continue
570
574
  if (parseSpecial("<style","</style>",7,8,'style')) continue
571
575
  if (html.startsWith("<![CDATA[",i)){
572
576
  const end=html.indexOf("]]>",i+9);
573
577
  if (end===-1) break;
574
578
  const content=html.substring(i+9,end);
575
579
  const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
576
580
  cdataNode.nodeValue=content;
577
581
  i=end+3;
578
582
  continue;
579
583
  }
580
584
  if (html.startsWith("<",i)){
581
585
  if (currentText && stack[stack.length-1]){
582
586
  const textNode=new TextNode(currentText)
583
587
  stack[stack.length-1].childNodes.push(textNode);
584
588
  textNode.parent=stack[stack.length-1]
585
589
  currentText="";
586
590
  }
587
591
  let tagEnd=i+1;
588
592
  let insideQuotes=false;
589
593
  let quoteChar=null;
590
594
  while (tagEnd< html.length){
591
595
  const char=html[tagEnd];
592
596
  if (!insideQuotes && (char==='"' || char==="'")){
593
597
  insideQuotes=true;
594
598
  quoteChar=char;
595
599
  } else if (insideQuotes && char===quoteChar){
596
600
  insideQuotes=false;
597
601
  quoteChar=null;
598
602
  }
599
603
  if (!insideQuotes && char==='>') break;
600
604
  tagEnd++;
601
605
  }
602
606
  const tagContent=html.substring(i+1,tagEnd);
603
607
  if (tagContent.startsWith("/")) stack.pop();
604
608
  else{
605
609
  let isSelfClosing=tagContent.endsWith('/');
606
610
  const tagNameEnd=tagContent.search(/\s|>|\//);
607
611
  const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
608
612
  const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
609
613
  const attributes=parseAttributes(attributesString);
610
614
  if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
611
615
  else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
612
616
  }
613
617
  i=tagEnd+1;
614
618
  } else{
615
619
  currentText+=html[i];
616
620
  i++;
617
621
  }
618
622
  }
619
623
  if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
620
624
  return root;
625
+ }
626
+ function buildFromCache(cached){
621
627
  function buildNode(cache,parent=null){
622
628
  if(typeof cache==='string') return parent.childNodes.push(cache)
623
629
  const{isSingle,tagName,attributes,childNodes,textContent}=cache
624
630
  if(textContent) return parent.childNodes.push(new TextNode(textContent))
625
631
  if(isSingle) return parent.childNodes.push(new SingleNode(tagName,attributes))
626
632
  const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
627
633
  childNodes.forEach(childNode=>{
628
634
  buildNode(childNode,newDoc)
629
635
  });
630
636
  return newDoc
631
637
  }
632
638
  return buildNode(cached)
639
+ }
633
640
 
641
+ function cacheDoc(doc){
634
642
  const props=['isSingle','tagName','attributes']
635
643
  function addToCache(element,cache={}){
636
644
  if(typeof element==='string') return element
637
645
  if(element.nodeName==='#text') return{textContent:element.textContent}
638
646
  props.forEach(prop=>{
639
647
  if(element[prop]) cache[prop]=element[prop]
640
648
  });
641
649
  if(!element.childNodes) return cache
642
650
  cache.childNodes=[]
643
651
  element.childNodes.forEach(childNode=>{
644
652
  cache.childNodes.push(addToCache(childNode))
645
653
  });
646
654
  return cache
647
655
  }
648
656
  return addToCache(doc)
649
657
  }
650
- export default { parseHTML, Node, Query, TextNode, SingleNode }
658
+ export default { parseHTML, Node, Query, TextNode, SingleNode, buildFromCache, cacheDoc, Root }