als-document 1.4.0 → 1.4.2

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
@@ -23,7 +23,7 @@ class NodeClassList{
23
23
  constructor(node){ this.node=node }
24
24
  get classes(){ re
25
25
  function insertBefore(arr,index,newItem){
26
26
  const existingIndex=arr.indexOf(newItem);
27
27
  if (existingIndex!==-1) arr.splice(existingIndex,1);
28
28
  arr.splice(index,0,newItem);
29
29
  }
30
30
  class Node{
31
31
  constructor(tagName,attributes={},parent=null){
32
32
  this.isSingle=false;
33
33
  this._tagName=tagName.toLowerCase();
34
34
  this.tagName=tagName.toUpperCase();
35
35
  this.attributes=attributes;
36
36
  this.childNodes=[];
37
37
  if (parent!==null) parent.childNodes.push(this)
38
38
  this.parent=parent;
39
39
  this._classList=null;
40
40
  this.__style=null;
41
41
  this._dataset=null
42
42
  }
43
43
  get id(){ return this.attributes.id ? this.attributes.id : null; }
44
44
  set id(newValue){ this.attributes.id=newValue; }
45
45
  get className(){ return this.attributes.class || null }
46
46
  get parentNode(){ return this.parent }
47
47
  get ancestors(){
48
48
  if (!this.parent) return []
49
49
  const ancestors=[]
50
50
  let element=this.parent
51
51
  while (element.tagName!=='ROOT'){
52
52
  ancestors.push(element)
53
53
  element=element.parent
54
54
  }
55
55
  return ancestors.reverse()
56
56
  }
57
57
  get childNodeIndex(){
58
58
  if (!this.parent) return null
59
59
  return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
60
60
  }
61
61
  get childIndex(){
62
62
  if (!this.parent) return null
63
63
  return this.parent.children ? this.parent.children.indexOf(this) : null
64
64
  }
65
65
  get previousElementSibling(){ return this.prev }
66
66
  get prev(){
67
67
  if (this.childIndex===null) return null
68
68
  return this.parent.children[this.childIndex-1]
69
69
  }
70
70
  get nextElementSibling(){ return this.next }
71
71
  get next(){
72
72
  if (this.childIndex===null) return null
73
73
  return this.parent.children[this.childIndex+1] || null
74
74
  }
75
75
  get dataset(){
76
76
  if (!this._dataset) this._dataset=getDataset(this);
77
77
  return this._dataset;
78
78
  }
79
79
  get classList(){
80
80
  if (!this._classList) this._classList=new NodeClassList(this);
81
81
  return this._classList;
82
82
  }
83
83
  get style(){
84
84
  if (!this.__style) this.__style=buildStyle(this.attributes)
85
85
  return this.__style
86
86
  }
87
87
  get outerHTML(){
88
88
  const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
89
89
  return `<${this._tagName}${attrs ? ' '+attrs : ''}>${this.innerHTML}</${this._tagName}>`;
90
90
  }
91
91
  getAttribute(attrName){ return this.attributes[attrName]!==undefined ? this.attributes[attrName] : null }
92
92
  setAttribute(attrName,value=''){ this.attributes[attrName]=value }
93
93
  removeAttribute(attrName){ delete this.attributes[attrName] }
94
94
  remove(){
95
95
  if (!this.parent) return
96
96
  const index=this.childNodeIndex;
97
97
  if (index!==null) this.parent.childNodes.splice(index,1);
98
98
  }
99
99
  get innerHTML(){
100
100
  return this.childNodes.map(child=>{
101
101
  if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
102
102
  else if (child instanceof TextNode) return child.textContent;
103
103
  else return child
104
104
  }).join("");
105
105
  }
106
106
  get innerText(){
107
107
  return this.childNodes.map(child=>{
108
108
  if (child instanceof Node || child instanceof SingleNode) return child.innerText;
109
109
  else if (child instanceof TextNode) return child.textContent;
110
110
  else return child
111
111
  }).join("");
112
112
  }
113
113
  set innerText(value){
114
114
  this.childNodes=[new TextNode(value)]
115
115
  return value
116
116
  }
117
117
  $$(query){ return this.querySelectorAll(query) }
118
118
  querySelectorAll(query){
119
119
  const selectors=Query.get(query)
120
120
  return find(selectors,this,new Set())
121
121
  }
122
122
  $(query){ return this.querySelector(query) }
123
123
  querySelector(query){
124
124
  const selectors=Query.get(query)
125
125
  return find(selectors,this,new Set(),true)[0] || null
126
126
  }
127
127
  getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
128
128
  getElementsByTagName(query){ return this.querySelectorAll(query) }
129
129
  getElementById(query){ return this.querySelector('#'+query) }
130
130
  get children(){
131
131
  return this.childNodes.filter(child=>{
132
132
  if (!(child instanceof Node)) return false
133
133
  if (child._tagName==='#comment') return false
134
134
  return true
135
135
  });
136
136
  }
137
137
  insertAdjacentElement(position,newElement){
138
138
  if (newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
139
139
  const pos=position.toLowerCase();
140
140
  if(newElement.parentNode){
141
141
  newElement.parentNode.childNodes=newElement.parentNode.childNodes.filter(el=>el!==newElement)
142
142
  }
143
143
  if (pos==='afterbegin' || pos==='beforeend'){
144
144
  if (pos==="afterbegin") this.childNodes.unshift(newElement);
145
145
  else if (pos==="beforeend") this.childNodes.push(newElement);
146
146
  newElement.parent=this
147
147
  return newElement
148
148
  }
149
149
  if (!this.parent) throw new Error("Can't insert element to element without parent")
150
150
  if (pos==="beforebegin") insertBefore(this.parent.childNodes,this.childNodeIndex,newElement)
151
151
  else if (pos==="afterend") this.parent.childNodes.splice(this.childNodeIndex+1,0,newElement);
152
152
  newElement.parent=this.parent
153
153
  return newElement
154
154
  }
155
155
  insertAdjacentHTML(position,html){
156
156
  const newNode=parseHTML(html);
157
157
  newNode.childNodes.forEach(node=>{
158
158
  this.insertAdjacentElement(position,node);
159
159
  });
160
160
  return newNode
161
161
  }
162
162
  insertAdjacentText(position,text){
163
163
  return this.insertAdjacentElement(position,new TextNode(text));
164
164
  }
165
165
  insert(position,element){
166
166
  const positions=['beforebegin','afterbegin','beforeend','afterend']
167
167
  if (positions[position]) position=positions[position]
168
168
  if (typeof element==='string'){
169
169
  element=element.trim()
170
170
  if (element.startsWith('<') && element.endsWith('>')){
171
171
  return this.insertAdjacentHTML(position,element)
172
172
  }
173
173
  return this.insertAdjacentText(position,element)
174
174
  }
175
175
  return this.insertAdjacentElement(position,element)
176
176
  }
177
177
  set innerHTML(html){
178
178
  this.childNodes=html.trim().startsWith('<') ? parseHTML(html).childNodes : [html]
179
179
  this.children.forEach(child=>child.parent=this);
180
180
  }
181
181
  set outerHTML(html){
182
182
  const parsed=parseHTML(html);
183
183
  if (!this.parent) throw new Error('element has no parent node')
184
184
  const index=this.childIndex
185
185
  if (index!==null) this.parent.childNodes.splice(index,1,...parsed.childNodes);
186
186
  }
187
187
  appendChild(newChild){
188
188
  if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
189
189
  if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
190
190
  } else if (typeof newChild==='string') newChild=new TextNode(newChild)
191
191
  else return newChild
192
192
  this.childNodes.push(newChild);
193
193
  newChild.parent=this;
194
194
  return newChild;
195
195
  }
196
196
  get textContent(){
197
197
  if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
198
198
  return this.childNodes.map(child=>{
199
199
  if (child instanceof SingleNode) return ''
200
200
  if (child instanceof TextNode) return child.nodeValue
201
201
  if (child instanceof Node) return child.textContent;
202
202
  else return child;
203
203
  }).join('');
204
204
  }
205
205
  set textContent(value){
206
206
  this.childNodes=[];
207
207
  if (value!==null && value!==undefined){
208
208
  this.childNodes.push(value.toString());
209
209
  }
210
210
  }
211
211
  }
212
- class SingleNode extends Node{
213
212
  constructor(tagName,attributes={},parent=null){
214
213
  if(attributes['?'] && tagName==='?xml') delete attributes['?']
215
214
  super(tagName,attributes,parent);
216
215
  this.isSingle=true
217
216
  }
218
217
  get outerHTML(){
219
218
  if (this._tagName==="#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
220
219
  const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
221
220
  return `<${this._tagName} ${attrs}${this._tagName==='?xml' ? '?' : ''}>`;
222
221
  }
223
222
  get innerHTML(){ return ""; }
224
223
  set innerHTML(_){ }
225
224
  $(_){return null}
226
225
  $$(_){return []}
227
226
  querySelectorAll(_){ return []; }
228
227
  querySelector(_){ return null; }
229
228
  getElementsByClassName(_){ return []; }
230
229
  getElementsByTagName(_){ return []; }
231
230
  getElementById(_){ return null; }
232
231
  get children(){ return []; }
233
232
  insertAdjacentElement(position,newElement){
234
233
  if(position==='afterbegin') position='beforebegin'
235
234
  if(position==='beforeend') position='afterend'
236
235
  super.insertAdjacentElement(position,newElement)
237
236
  }
238
237
  insertAdjacentHTML(position,html){
239
238
  if(position==='afterbegin') position='beforebegin'
240
239
  if(position==='beforeend') position='afterend'
241
240
  super.insertAdjacentHTML(position,html)
242
241
  }
243
242
  insertAdjacentText(position,text){
244
243
  if(position==='afterbegin') position='beforebegin'
245
244
  if(position==='beforeend') position='afterend'
246
245
  super.insertAdjacentText(position,text)
247
246
  }
248
247
  insert(position,element){
249
248
  if(position===1) position=0
250
249
  if(position===2) position=3
251
250
  super.insert(position,element)
252
251
  }
253
252
  appendChild(_){ }
254
253
  get textContent(){ return ""; }
255
254
  set textContent(_){ }
255
+ class SingleNode extends Node{
256
256
  constructor(tagName,attributes={},parent=null){
257
257
  if(attributes['?'] && tagName==='?xml') delete attributes['?']
258
258
  super(tagName,attributes,parent);
259
259
  this.isSingle=true
260
260
  }
261
261
  get outerHTML(){
262
262
  if (this._tagName==="#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
263
263
  const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
264
264
  return `<${this._tagName} ${attrs}${this._tagName==='?xml' ? '?' : ''}>`;
265
265
  }
266
266
  get innerHTML(){ return ""; }
267
267
  set innerHTML(_){ }
268
268
  $(_){return null}
269
269
  $$(_){return []}
270
270
  querySelectorAll(_){ return []; }
271
271
  querySelector(_){ return null; }
272
272
  getElementsByClassName(_){ return []; }
273
273
  getElementsByTagName(_){ return []; }
274
274
  getElementById(_){ return null; }
275
275
  get children(){ return []; }
276
276
  insertAdjacentElement(position,newElement){
277
277
  if(position==='afterbegin') position='beforebegin'
278
278
  if(position==='beforeend') position='afterend'
279
279
  return super.insertAdjacentElement(position,newElement)
280
280
  }
281
281
  insertAdjacentHTML(position,html){
282
282
  if(position==='afterbegin') position='beforebegin'
283
283
  if(position==='beforeend') position='afterend'
284
284
  return super.insertAdjacentHTML(position,html)
285
285
  }
286
286
  insertAdjacentText(position,text){
287
287
  if(position==='afterbegin') position='beforebegin'
288
288
  if(position==='beforeend') position='afterend'
289
289
  return super.insertAdjacentText(position,text)
290
290
  }
291
291
  insert(position,element){
292
292
  if(position===1) position=0
293
293
  if(position===2) position=3
294
294
  return super.insert(position,element)
295
295
  }
296
296
  appendChild(_){ }
297
297
  get textContent(){ return ""; }
298
298
  set textContent(_){ }
299
299
  }
300
300
 
301
301
  class Root extends Node{
302
302
  constructor(){
303
303
  super('ROOT',{},null);
304
304
  this.isSingle=false
305
305
  }
@@ -33,7 +33,7 @@ class Document extends Node{
33
33
  constructor(html,url){
34
34
  super('ROOT',{},nul
35
35
  function parseAttributes(str){
36
36
  const attrs={};
37
37
  let key="";
38
38
  let value="";
39
39
  let isKey=true;
40
40
  let quoteChar=null;
41
41
  for (let i=0; i< str.length; i++){
42
42
  const char=str[i];
43
43
  if (isKey && (char==='=' || char===' ')){
44
44
  if (char==='=') isKey=false;
45
45
  else if (key.trim()){
46
46
  attrs[key.trim()]=true;
47
47
  key="";
48
48
  }
49
49
  continue;
50
50
  }
51
51
  if (!quoteChar && (char==='"' || char==="'")){
52
52
  quoteChar=char;
53
53
  continue;
54
54
  } else if (quoteChar && char===quoteChar){
55
55
  quoteChar=null;
56
56
  attrs[key.trim()]=value.trim();
57
57
  key=""; value=""; isKey=true;
58
58
  continue;
59
59
  }
60
60
  if (isKey) key+=char;
61
61
  else value+=char;
62
62
  }
63
63
  if (key.trim() &&!value) attrs[key.trim()]='';
64
64
  return attrs;
65
65
  }
66
66
  const VOID_TAGS=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","!doctype",'?xml']);
67
- function parseHTML(html){
68
67
  const root=new Root();
69
68
  const stack=[root];
70
69
  let currentText="",i=0;
71
70
  let max=0
72
71
  function parseScript(){
73
72
  if (!html.startsWith("<script",i)) return false;
74
73
  const openTagEnd=html.indexOf(">",i);
75
74
  if (openTagEnd===-1) return false;
76
75
  const attributesString=html.substring(i+7,openTagEnd).trim();
77
76
  const attributes=parseAttributes(attributesString);
78
77
  let closeTagStart=html.indexOf("</script>",openTagEnd);
79
78
  if (closeTagStart===-1) return false;
80
79
  const content=html.substring(openTagEnd+1,closeTagStart);
81
80
  const scriptNode=new Node('script',attributes,stack[stack.length-1]);
82
81
  if(content.length>0) scriptNode.childNodes.push(content);
83
82
  i=closeTagStart+9;
84
83
  return true;
85
84
  }
86
85
  function parseSpecial(startStr,endStr,n1,n2,tag){
87
86
  if (!html.startsWith(startStr,i)) return false
88
87
  const end=html.indexOf(endStr,i+n1);
89
88
  const strNode=new Node(tag,{},stack[stack.length-1]);
90
89
  strNode.childNodes.push(html.substring(i+n1,end));
91
90
  i=end+n2;
92
91
  return true
93
92
  }
94
93
  while (i< html.length){
95
94
  if(i>=max) max=i;
96
95
  else break;
97
96
  if (parseScript()) continue
98
97
  if (parseSpecial("<!--","-->",4,3,'#comment')) continue
99
98
  if (parseSpecial("<style","</style>",7,8,'style')) continue
100
99
  if (html.startsWith("<![CDATA[",i)){
101
100
  const end=html.indexOf("]]>",i+9);
102
101
  if (end===-1) break;
103
102
  const content=html.substring(i+9,end);
104
103
  const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
105
104
  cdataNode.nodeValue=content;
106
105
  i=end+3;
107
106
  continue;
108
107
  }
109
108
  if (html.startsWith("<",i)){
110
109
  if (currentText && stack[stack.length-1]){
111
110
  const textNode=new TextNode(currentText)
112
111
  stack[stack.length-1].childNodes.push(textNode);
113
112
  textNode.parent=stack[stack.length-1]
114
113
  currentText="";
115
114
  }
116
115
  let tagEnd=i+1;
117
116
  let insideQuotes=false;
118
117
  let quoteChar=null;
119
118
  while (tagEnd< html.length){
120
119
  const char=html[tagEnd];
121
120
  if (!insideQuotes && (char==='"' || char==="'")){
122
121
  insideQuotes=true;
123
122
  quoteChar=char;
124
123
  } else if (insideQuotes && char===quoteChar){
125
124
  insideQuotes=false;
126
125
  quoteChar=null;
127
126
  }
128
127
  if (!insideQuotes && char==='>') break;
129
128
  tagEnd++;
130
129
  }
131
130
  const tagContent=html.substring(i+1,tagEnd);
132
131
  if (tagContent.startsWith("/")) stack.pop();
133
132
  else{
134
133
  let isSelfClosing=tagContent.endsWith('/');
135
134
  const tagNameEnd=tagContent.search(/\s|>|\//);
136
135
  const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
137
136
  const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
138
137
  const attributes=parseAttributes(attributesString);
139
138
  if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
140
139
  else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
141
140
  }
142
141
  i=tagEnd+1;
143
142
  } else{
144
143
  currentText+=html[i];
145
144
  i++;
146
145
  }
147
146
  }
148
147
  if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
149
148
  return root;
149
+ function parseHTML(html){
150
150
  const root=new Root();
151
151
  const stack=[root];
152
152
  let currentText="",i=0;
153
153
  let max=0
154
154
  function parseScript(){
155
155
  if (!html.startsWith("<script",i)) return false;
156
156
  const openTagEnd=html.indexOf(">",i);
157
157
  if (openTagEnd===-1) return false;
158
158
  const attributesString=html.substring(i+7,openTagEnd).trim();
159
159
  const attributes=parseAttributes(attributesString);
160
160
  let closeTagStart=html.indexOf("</script>",openTagEnd);
161
161
  if (closeTagStart===-1) return false;
162
162
  const content=html.substring(openTagEnd+1,closeTagStart);
163
163
  const scriptNode=new Node('script',attributes,stack[stack.length-1]);
164
164
  if(content.length>0) scriptNode.childNodes.push(content);
165
165
  i=closeTagStart+9;
166
166
  return true;
167
167
  }
168
168
  function parseSpecial(startStr,endStr,n1,n2,tag){
169
169
  if (!html.startsWith(startStr,i)) return false
170
170
  if(currentText.length) stack[stack.length-1].childNodes.push(new TextNode(currentText));
171
171
  const end=html.indexOf(endStr,i+n1);
172
172
  const strNode=new Node(tag,{},stack[stack.length-1]);
173
173
  strNode.childNodes.push(html.substring(i+n1,end));
174
174
  i=end+n2;
175
175
  return true
176
176
  }
177
177
  while (i< html.length){
178
178
  if(i>=max) max=i;
179
179
  else break;
180
180
  if (parseScript()) continue
181
181
  if (parseSpecial("<!--","-->",4,3,'#comment')) continue
182
182
  if (parseSpecial("<style","</style>",7,8,'style')) continue
183
183
  if (html.startsWith("<![CDATA[",i)){
184
184
  const end=html.indexOf("]]>",i+9);
185
185
  if (end===-1) break;
186
186
  const content=html.substring(i+9,end);
187
187
  const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
188
188
  cdataNode.nodeValue=content;
189
189
  i=end+3;
190
190
  continue;
191
191
  }
192
192
  if (html.startsWith("<",i)){
193
193
  if (currentText && stack[stack.length-1]){
194
194
  const textNode=new TextNode(currentText)
195
195
  stack[stack.length-1].childNodes.push(textNode);
196
196
  textNode.parent=stack[stack.length-1]
197
197
  currentText="";
198
198
  }
199
199
  let tagEnd=i+1;
200
200
  let insideQuotes=false;
201
201
  let quoteChar=null;
202
202
  while (tagEnd< html.length){
203
203
  const char=html[tagEnd];
204
204
  if (!insideQuotes && (char==='"' || char==="'")){
205
205
  insideQuotes=true;
206
206
  quoteChar=char;
207
207
  } else if (insideQuotes && char===quoteChar){
208
208
  insideQuotes=false;
209
209
  quoteChar=null;
210
210
  }
211
211
  if (!insideQuotes && char==='>') break;
212
212
  tagEnd++;
213
213
  }
214
214
  const tagContent=html.substring(i+1,tagEnd);
215
215
  if (tagContent.startsWith("/")) stack.pop();
216
216
  else{
217
217
  let isSelfClosing=tagContent.endsWith('/');
218
218
  const tagNameEnd=tagContent.search(/\s|>|\//);
219
219
  const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
220
220
  const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
221
221
  const attributes=parseAttributes(attributesString);
222
222
  if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
223
223
  else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
224
224
  }
225
225
  i=tagEnd+1;
226
226
  } else{
227
227
  currentText+=html[i];
228
228
  i++;
229
229
  }
230
230
  }
231
231
  if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
232
232
  return root;
233
233
  }
234
234
  function buildFromCache(cached){
235
235
  function buildNode(cache,parent=null){
236
236
  if(typeof cache==='string') return parent.childNodes.push(cache)
237
237
  const{isSingle,tagName,attributes,childNodes,textContent}=cache
238
238
  if(textContent) return parent.childNodes.push(new TextNode(textContent))
239
239
  if(isSingle && parent) return parent.childNodes.push(new SingleNode(tagName,attributes))
240
240
  const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
241
241
  childNodes.forEach(childNode=>{
242
242
  buildNode(childNode,newDoc)
243
243
  });
244
244
  return newDoc
245
245
  }
246
246
  return buildNode(cached)
247
247
  }
248
248