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