als-document 1.4.3 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- class Query{
2
1
  static get(query){
3
2
  let q=new Query(query)
4
3
  return q.selectors
5
4
  }
6
5
  constructor(query){
7
6
  this.query=query
8
7
  this.selectors=[]
9
8
  this.stringValues=[];
10
9
  this.parseSelectors(query.split(','))
11
10
  }
12
11
  parseSelectors(selectors){
13
12
  selectors.forEach(selector=>{
14
13
  let originalSelector=selector.trim()
15
14
  selector=this.removeSpaces(selector)
16
15
  this.stringValues=[]
17
16
  selector=selector.replace(/\[.*?\]/g,(value)=>{
18
17
  this.stringValues.push(value)
19
18
  return `[${this.stringValues.length-1}]`
20
19
  })
21
20
  let [element,ancestors]=this.splitAndCutLast(selector,' ')
22
21
  element=this.getFamily(element)
23
22
  if (ancestors.length>0)
24
23
  element.ancestors=ancestors.map(ancestor=>this.getFamily(ancestor))
25
24
  element.group=originalSelector
26
25
  this.selectors.push(element)
27
26
  });
28
27
  }
29
28
  splitAndCutLast(string,splitBy){
30
29
  const array=string.split(splitBy);
31
30
  const last=array.pop();
32
31
  return [last,array];
33
32
  }
34
33
  getFamily(group,element,prev,prevAny,sign){
35
34
  if (group.match(/\~|\+/)!==null){
36
35
  let [last,prevBrothers]=this.splitAndCutLast(group,/\~|\+/)
37
36
  let signs=group.replace(last,'')
38
37
  prevBrothers.forEach(el=>signs=signs.replace(el,''))
39
38
  signs=signs.match(/\~|\+/g)
40
39
  if (signs.length==1){
41
40
  sign=signs[0]
42
41
  } else if (signs.length>1){
43
42
  sign=signs.splice(signs.length-1,signs.length-1)[0]
44
43
  prevBrothers[0]=prevBrothers.map((b,i)=>{
45
44
  if (i< prevBrothers.length-1) b+=signs[i]
46
45
  return b
47
46
  }).join('')
48
47
  prevBrothers[0]=this.getFamily(prevBrothers[0])
49
48
  }
50
49
  if (sign=='~') prevAny=prevBrothers[0]
51
50
  else if (sign=='+') prev=prevBrothers[0]
52
51
  element=last
53
52
  } else element=group
54
53
  let family
55
54
  if (prev || prevAny){
56
55
  family=this.getParents(element)
57
56
  if (prev) family.prev=this.getParents(prev)
58
57
  if (prevAny) family.prevAny=this.getParents(prevAny)
59
58
  } else family=this.getParents(element)
60
59
  if (family.query!==group) family.group=group
61
60
  return family
62
61
  }
63
62
  getParents(selector){
64
63
  if (typeof selector=='string'){
65
64
  let [element,parents]=this.splitAndCutLast(selector,'>')
66
65
  element=this.buildElement(element)
67
66
  parents=parents.map(parent=>this.buildElement(parent))
68
67
  if (parents.length>0) element.parents=parents
69
68
  return element
70
69
  } else return selector
71
70
  }
72
71
  buildElement(element,id=null,tag=null,classList=[]){
73
72
  let query=element
74
73
  element=element.replace(/\#(\w-?)*/,$id=>{
75
74
  id=$id.replace(/^\#/,''); return ''
76
75
  })
77
76
  element=element.replace(/\.(\w-?)*/,$class=>{
78
77
  classList.push($class.replace(/^\./,'')); return ''
79
78
  })
80
79
  element=element.replace(/(\w\:?-?)*/,$tag=>{
81
80
  tag=$tag=='' ? null : $tag; return ''
82
81
  })
83
82
  let attribs=this.getAttributes(element)
84
83
  element={ query }
85
84
  if (id) element.id=id
86
85
  if (tag) element.tag=tag
87
86
  if (classList.length>0) element.classList=classList
88
87
  if (attribs.length>0) element.attribs=attribs
89
88
  return element
90
89
  }
91
90
  getAttributes(element){
92
91
  let attribs=this.stringValues.filter((value,index)=>{
93
92
  let searchValue=`[${index}]`
94
93
  if (element.match(searchValue)) return true
95
94
  else return false
96
95
  })
97
96
  attribs=attribs.map(attrib=>{
98
97
  let query=attrib
99
98
  attrib=attrib.replace('[','').replace(']','')
100
99
  let [name,...values]=attrib.split('=')
101
100
  const value=values.join('=').trim().replace(/^\"/,'').replace(/\"$/,'')
102
101
  let sign
103
102
  attrib={query,name}
104
103
  if(value){
105
104
  sign='='
106
105
  attrib.name=attrib.name.replace(/[\~\|\^\$\*]$/,(match=>{
107
106
  sign=match+sign
108
107
  return ''
109
108
  }))
110
109
  attrib.value=value
111
110
  }
112
111
  if (sign){
113
112
  attrib.sign=sign
114
113
  attrib.check=this.getAttribFn(sign).bind(attrib)
115
114
  }
116
115
  return attrib
117
116
  });
118
117
  return attribs
119
118
  }
120
119
  getAttribFn(sign){
121
120
  if (sign=='=') return function (value){ return value===this.value }
122
121
  if (sign=='*=') return function (value){ return value.includes(this.value) }
123
122
  if (sign=='^=') return function (value){ return value.startsWith(this.value) }
124
123
  if (sign=='$=') return function (value){ return value.endsWith(this.value) }
125
124
  if (sign=='|=') return function (value){
126
125
  return value.trim().split(' ').length==1
127
126
  && (value.startsWith(this.value) || value.startsWith(this.value+'-'))
128
127
  ? true : false
129
128
  }
130
129
  if (sign=='~=') return function (value){
131
130
  return this.value.trim().split(' ').length==1 && value.includes(this.value) ? true : false
132
131
  }
133
132
  }
134
133
  removeSpaces(selector){
135
134
  selector=selector.replace(/\s{2}/g,' ')
136
135
  selector=selector.replace(/\s?\^?\$?\|?\~?\*?\=\s*/g,(m)=>m.trim())
137
136
  selector=selector.replace(/\s?(\+|\~|\>)\s?/g,(m)=>m.trim())
138
137
  return selector
139
138
  }
139
+ class Query{
140
140
  static get(query){
141
141
  let q=new Query(query)
142
142
  return q.selectors
143
143
  }
144
144
  constructor(query){
145
145
  this.query=query
146
146
  this.selectors=[]
147
147
  this.stringValues=[];
148
148
  this.parseSelectors(query.split(','))
149
149
  }
150
150
  parseSelectors(selectors){
151
151
  selectors.forEach(selector=>{
152
152
  let originalSelector=selector.trim()
153
153
  selector=this.removeSpaces(selector)
154
154
  this.stringValues=[]
155
155
  selector=selector.replace(/\[.*?\]/g,(value)=>{
156
156
  this.stringValues.push(value)
157
157
  return `[${this.stringValues.length-1}]`
158
158
  })
159
159
  let [element,ancestors]=this.splitAndCutLast(selector.trim(),/\s+/)
160
160
  element=this.getFamily(element)
161
161
  if (ancestors.length>0)
162
162
  element.ancestors=ancestors.map(ancestor=>this.getFamily(ancestor))
163
163
  element.group=originalSelector
164
164
  this.selectors.push(element)
165
165
  });
166
166
  }
167
167
  splitAndCutLast(string,splitBy){
168
168
  const array=string.split(splitBy);
169
169
  const last=array.pop();
170
170
  return [last,array];
171
171
  }
172
172
  getFamily(group,element,prev,prevAny,sign){
173
173
  if (group.match(/\~|\+/)!==null){
174
174
  let [last,prevBrothers]=this.splitAndCutLast(group,/\~|\+/)
175
175
  let signs=group.replace(last,'')
176
176
  prevBrothers.forEach(el=>signs=signs.replace(el,''))
177
177
  signs=signs.match(/\~|\+/g)
178
178
  if (signs.length==1){
179
179
  sign=signs[0]
180
180
  } else if (signs.length>1){
181
181
  sign=signs.splice(signs.length-1,signs.length-1)[0]
182
182
  prevBrothers[0]=prevBrothers.map((b,i)=>{
183
183
  if (i< prevBrothers.length-1) b+=signs[i]
184
184
  return b
185
185
  }).join('')
186
186
  prevBrothers[0]=this.getFamily(prevBrothers[0])
187
187
  }
188
188
  if (sign=='~') prevAny=prevBrothers[0]
189
189
  else if (sign=='+') prev=prevBrothers[0]
190
190
  element=last
191
191
  } else element=group
192
192
  let family
193
193
  if (prev || prevAny){
194
194
  family=this.getParents(element)
195
195
  if (prev) family.prev=this.getParents(prev)
196
196
  if (prevAny) family.prevAny=this.getParents(prevAny)
197
197
  } else family=this.getParents(element)
198
198
  if (family.query!==group) family.group=group
199
199
  return family
200
200
  }
201
201
  getParents(selector){
202
202
  if (typeof selector=='string'){
203
203
  let [element,parents]=this.splitAndCutLast(selector,'>')
204
204
  element=this.buildElement(element)
205
205
  parents=parents.map(parent=>this.buildElement(parent))
206
206
  if (parents.length>0) element.parents=parents
207
207
  return element
208
208
  } else return selector
209
209
  }
210
210
  buildElement(element,id=null,tag=null,classList=[]){
211
211
  let query=element
212
212
  element=element.replace(/\#(\w-?)*/,$id=>{
213
213
  id=$id.replace(/^\#/,''); return ''
214
214
  })
215
215
  element=element.replace(/\.(\w-?)*/,$class=>{
216
216
  classList.push($class.replace(/^\./,'')); return ''
217
217
  })
218
218
  element=element.replace(/(\w\:?-?)*/,$tag=>{
219
219
  tag=$tag=='' ? null : $tag.toLowerCase(); return ''
220
220
  })
221
221
  let attribs=this.getAttributes(element)
222
222
  element={ query }
223
223
  if (id) element.id=id
224
224
  if (tag) element.tag=tag
225
225
  if (classList.length>0) element.classList=classList
226
226
  if (attribs.length>0) element.attribs=attribs
227
227
  return element
228
228
  }
229
229
  getAttributes(element){
230
230
  let attribs=this.stringValues.filter((value,index)=>{
231
231
  let searchValue=`[${index}]`
232
232
  if (element.match(searchValue)) return true
233
233
  else return false
234
234
  })
235
235
  attribs=attribs.map(attrib=>{
236
236
  let query=attrib
237
237
  attrib=attrib.replace('[','').replace(']','')
238
238
  let [name,...values]=attrib.split('=')
239
239
  const value=values.join('=').trim().replace(/^['"]/,'').replace(/['"]$/,'')
240
240
  let sign
241
241
  attrib={query,name}
242
242
  if(value){
243
243
  sign='='
244
244
  attrib.name=attrib.name.replace(/[\~\|\^\$\*]$/,(match=>{
245
245
  sign=match+sign
246
246
  return ''
247
247
  }))
248
248
  attrib.value=value
249
249
  }
250
250
  if (sign){
251
251
  attrib.sign=sign
252
252
  attrib.check=this.getAttribFn(sign).bind(attrib)
253
253
  }
254
254
  return attrib
255
255
  });
256
256
  return attribs
257
257
  }
258
258
  getAttribFn(sign){
259
259
  if (sign=='=') return function (value){ return value===this.value }
260
260
  if (sign=='*=') return function (value){ return value.includes(this.value) }
261
261
  if (sign=='^=') return function (value){ return value.startsWith(this.value) }
262
262
  if (sign=='$=') return function (value){ return value.endsWith(this.value) }
263
263
  if (sign=='|=') return function (value){
264
264
  return value.trim().split(' ').length==1
265
265
  && (value.startsWith(this.value) || value.startsWith(this.value+'-'))
266
266
  ? true : false
267
267
  }
268
268
  if (sign=='~=') return function (value){
269
269
  return this.value.trim().split(' ').length==1 && value.includes(this.value) ? true : false
270
270
  }
271
271
  }
272
272
  removeSpaces(selector){
273
273
  selector=selector.replace(/\s{2}/g,' ')
274
274
  selector=selector.replace(/\s?\^?\$?\|?\~?\*?\=\s*/g,(m)=>m.trim())
275
275
  selector=selector.replace(/\s?(\+|\~|\>)\s?/g,(m)=>m.trim())
276
276
  return selector
277
277
  }
278
278
  }
279
279
  function checkElement(el,selector){
280
280
  if(selector==undefined) return true
281
281
  if(el==null) return false
282
282
  let{tag,classList,attribs:attributes,id,prev,ancestors,parents,prevAny}=selector
283
283
  if(typeof el==='string') return false
284
284
  if(id!==undefined && el.id===null) return false
285
285
  if(id && id!==el.id) return false
286
286
  if(tag && el._tagName===undefined) return false
287
287
  else if(tag && tag!==el._tagName) return false
288
288
  const clas=el.attributes.class
289
289
  if(classList!==undefined && (clas===undefined || clas==='')) return false
290
290
  else if(classList!==undefined){
291
291
  if(classList.every(e=>el.classList.contains(e))===false) return false
292
292
  }
293
293
  if(checkattributes(attributes,el)===false) return false
294
294
  if(checkElement(el.prev,prev)===false) return false
295
295
  if(checkAncestors(el.ancestors,ancestors)===false) return false
296
296
  if(checkParents(el.ancestors,parents)===false) return false
297
297
  if(el.parent){
298
298
  if(checkPrevAny(el.parent.children,el.childIndex,prevAny)==false) return false
299
299
  }
300
300
  return true
301
301
  }
302
302
  function checkattributes(attributes=[],el){
303
303
  let elattributes=el.attributes
304
304
  let names=Object.keys(elattributes)
305
305
  let passedTests=0
306
306
  if(attributes) for(let i=0; i<attributes.length; i++){
307
307
  let{name,value,check}=attributes[i]
308
308
  if(name=='inner' && value!==undefined && check && el.inner){
309
309
  if(check(el.inner)) passedTests++
310
310
  }
311
311
  if(!names.includes(name)) continue
312
312
  else if(value==undefined) passedTests++
313
313
  else if(value && elattributes[name]){
314
314
  if(check(elattributes[name])==false) continue
315
315
  else passedTests++
316
316
  }
317
317
  }
318
318
  if(passedTests==attributes.length) return true
319
319
  else return false
@@ -15,27 +15,216 @@ function find(selectors,element,collection,first=false,firstTime=true){
15
15
  if(!f
16
16
  class TextNode{
17
17
  constructor(data){
18
18
  this.nodeName='#text';
19
19
  this.parent=null;
20
20
  this.textContent=data;
21
21
  }
22
22
  get nodeValue(){return this.textContent}
23
23
  get parentNode(){return this.parent}
24
24
  }
25
25
 
26
- function buildStyle(attributes){
27
26
  const styles=attributes.style || "";
28
27
  const camelToKebab=str=>str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,'$1-$2').toLowerCase();
29
28
  const kebabToCamel=str=>str.replace(/-([a-z])/g,g=>g[1].toUpperCase());
30
29
  const baseStyleObj=styles.split(";").reduce((acc,style)=>{
31
30
  const [key,value]=style.split(":").map(s=>s.trim());
32
31
  if (key && value) acc[kebabToCamel(key)]=value;
33
32
  return acc;
34
33
  },{});
35
34
  return new Proxy(baseStyleObj,{
36
35
  get: (obj,prop)=>obj[camelToKebab(prop)] || obj[prop],set: (obj,prop,value)=>{
37
36
  obj[camelToKebab(prop)]=value;
38
37
  attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
39
38
  return true;
40
39
  },deleteProperty: (obj,prop)=>{
41
40
  delete obj[camelToKebab(prop)];
42
41
  attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
43
42
  return true;
44
43
  }
45
44
  });
46
- }
45
+ function buildStyle(attributes){
46
+ const styles=attributes.style || "";
47
+ const camelToKebab=str=>str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,'$1-$2').toLowerCase();
48
+ const kebabToCamel=str=>str.replace(/-([a-z])/g,g=>g[1].toUpperCase());
49
+ const splitDeclarations=str=>{
50
+ const out=[];
51
+ let buf="",depth=0,quote=null;
52
+ for (let i=0; i< str.length; i++){
53
+ const c=str[i];
54
+ if (quote){ if (c===quote) quote=null; buf+=c; }
55
+ else if (c==='"' || c==="'"){ quote=c; buf+=c; }
56
+ else if (c==='('){ depth++; buf+=c; }
57
+ else if (c===')'){ if (depth>0) depth--; buf+=c; }
58
+ else if (c===';' && depth===0){ out.push(buf); buf=""; }
59
+ else buf+=c;
60
+ }
61
+ if (buf.trim()) out.push(buf);
62
+ return out;
63
+ };
64
+ const serialize=obj=>Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
65
+ const baseStyleObj=splitDeclarations(styles).reduce((acc,decl)=>{
66
+ const idx=decl.indexOf(":");
67
+ if (idx===-1) return acc;
68
+ const key=decl.slice(0,idx).trim();
69
+ const value=decl.slice(idx+1).trim();
70
+ if (key && value) acc[kebabToCamel(key)]=value;
71
+ return acc;
72
+ },{});
73
+ return new Proxy(baseStyleObj,{
74
+ get: (obj,prop)=>typeof prop==='string' ? obj[kebabToCamel(prop)] : obj[prop],set: (obj,prop,value)=>{
75
+ obj[kebabToCamel(prop)]=value;
76
+ attributes.style=serialize(obj);
77
+ return true;
78
+ },deleteProperty: (obj,prop)=>{
79
+ delete obj[kebabToCamel(prop)];
80
+ attributes.style=serialize(obj);
81
+ return true;
82
+ }
83
+ });
84
+ }
47
85
 
48
86
  class NodeClassList{
49
87
  constructor(node){ this.node=node }
50
88
  get classes(){ return (this.node.attributes.class || "").split(" ").filter(Boolean) }
51
89
  set classes(val){ this.node.attributes.class=val.join(" ") }
52
90
  contains(className){ return this.classes.includes(className) }
53
91
  add(className){
54
92
  const currentClasses=this.classes;
55
93
  if (!currentClasses.includes(className)) this.classes=[...currentClasses,className];
56
94
  }
57
95
  remove(className){ this.classes=this.classes.filter(cls=>cls!==className); }
58
96
  toggle(className){
59
97
  if (this.classes.includes(className)) this.remove(className);
60
98
  else this.add(className);
61
99
  }
62
100
  replace(oldClass,newClass){
63
101
  if (this.classes.includes(oldClass)){
64
102
  this.remove(oldClass);
65
103
  this.add(newClass);
66
104
  }
67
105
  }
68
106
  }
69
107
  function insertBefore(arr,index,newItem){
70
108
  const existingIndex=arr.indexOf(newItem);
71
109
  if (existingIndex!==-1) arr.splice(existingIndex,1);
72
110
  arr.splice(index,0,newItem);
73
- }
74
111
  class Node{
75
112
  constructor(tagName,attributes={},parent=null){
76
113
  this.isSingle=false;
77
114
  this._tagName=tagName.toLowerCase();
78
115
  this.tagName=tagName.toUpperCase();
79
116
  this.attributes=attributes;
80
117
  this.childNodes=[];
81
118
  if (parent!==null) parent.childNodes.push(this)
82
119
  this.parent=parent;
83
120
  this._classList=null;
84
121
  this.__style=null;
85
122
  this._dataset=null
86
123
  }
87
124
  get id(){ return this.attributes.id ? this.attributes.id : null; }
88
125
  set id(newValue){ this.attributes.id=newValue; }
89
126
  get className(){ return this.attributes.class || null }
90
127
  get parentNode(){ return this.parent }
91
128
  get ancestors(){
92
129
  if (!this.parent) return []
93
130
  const ancestors=[]
94
131
  let element=this.parent
95
132
  while (element.tagName!=='ROOT'){
96
133
  ancestors.push(element)
97
134
  element=element.parent
98
135
  }
99
136
  return ancestors.reverse()
100
137
  }
101
138
  get childNodeIndex(){
102
139
  if (!this.parent) return null
103
140
  return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
104
141
  }
105
142
  get childIndex(){
106
143
  if (!this.parent) return null
107
144
  return this.parent.children ? this.parent.children.indexOf(this) : null
108
145
  }
109
146
  get previousElementSibling(){ return this.prev }
110
147
  get prev(){
111
148
  if (this.childIndex===null) return null
112
149
  return this.parent.children[this.childIndex-1]
113
150
  }
114
151
  get nextElementSibling(){ return this.next }
115
152
  get next(){
116
153
  if (this.childIndex===null) return null
117
154
  return this.parent.children[this.childIndex+1] || null
118
155
  }
119
156
  get dataset(){
120
157
  if (!this._dataset) this._dataset=getDataset(this);
121
158
  return this._dataset;
122
159
  }
123
160
  get classList(){
124
161
  if (!this._classList) this._classList=new NodeClassList(this);
125
162
  return this._classList;
126
163
  }
127
164
  get style(){
128
165
  if (!this.__style) this.__style=buildStyle(this.attributes)
129
166
  return this.__style
130
167
  }
131
168
  get outerHTML(){
132
169
  const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
133
170
  return `<${this._tagName}${attrs ? ' '+attrs : ''}>${this.innerHTML}</${this._tagName}>`;
134
171
  }
135
172
  getAttribute(attrName){ return this.attributes[attrName]!==undefined ? this.attributes[attrName] : null }
136
173
  setAttribute(attrName,value=''){ this.attributes[attrName]=value }
137
174
  removeAttribute(attrName){ delete this.attributes[attrName] }
138
175
  remove(){
139
176
  if (!this.parent) return
140
177
  const index=this.childNodeIndex;
141
178
  if (index!==null) this.parent.childNodes.splice(index,1);
142
179
  }
143
180
  get innerHTML(){
144
181
  return this.childNodes.map(child=>{
145
182
  if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
146
183
  else if (child instanceof TextNode) return child.textContent;
147
184
  else return child
148
185
  }).join("");
149
186
  }
150
187
  get innerText(){
151
188
  return this.childNodes.map(child=>{
152
189
  if (child instanceof Node || child instanceof SingleNode) return child.innerText;
153
190
  else if (child instanceof TextNode) return child.textContent;
154
191
  else return child
155
192
  }).join("");
156
193
  }
157
194
  set innerText(value){
158
195
  this.childNodes=[new TextNode(value)]
159
196
  return value
160
197
  }
161
198
  $$(query){ return this.querySelectorAll(query) }
162
199
  querySelectorAll(query){
163
200
  const selectors=Query.get(query)
164
201
  return find(selectors,this,new Set())
165
202
  }
166
203
  $(query){ return this.querySelector(query) }
167
204
  querySelector(query){
168
205
  const selectors=Query.get(query)
169
206
  return find(selectors,this,new Set(),true)[0] || null
170
207
  }
171
208
  getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
172
209
  getElementsByTagName(query){ return this.querySelectorAll(query) }
173
210
  getElementById(query){ return this.querySelector('#'+query) }
174
211
  get children(){
175
212
  return this.childNodes.filter(child=>{
176
213
  if (!(child instanceof Node)) return false
177
214
  if (child._tagName==='#comment') return false
178
215
  return true
179
216
  });
180
217
  }
181
218
  insertAdjacentElement(position,newElement){
182
219
  if (newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
183
220
  const pos=position.toLowerCase();
184
221
  if(newElement.parentNode){
185
222
  newElement.parentNode.childNodes=newElement.parentNode.childNodes.filter(el=>el!==newElement)
186
223
  }
187
224
  if (pos==='afterbegin' || pos==='beforeend'){
188
225
  if (pos==="afterbegin") this.childNodes.unshift(newElement);
189
226
  else if (pos==="beforeend") this.childNodes.push(newElement);
190
227
  newElement.parent=this
191
228
  return newElement
192
229
  }
193
230
  if (!this.parent) throw new Error("Can't insert element to element without parent")
194
231
  if (pos==="beforebegin") insertBefore(this.parent.childNodes,this.childNodeIndex,newElement)
195
232
  else if (pos==="afterend") this.parent.childNodes.splice(this.childNodeIndex+1,0,newElement);
196
233
  newElement.parent=this.parent
197
234
  return newElement
198
235
  }
199
236
  insertAdjacentHTML(position,html){
200
237
  const newNode=parseHTML(html);
201
238
  newNode.childNodes.forEach(node=>{
202
239
  this.insertAdjacentElement(position,node);
203
240
  });
204
241
  return newNode
205
242
  }
206
243
  insertAdjacentText(position,text){
207
244
  return this.insertAdjacentElement(position,new TextNode(text));
208
245
  }
209
246
  insert(position,element){
210
247
  const positions=['beforebegin','afterbegin','beforeend','afterend']
211
248
  if (positions[position]) position=positions[position]
212
249
  if (typeof element==='string'){
213
250
  element=element.trim()
214
251
  if (element.startsWith('<') && element.endsWith('>')){
215
252
  return this.insertAdjacentHTML(position,element)
216
253
  }
217
254
  return this.insertAdjacentText(position,element)
218
255
  }
219
256
  return this.insertAdjacentElement(position,element)
220
257
  }
221
258
  set innerHTML(html){
222
259
  this.childNodes=html.trim().startsWith('<') ? parseHTML(html).childNodes : [html]
223
260
  this.children.forEach(child=>child.parent=this);
224
261
  }
225
262
  set outerHTML(html){
226
263
  const parsed=parseHTML(html);
227
264
  if (!this.parent) throw new Error('element has no parent node')
228
265
  const index=this.childIndex
229
266
  if (index!==null) this.parent.childNodes.splice(index,1,...parsed.childNodes);
230
267
  }
231
268
  appendChild(newChild){
232
269
  if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
233
270
  if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
234
271
  } else if (typeof newChild==='string') newChild=new TextNode(newChild)
235
272
  else return newChild
236
273
  this.childNodes.push(newChild);
237
274
  newChild.parent=this;
238
275
  return newChild;
239
276
  }
240
277
  get textContent(){
241
278
  if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
242
279
  return this.childNodes.map(child=>{
243
280
  if (child instanceof SingleNode) return ''
244
281
  if (child instanceof TextNode) return child.nodeValue
245
282
  if (child instanceof Node) return child.textContent;
246
283
  else return child;
247
284
  }).join('');
248
285
  }
249
286
  set textContent(value){
250
287
  this.childNodes=[];
251
288
  if (value!==null && value!==undefined){
252
289
  this.childNodes.push(value.toString());
253
290
  }
254
291
  }
292
+ }
255
293
  class Node{
256
294
  constructor(tagName,attributes={},parent=null){
257
295
  this.isSingle=false;
258
296
  this._tagName=tagName.toLowerCase();
259
297
  this.tagName=tagName.toUpperCase();
260
298
  this.attributes=attributes;
261
299
  this.childNodes=[];
262
300
  if (parent!==null) parent.childNodes.push(this)
263
301
  this.parent=parent;
264
302
  this._classList=null;
265
303
  this.__style=null;
266
304
  this._dataset=null
267
305
  }
268
306
  get id(){ return this.attributes.id ? this.attributes.id : null; }
269
307
  set id(newValue){ this.attributes.id=newValue; }
270
308
  get className(){ return this.attributes.class || null }
271
309
  get parentNode(){ return this.parent }
272
310
  get ancestors(){
273
311
  if (!this.parent) return []
274
312
  const ancestors=[]
275
313
  let element=this.parent
276
314
  while (element.tagName!=='ROOT'){
277
315
  ancestors.push(element)
278
316
  element=element.parent
279
317
  }
280
318
  return ancestors.reverse()
281
319
  }
282
320
  get childNodeIndex(){
283
321
  if (!this.parent) return null
284
322
  return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
285
323
  }
286
324
  get childIndex(){
287
325
  if (!this.parent) return null
288
326
  return this.parent.children ? this.parent.children.indexOf(this) : null
289
327
  }
290
328
  get previousElementSibling(){ return this.prev }
291
329
  get prev(){
292
330
  if (this.childIndex===null) return null
293
331
  return this.parent.children[this.childIndex-1]
294
332
  }
295
333
  get nextElementSibling(){ return this.next }
296
334
  get next(){
297
335
  if (this.childIndex===null) return null
298
336
  return this.parent.children[this.childIndex+1] || null
299
337
  }
300
338
  get dataset(){
301
339
  if (!this._dataset) this._dataset=getDataset(this);
302
340
  return this._dataset;
303
341
  }
304
342
  get classList(){
305
343
  if (!this._classList) this._classList=new NodeClassList(this);
306
344
  return this._classList;
307
345
  }
308
346
  get style(){
309
347
  if (!this.__style) this.__style=buildStyle(this.attributes)
310
348
  return this.__style
311
349
  }
312
350
  get outerHTML(){
313
351
  const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
314
352
  return `<${this._tagName}${attrs ? ' '+attrs : ''}>${this.innerHTML}</${this._tagName}>`;
315
353
  }
316
354
  getAttribute(attrName){ return this.attributes[attrName]!==undefined ? this.attributes[attrName] : null }
317
355
  setAttribute(attrName,value=''){ this.attributes[attrName]=value }
318
356
  removeAttribute(attrName){ delete this.attributes[attrName] }
319
357
  remove(){
320
358
  if (!this.parent) return
321
359
  const index=this.childNodeIndex;
322
360
  if (index!==null) this.parent.childNodes.splice(index,1);
323
361
  }
324
362
  get innerHTML(){
325
363
  return this.childNodes.map(child=>{
326
364
  if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
327
365
  else if (child instanceof TextNode) return child.textContent;
328
366
  else return child
329
367
  }).join("");
330
368
  }
331
369
  get innerText(){
332
370
  return this.childNodes.map(child=>{
333
371
  if (child instanceof Node || child instanceof SingleNode) return child.innerText;
334
372
  else if (child instanceof TextNode) return child.textContent;
335
373
  else return child
336
374
  }).join("");
337
375
  }
338
376
  set innerText(value){
339
377
  this.childNodes=[new TextNode(value)]
340
378
  return value
341
379
  }
342
380
  $$(query){ return this.querySelectorAll(query) }
343
381
  querySelectorAll(query){
344
382
  const selectors=Query.get(query)
345
383
  return find(selectors,this,new Set())
346
384
  }
347
385
  $(query){ return this.querySelector(query) }
348
386
  querySelector(query){
349
387
  const selectors=Query.get(query)
350
388
  return find(selectors,this,new Set(),true)[0] || null
351
389
  }
352
390
  getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
353
391
  getElementsByTagName(query){ return this.querySelectorAll(query) }
354
392
  getElementById(query){ return this.querySelector('#'+query) }
355
393
  get children(){
356
394
  return this.childNodes.filter(child=>{
357
395
  if (!(child instanceof Node)) return false
358
396
  if (child._tagName==='#comment') return false
359
397
  return true
360
398
  });
361
399
  }
362
400
  insertAdjacentElement(position,newElement){
363
401
  if (newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
364
402
  const pos=position.toLowerCase();
365
403
  if(newElement.parentNode){
366
404
  newElement.parentNode.childNodes=newElement.parentNode.childNodes.filter(el=>el!==newElement)
367
405
  }
368
406
  if (pos==='afterbegin' || pos==='beforeend'){
369
407
  if (pos==="afterbegin") this.childNodes.unshift(newElement);
370
408
  else if (pos==="beforeend") this.childNodes.push(newElement);
371
409
  newElement.parent=this
372
410
  return newElement
373
411
  }
374
412
  if (!this.parent) throw new Error("Can't insert element to element without parent")
375
413
  if (pos==="beforebegin") insertBefore(this.parent.childNodes,this.childNodeIndex,newElement)
376
414
  else if (pos==="afterend") this.parent.childNodes.splice(this.childNodeIndex+1,0,newElement);
377
415
  newElement.parent=this.parent
378
416
  return newElement
379
417
  }
380
418
  insertAdjacentHTML(position,html){
381
419
  const newNode=parseHTML(html);
382
420
  const nodes=[...newNode.childNodes];
383
421
  const pos=position.toLowerCase();
384
422
  if (pos==='afterbegin' || pos==='afterend') nodes.reverse();
385
423
  nodes.forEach(node=>{
386
424
  this.insertAdjacentElement(position,node);
387
425
  });
388
426
  return newNode
389
427
  }
390
428
  insertAdjacentText(position,text){
391
429
  return this.insertAdjacentElement(position,new TextNode(text));
392
430
  }
393
431
  insert(position,element){
394
432
  const positions=['beforebegin','afterbegin','beforeend','afterend']
395
433
  if (positions[position]) position=positions[position]
396
434
  if (typeof element==='string'){
397
435
  element=element.trim()
398
436
  if (element.startsWith('<') && element.endsWith('>')){
399
437
  return this.insertAdjacentHTML(position,element)
400
438
  }
401
439
  return this.insertAdjacentText(position,element)
402
440
  }
403
441
  return this.insertAdjacentElement(position,element)
404
442
  }
405
443
  set innerHTML(html){
406
444
  this.childNodes=html.trim().startsWith('<') ? parseHTML(html).childNodes : [html]
407
445
  this.children.forEach(child=>child.parent=this);
408
446
  }
409
447
  set outerHTML(html){
410
448
  const parsed=parseHTML(html);
411
449
  if (!this.parent) throw new Error('element has no parent node')
412
450
  const index=this.childNodeIndex
413
451
  if (index!==null){
414
452
  const nodes=parsed.childNodes
415
453
  nodes.forEach(node=>node.parent=this.parent)
416
454
  this.parent.childNodes.splice(index,1,...nodes)
417
455
  }
418
456
  }
419
457
  appendChild(newChild){
420
458
  if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
421
459
  if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
422
460
  } else if (typeof newChild==='string') newChild=new TextNode(newChild)
423
461
  else return newChild
424
462
  this.childNodes.push(newChild);
425
463
  newChild.parent=this;
426
464
  return newChild;
427
465
  }
428
466
  get textContent(){
429
467
  if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
430
468
  return this.childNodes.map(child=>{
431
469
  if (child instanceof SingleNode) return ''
432
470
  if (child instanceof TextNode) return child.nodeValue
433
471
  if (child instanceof Node) return child.textContent;
434
472
  else return child;
435
473
  }).join('');
436
474
  }
437
475
  set textContent(value){
438
476
  this.childNodes=[];
439
477
  if (value!==null && value!==undefined){
440
478
  this.childNodes.push(value.toString());
441
479
  }
442
480
  }
443
481
  }
444
482
  class SingleNode extends Node{
445
483
  constructor(tagName,attributes={},parent=null){
446
484
  if(attributes['?'] && tagName==='?xml') delete attributes['?']
447
485
  super(tagName,attributes,parent);
448
486
  this.isSingle=true
449
487
  }
450
488
  get outerHTML(){
451
489
  if (this._tagName==="#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
452
490
  const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
453
491
  return `<${this._tagName} ${attrs}${this._tagName==='?xml' ? '?' : ''}>`;
454
492
  }
455
493
  get innerHTML(){ return ""; }
456
494
  set innerHTML(_){ }
457
495
  $(_){return null}
458
496
  $$(_){return []}
459
497
  querySelectorAll(_){ return []; }
460
498
  querySelector(_){ return null; }
461
499
  getElementsByClassName(_){ return []; }
462
500
  getElementsByTagName(_){ return []; }
463
501
  getElementById(_){ return null; }
464
502
  get children(){ return []; }
465
503
  insertAdjacentElement(position,newElement){
466
504
  if(position==='afterbegin') position='beforebegin'
467
505
  if(position==='beforeend') position='afterend'
468
506
  return super.insertAdjacentElement(position,newElement)
469
507
  }
470
508
  insertAdjacentHTML(position,html){
471
509
  if(position==='afterbegin') position='beforebegin'
472
510
  if(position==='beforeend') position='afterend'
473
511
  return super.insertAdjacentHTML(position,html)
474
512
  }
475
513
  insertAdjacentText(position,text){
476
514
  if(position==='afterbegin') position='beforebegin'
477
515
  if(position==='beforeend') position='afterend'
478
516
  return super.insertAdjacentText(position,text)
479
517
  }
480
518
  insert(position,element){
481
519
  if(position===1) position=0
482
520
  if(position===2) position=3
483
521
  return super.insert(position,element)
484
522
  }
485
523
  appendChild(_){ }
486
524
  get textContent(){ return ""; }
487
525
  set textContent(_){ }
488
526
  }
489
527
 
490
528
  class Root extends Node{
491
529
  constructor(){
492
530
  super('ROOT',{},null);
493
531
  this.isSingle=false
494
532
  }
495
533
  }
496
- class Document extends Node{
497
534
  constructor(html,url){
498
535
  super('ROOT',{},null);
499
536
  this.isSingle=false
500
537
  this.URL=url
501
538
  if(html instanceof Document) this.childNodes=[...buildFromCache(cacheDoc(html)).childNodes]
502
539
  else if(typeof html==='string') this.innerHTML=html
503
540
  else this.html
504
541
  }
505
542
  get documentElement(){return this.html}
506
543
  get html(){
507
544
  const html=this.$('html')
508
545
  if(html===null) this.innerHTML=/*html*/`<html lang="en"><head><meta charset="UTF-8"><title></title></head><body></body></html>`
509
546
  return this.$('html')
510
547
  }
511
548
  get innerHTML(){
512
549
  const inner=super.innerHTML
513
550
  return '<!DOCTYPE html>'+inner
514
551
  }
515
552
  set innerHTML(html){return super.innerHTML=html}
516
553
  get head(){
517
554
  const head=this.html.$('head')
518
555
  if(head===null) this.html.insert(1,'<head></head>')
519
556
  return this.$('head')
520
557
  }
521
558
  get body(){
522
559
  const body=this.html.$('body')
523
560
  if(body===null) this.head.insert(3,'<body></body>')
524
561
  return this.$('body')
525
562
  }
526
563
  get title(){
527
564
  const title=this.head.$('title')
528
565
  if(title===null) this.head.insert(1,'<title></title>')
529
566
  return this.$('title').innerText
530
567
  }
531
568
  get charset(){return this.$('meta[charset]')}
532
569
  set title(title){return this.head.$('title').innerText=title}
533
570
  get clone(){return new Document(this)}
571
+ class Document extends Node{
534
572
  constructor(html,url){
535
573
  super('ROOT',{},null);
536
574
  this.isSingle=false
537
575
  this.URL=url
538
576
  if(html instanceof Document){
539
577
  this.childNodes=[...buildFromCache(cacheDoc(html)).childNodes]
540
578
  this.childNodes.forEach(child=>{ if(child instanceof Node || child instanceof TextNode) child.parent=this })
541
579
  }
542
580
  else if(typeof html==='string') this.innerHTML=html
543
581
  else this.html
544
582
  }
545
583
  get documentElement(){return this.html}
546
584
  get html(){
547
585
  let html=this.$('html')
548
586
  if(html===null){
549
587
  const existing=this.childNodes
550
588
  this.innerHTML=/*html*/`<html lang="en"><head><meta charset="UTF-8"><title></title></head><body></body></html>`
551
589
  html=this.$('html')
552
590
  if(existing.length){
553
591
  const body=this.$('body')
554
592
  existing.forEach(child=>{
555
593
  body.childNodes.push(child)
556
594
  if(child instanceof Node || child instanceof TextNode) child.parent=body
557
595
  })
558
596
  }
559
597
  }
560
598
  return html
561
599
  }
562
600
  get innerHTML(){
563
601
  const inner=super.innerHTML
564
602
  return '<!DOCTYPE html>'+inner
565
603
  }
566
604
  set innerHTML(html){return super.innerHTML=html}
567
605
  get head(){
568
606
  const head=this.html.$('head')
569
607
  if(head===null) this.html.insert(1,'<head></head>')
570
608
  return this.$('head')
571
609
  }
572
610
  get body(){
573
611
  const body=this.html.$('body')
574
612
  if(body===null) this.head.insert(3,'<body></body>')
575
613
  return this.$('body')
576
614
  }
577
615
  get title(){
578
616
  const title=this.head.$('title')
579
617
  if(title===null) this.head.insert(1,'<title></title>')
580
618
  return this.$('title').innerText
581
619
  }
582
620
  get charset(){return this.$('meta[charset]')}
583
621
  set title(title){
584
622
  if(this.head.$('title')===null) this.head.insert(1,'<title></title>')
585
623
  this.head.$('title').innerText=title
586
624
  }
587
625
  get clone(){return new Document(this,this.URL)}
588
626
  }
589
- function parseAttributes(str){
590
627
  const attrs={};
591
628
  let key="";
592
629
  let value="";
593
630
  let isKey=true;
594
631
  let quoteChar=null;
595
632
  for (let i=0; i< str.length; i++){
596
633
  const char=str[i];
597
634
  if (isKey && (char==='=' || char===' ')){
598
635
  if (char==='=') isKey=false;
599
636
  else if (key.trim()){
600
637
  attrs[key.trim()]=true;
601
638
  key="";
602
639
  }
603
640
  continue;
604
641
  }
605
642
  if (!quoteChar && (char==='"' || char==="'")){
606
643
  quoteChar=char;
607
644
  continue;
608
645
  } else if (quoteChar && char===quoteChar){
609
646
  quoteChar=null;
610
647
  attrs[key.trim()]=value.trim();
611
648
  key=""; value=""; isKey=true;
612
649
  continue;
613
650
  }
614
651
  if (isKey) key+=char;
615
652
  else value+=char;
616
653
  }
617
654
  if (key.trim() &&!value) attrs[key.trim()]='';
618
655
  return attrs;
656
+ function parseAttributes(str){
657
+ const attrs={};
658
+ const len=str.length;
659
+ const isWS=c=>c===' ' || c==='\t' || c==='\n' || c==='\r' || c==='\f';
660
+ let i=0;
661
+ while (i< len){
662
+ while (i< len && isWS(str[i])) i++;
663
+ if (i>=len) break;
664
+ let name="";
665
+ while (i< len &&!isWS(str[i]) && str[i]!=='='){ name+=str[i]; i++; }
666
+ if (!name){ i++; continue; }
667
+ while (i< len && isWS(str[i])) i++;
668
+ if (str[i]==='='){
669
+ i++;
670
+ while (i< len && isWS(str[i])) i++;
671
+ let value="";
672
+ const quote=str[i];
673
+ if (quote==='"' || quote==="'"){
674
+ i++;
675
+ while (i< len && str[i]!==quote){ value+=str[i]; i++; }
676
+ i++;
677
+ } else{
678
+ while (i< len &&!isWS(str[i])){ value+=str[i]; i++; }
679
+ }
680
+ attrs[name]=value.trim();
681
+ } else{
682
+ attrs[name]='';
683
+ }
684
+ }
685
+ return attrs;
619
686
  }
687
+
620
688
  const VOID_TAGS=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","!doctype",'?xml']);
621
- function parseHTML(html){
622
689
  const root=new Root();
623
690
  const stack=[root];
624
691
  let currentText="",i=0;
625
692
  let max=0
626
693
  function parseScript(){
627
694
  if (!html.startsWith("<script",i)) return false;
628
695
  const openTagEnd=html.indexOf(">",i);
629
696
  if (openTagEnd===-1) return false;
630
697
  const attributesString=html.substring(i+7,openTagEnd).trim();
631
698
  const attributes=parseAttributes(attributesString);
632
699
  let closeTagStart=html.indexOf("</script>",openTagEnd);
633
700
  if (closeTagStart===-1) return false;
634
701
  const content=html.substring(openTagEnd+1,closeTagStart);
635
702
  const scriptNode=new Node('script',attributes,stack[stack.length-1]);
636
703
  if(content.length>0) scriptNode.childNodes.push(content);
637
704
  i=closeTagStart+9;
638
705
  return true;
639
706
  }
640
707
  function parseSpecial(startStr,endStr,n1,n2,tag){
641
708
  if (!html.startsWith(startStr,i)) return false
642
709
  if(currentText.length) stack[stack.length-1].childNodes.push(new TextNode(currentText));
643
710
  const end=html.indexOf(endStr,i+n1);
644
711
  const strNode=new Node(tag,{},stack[stack.length-1]);
645
712
  strNode.childNodes.push(html.substring(i+n1,end));
646
713
  i=end+n2;
647
714
  return true
648
715
  }
649
716
  while (i< html.length){
650
717
  if(i>=max) max=i;
651
718
  else break;
652
719
  if (parseScript()) continue
653
720
  if (parseSpecial("<!--","-->",4,3,'#comment')) continue
654
721
  if (parseSpecial("<style","</style>",7,8,'style')) continue
655
722
  if (html.startsWith("<![CDATA[",i)){
656
723
  const end=html.indexOf("]]>",i+9);
657
724
  if (end===-1) break;
658
725
  const content=html.substring(i+9,end);
659
726
  const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
660
727
  cdataNode.nodeValue=content;
661
728
  i=end+3;
662
729
  continue;
663
730
  }
664
731
  if (html.startsWith("<",i)){
665
732
  if (currentText && stack[stack.length-1]){
666
733
  const textNode=new TextNode(currentText)
667
734
  stack[stack.length-1].childNodes.push(textNode);
668
735
  textNode.parent=stack[stack.length-1]
669
736
  currentText="";
670
737
  }
671
738
  let tagEnd=i+1;
672
739
  let insideQuotes=false;
673
740
  let quoteChar=null;
674
741
  while (tagEnd< html.length){
675
742
  const char=html[tagEnd];
676
743
  if (!insideQuotes && (char==='"' || char==="'")){
677
744
  insideQuotes=true;
678
745
  quoteChar=char;
679
746
  } else if (insideQuotes && char===quoteChar){
680
747
  insideQuotes=false;
681
748
  quoteChar=null;
682
749
  }
683
750
  if (!insideQuotes && char==='>') break;
684
751
  tagEnd++;
685
752
  }
686
753
  const tagContent=html.substring(i+1,tagEnd);
687
754
  if (tagContent.startsWith("/")) stack.pop();
688
755
  else{
689
756
  let isSelfClosing=tagContent.endsWith('/');
690
757
  const tagNameEnd=tagContent.search(/\s|>|\//);
691
758
  const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
692
759
  const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
693
760
  const attributes=parseAttributes(attributesString);
694
761
  if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
695
762
  else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
696
763
  }
697
764
  i=tagEnd+1;
698
765
  } else{
699
766
  currentText+=html[i];
700
767
  i++;
701
768
  }
702
769
  }
703
770
  if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
704
771
  return root;
772
+ function parseHTML(html){
773
+ const root=new Root();
774
+ const stack=[root];
775
+ let currentText="",i=0;
776
+ let max=0
777
+ const isWS=c=>c===' ' || c==='\t' || c==='\n' || c==='\r' || c==='\f';
778
+ function flushText(){
779
+ if (currentText){
780
+ const textNode=new TextNode(currentText)
781
+ stack[stack.length-1].childNodes.push(textNode)
782
+ textNode.parent=stack[stack.length-1]
783
+ currentText=""
784
+ }
785
+ }
786
+ function startsWithCI(str,pos){
787
+ return html.substr(pos,str.length).toLowerCase()===str;
788
+ }
789
+ function indexOfCI(needle,from){
790
+ const nlen=needle.length
791
+ const limit=html.length-nlen
792
+ for (let j=from; j<=limit; j++){
793
+ let ok=true
794
+ for (let k=0; k< nlen; k++){
795
+ let c=html[j+k]
796
+ if (c>='A' && c<='Z') c=c.toLowerCase()
797
+ if (c!==needle[k]){ ok=false; break }
798
+ }
799
+ if (ok) return j
800
+ }
801
+ return-1
802
+ }
803
+ function scanTagEnd(from){
804
+ let j=from+1
805
+ let inQuotes=false,q=null
806
+ while (j< html.length){
807
+ const c=html[j]
808
+ if (!inQuotes && (c==='"' || c==="'")){ inQuotes=true; q=c }
809
+ else if (inQuotes && c===q){ inQuotes=false; q=null }
810
+ if (!inQuotes && c==='>') return j
811
+ j++
812
+ }
813
+ return-1
814
+ }
815
+ function parseRawText(tagName){
816
+ if (html[i]!=='<') return false
817
+ const open="<"+tagName
818
+ if (!startsWithCI(open,i)) return false
819
+ const after=html[i+open.length]
820
+ if (after!==undefined &&!isWS(after) && after!=='>' && after!=='/') return false
821
+ const openTagEnd=scanTagEnd(i)
822
+ if (openTagEnd===-1) return false
823
+ flushText()
824
+ let attributesString=html.substring(i+open.length,openTagEnd).trim()
825
+ if (attributesString.endsWith('/')) attributesString=attributesString.slice(0,-1).trim()
826
+ const attributes=parseAttributes(attributesString)
827
+ const closeTag="</"+tagName+">"
828
+ const closeStart=indexOfCI(closeTag,openTagEnd+1)
829
+ const missing=closeStart===-1
830
+ const content=html.substring(openTagEnd+1,missing ? html.length : closeStart)
831
+ const node=new Node(tagName,attributes,stack[stack.length-1]);
832
+ if (content.length>0) node.childNodes.push(content);
833
+ i=missing ? html.length : closeStart+closeTag.length
834
+ return true
835
+ }
836
+ function parseSpecial(startStr,endStr,n1,n2,tag){
837
+ if (!html.startsWith(startStr,i)) return false
838
+ flushText()
839
+ let end=html.indexOf(endStr,i+n1);
840
+ const missing=end===-1
841
+ if (missing) end=html.length
842
+ const strNode=new Node(tag,{},stack[stack.length-1]);
843
+ strNode.childNodes.push(html.substring(i+n1,end));
844
+ i=missing ? end : end+n2;
845
+ return true
846
+ }
847
+ while (i< html.length){
848
+ if (i>=max) max=i;
849
+ else break;
850
+ if (parseRawText('script')) continue
851
+ if (parseSpecial("<!--","-->",4,3,'#comment')) continue
852
+ if (parseRawText('style')) continue
853
+ if (html.startsWith("<![CDATA[",i)){
854
+ if (currentText.trim()) flushText()
855
+ let end=html.indexOf("]]>",i+9);
856
+ const missing=end===-1
857
+ if (missing) end=html.length
858
+ const content=html.substring(i+9,end);
859
+ const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
860
+ cdataNode.nodeValue=content;
861
+ i=missing ? end : end+3;
862
+ continue;
863
+ }
864
+ if (html.startsWith("<",i)){
865
+ flushText()
866
+ const tagEnd=scanTagEnd(i);
867
+ const realEnd=tagEnd===-1 ? html.length : tagEnd;
868
+ const tagContent=html.substring(i+1,realEnd);
869
+ if (tagContent.startsWith("/")){
870
+ const closeName=tagContent.slice(1).trim().toLowerCase();
871
+ let idx=-1;
872
+ for (let s=stack.length-1; s>=1; s--){
873
+ if (stack[s]._tagName===closeName){ idx=s; break }
874
+ }
875
+ if (idx!==-1) stack.length=idx;
876
+ } else{
877
+ let isSelfClosing=tagContent.endsWith('/');
878
+ const tagNameEnd=tagContent.search(/\s|>|\//);
879
+ const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : realEnd-i-1);
880
+ const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
881
+ const attributes=parseAttributes(attributesString);
882
+ if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
883
+ else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
884
+ }
885
+ i=realEnd+1;
886
+ } else{
887
+ currentText+=html[i];
888
+ i++;
889
+ }
890
+ }
891
+ if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
892
+ return root;
705
893
  }
706
- function buildFromCache(cached){
707
894
  function buildNode(cache,parent=null){
708
895
  if(typeof cache==='string') return parent.childNodes.push(cache)
709
896
  const{isSingle,tagName,attributes,childNodes,textContent}=cache
710
897
  if(textContent) return parent.childNodes.push(new TextNode(textContent))
711
898
  if(isSingle && parent) return parent.childNodes.push(new SingleNode(tagName,attributes))
712
899
  const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
713
900
  childNodes.forEach(childNode=>{
714
901
  buildNode(childNode,newDoc)
715
902
  });
716
903
  return newDoc
717
904
  }
718
905
  return buildNode(cached)
906
+
907
+ function buildFromCache(cached){
719
908
  function buildNode(cache,parent=null){
720
909
  if(typeof cache==='string') return parent.childNodes.push(cache)
721
910
  const{isSingle,tagName,attributes,childNodes,textContent}=cache
722
911
  if(textContent!==undefined){
723
912
  const textNode=new TextNode(textContent)
724
913
  textNode.parent=parent
725
914
  return parent.childNodes.push(textNode)
726
915
  }
727
916
  if(isSingle && parent) return new SingleNode(tagName,attributes,parent)
728
917
  const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
729
918
  childNodes.forEach(childNode=>{
730
919
  buildNode(childNode,newDoc)
731
920
  });
732
921
  return newDoc
733
922
  }
734
923
  return buildNode(cached)
735
924
  }
736
925
 
737
926
  function cacheDoc(doc){
738
927
  const props=['isSingle','tagName','attributes']
739
928
  function addToCache(element,cache={}){
740
929
  if(typeof element==='string') return element
741
930
  if(element.nodeName==='#text') return{textContent:element.textContent}
742
931
  props.forEach(prop=>{
743
932
  if(element[prop]){
744
933
  cache[prop]=typeof element[prop]==='object' ?{...element[prop]} : element[prop]
745
934
  }
746
935
  });
747
936
  if(!element.childNodes) return cache
748
937
  cache.childNodes=[]
749
938
  element.childNodes.forEach(childNode=>{
750
939
  cache.childNodes.push(addToCache(childNode))
751
940
  });
752
941
  return cache
753
942
  }
754
943
  return addToCache(doc)
755
944
  }