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