als-document 1.2.1 → 1.3.1

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