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