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