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