als-document 0.12.0 → 1.0.1-alpha
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 +33 -589
- package/index.js +32 -0
- package/index.mjs +32 -0
- package/package.json +13 -10
- package/readme.md +196 -156
- package/src/build.js +65 -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 +159 -0
- package/src/node/single-node.js +30 -0
- package/src/node/style.js +26 -0
- package/src/node/text-node.js +9 -0
- package/src/parse/parse-atts.js +36 -0
- package/src/parse/parser.js +74 -0
- package/src/parse/void-tags.js +5 -0
- package/src/query/check-element.js +84 -0
- package/src/query/query.js +142 -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 +30 -0
- package/tests/node.js +196 -0
- package/tests/parse-real.js +52 -0
- package/tests/parser.js +351 -0
- package/tests/query.js +66 -0
- package/tests/utils.js +37 -0
package/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
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,attributes,id,prev,ancestors,parents,prevAny}=selector
|
|
134
138
|
if(typeof el==='string') return false
|
|
135
139
|
if(el.isSpecial) return false
|
|
136
140
|
if(id!==undefined && el.id===null) return false
|
|
137
141
|
if(id && id!==el.id) return false
|
|
138
142
|
if(tag && el.tagName===undefined) return false
|
|
139
143
|
else if(tag && tag!==el.tagName) return false
|
|
140
144
|
const clas=el.attributes.class
|
|
141
145
|
if(classList!==undefined && (clas===undefined || clas==='')) return false
|
|
142
146
|
else if(classList!==undefined){
|
|
143
147
|
if(classList.every(e=>el.classList.contains(e))===false) return false
|
|
144
148
|
}
|
|
145
149
|
if(checkattributes(attributes,el)===false) return false
|
|
146
150
|
if(checkElement(el.prev,prev)===false) return false
|
|
147
151
|
if(checkAncestors(el.ancestors,ancestors)===false) return false
|
|
148
152
|
if(checkParents(el.ancestors,parents)===false) return false
|
|
149
153
|
if(el.parent){
|
|
150
154
|
if(checkPrevAny(el.parent.children,el.childIndex,prevAny)==false) return false
|
|
151
155
|
}
|
|
152
156
|
return true
|
|
157
|
+
}
|
|
153
158
|
function checkattributes(attributes=[],el){
|
|
154
159
|
let elattributes=el.attributes
|
|
155
160
|
let names=Object.keys(elattributes)
|
|
156
161
|
let passedTests=0
|
|
157
162
|
if(attributes) for(let i=0; i<attributes.length; i++){
|
|
158
163
|
let{name,value,check}=attributes[i]
|
|
159
164
|
if(name=='inner' && value!==undefined && check && el.inner){
|
|
160
165
|
if(check(el.inner)) passedTests++
|
|
161
166
|
}
|
|
162
167
|
if(!names.includes(name)) continue
|
|
163
168
|
else if(value==undefined) passedTests++
|
|
164
169
|
else if(value && elattributes[name]){
|
|
165
170
|
if(check(elattributes[name])==false) continue
|
|
166
171
|
else passedTests++
|
|
167
172
|
}
|
|
168
173
|
}
|
|
169
174
|
if(passedTests==attributes.length) return true
|
|
170
175
|
else return false
|
|
176
|
+
}
|
|
171
177
|
function checkPrevAny(children=[],index,prevAny){
|
|
172
178
|
let size=children.length
|
|
173
179
|
if((size==0 || index==0) && prevAny) return false
|
|
174
180
|
for(let i=index; i>=0; i--){
|
|
175
181
|
if(checkElement(children[i],prevAny)) return true
|
|
176
182
|
}
|
|
177
183
|
return false
|
|
184
|
+
}
|
|
178
185
|
function checkAncestors(ancestors=[],selectorAncestors=[]){
|
|
179
186
|
let count=0
|
|
180
187
|
if(selectorAncestors.length==0) return true
|
|
181
188
|
let endIndex=ancestors.length-1
|
|
182
189
|
let selectorIndex=selectorAncestors.length-1
|
|
183
190
|
while(selectorIndex>=0){
|
|
184
191
|
for(let i=endIndex; i>=0; i--){
|
|
185
192
|
endIndex=i-1
|
|
186
193
|
if(checkElement(ancestors[i],selectorAncestors[selectorIndex])==true){
|
|
187
194
|
count++
|
|
188
195
|
break
|
|
189
196
|
}
|
|
190
197
|
}
|
|
191
198
|
selectorIndex--
|
|
192
199
|
}
|
|
193
200
|
if(count==selectorAncestors.length) return true
|
|
194
201
|
else return false
|
|
202
|
+
}
|
|
195
203
|
function checkParents(ancestors=[],selectorParents=[]){
|
|
196
204
|
if(selectorParents.length===0) return true
|
|
197
205
|
if(ancestors.length< selectorParents.length) return false
|
|
198
206
|
let index=ancestors.length-1
|
|
199
207
|
for(let i=selectorParents.length-1; i>=0; i--){
|
|
200
208
|
if(checkElement(ancestors[index],selectorParents[i])===false) return false
|
|
201
209
|
index--
|
|
202
210
|
}
|
|
203
211
|
return true
|
|
212
|
+
}
|
|
213
|
+
const getDataName=prop=>'data-'+prop.toLowerCase()
|
|
214
|
+
function getDataset(element){
|
|
204
215
|
return new Proxy(element.attributes,{
|
|
205
216
|
get: (target,prop)=>{return target[getDataName(prop)]},set: (target,prop,value)=>{target[getDataName(prop)]=value; return true},deleteProperty: (target,prop)=>{
|
|
206
217
|
const dataAttr=getDataName(prop)
|
|
207
218
|
if (dataAttr in target){
|
|
208
219
|
delete target[dataAttr];
|
|
209
220
|
return true;
|
|
210
221
|
}
|
|
211
222
|
return false;
|
|
212
223
|
}
|
|
213
224
|
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function find(selectors,element,collection,first=false,firstTime=true){
|
|
214
228
|
for(let selector of selectors){
|
|
215
229
|
if(checkElement(element,selector)) collection.add(element)
|
|
216
230
|
}
|
|
217
231
|
if(element.children)
|
|
218
232
|
element.children.forEach(child=>{
|
|
219
233
|
if(first && collection.size>0) return
|
|
220
234
|
find(selectors,child,collection,first,false)
|
|
221
235
|
})
|
|
222
236
|
return firstTime ? [...collection] : collection
|
|
237
|
+
}
|
|
238
|
+
class TextNode{
|
|
223
239
|
constructor(data){
|
|
224
240
|
this.nodeName='#text';
|
|
225
241
|
this.parent=null;
|
|
226
242
|
this.textContent=data;
|
|
227
243
|
}
|
|
228
244
|
get nodeValue(){return this.textContent}
|
|
229
245
|
get parentNode(){return this.parent}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function buildStyle(attributes){
|
|
230
249
|
const styles=attributes.style || "";
|
|
231
250
|
const camelToKebab=str=>str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,'$1-$2').toLowerCase();
|
|
232
251
|
const kebabToCamel=str=>str.replace(/-([a-z])/g,g=>g[1].toUpperCase());
|
|
233
252
|
const baseStyleObj=styles.split(";").reduce((acc,style)=>{
|
|
234
253
|
const [key,value]=style.split(":").map(s=>s.trim());
|
|
235
254
|
if (key && value) acc[kebabToCamel(key)]=value;
|
|
236
255
|
return acc;
|
|
237
256
|
},{});
|
|
238
257
|
return new Proxy(baseStyleObj,{
|
|
239
258
|
get: (obj,prop)=>obj[camelToKebab(prop)] || obj[prop],set: (obj,prop,value)=>{
|
|
240
259
|
obj[camelToKebab(prop)]=value;
|
|
241
260
|
attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
|
|
242
261
|
return true;
|
|
243
262
|
},deleteProperty: (obj,prop)=>{
|
|
244
263
|
delete obj[camelToKebab(prop)];
|
|
245
264
|
attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
|
|
246
265
|
return true;
|
|
247
266
|
}
|
|
248
267
|
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
class NodeClassList{
|
|
249
271
|
constructor(node){ this.node=node }
|
|
250
272
|
get classes(){ return (this.node.attributes.class || "").split(" ").filter(Boolean) }
|
|
251
273
|
set classes(val){ this.node.attributes.class=val.join(" ") }
|
|
252
274
|
contains(className){ return this.classes.includes(className) }
|
|
253
275
|
add(className){
|
|
254
276
|
const currentClasses=this.classes;
|
|
255
277
|
if (!currentClasses.includes(className)) this.classes=[...currentClasses,className];
|
|
256
278
|
}
|
|
257
279
|
remove(className){ this.classes=this.classes.filter(cls=>cls!==className); }
|
|
258
280
|
toggle(className){
|
|
259
281
|
if (this.classes.includes(className)) this.remove(className);
|
|
260
282
|
else this.add(className);
|
|
261
283
|
}
|
|
262
284
|
replace(oldClass,newClass){
|
|
263
285
|
if (this.classes.includes(oldClass)){
|
|
264
286
|
this.remove(oldClass);
|
|
265
287
|
this.add(newClass);
|
|
266
288
|
}
|
|
267
289
|
}
|
|
290
|
+
}
|
|
291
|
+
class Node{
|
|
268
292
|
constructor(tagName,attributes={},parent=null){
|
|
269
293
|
this.isSingle=false;
|
|
270
294
|
this.tagName=tagName;
|
|
271
295
|
this.attributes=attributes;
|
|
272
296
|
this.childNodes=[];
|
|
273
297
|
if (parent!==null) parent.childNodes.push(this)
|
|
274
298
|
this.parent=parent;
|
|
275
299
|
this._classList=null;
|
|
276
300
|
this.__style=null;
|
|
277
301
|
this._dataset=null
|
|
278
302
|
}
|
|
279
303
|
get id(){ return this.attributes.id || null; }
|
|
280
304
|
get className(){return this.attributes.class || null}
|
|
281
305
|
get parentNode(){ return this.parent }
|
|
282
306
|
get ancestors(){
|
|
283
307
|
const ancestors=[]
|
|
284
308
|
let element=this.parent
|
|
285
309
|
while (element.tagName!=='ROOT'){
|
|
286
310
|
ancestors.push(element)
|
|
287
311
|
element=element.parent
|
|
288
312
|
}
|
|
289
313
|
return ancestors.reverse()
|
|
290
314
|
}
|
|
291
315
|
get childIndex(){ return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null }
|
|
292
316
|
get previousElementSibling(){ return this.prev }
|
|
293
317
|
get prev(){
|
|
294
318
|
if (!this.childIndex) return null
|
|
295
319
|
return this.parent.childNodes[this.childIndex-1]
|
|
296
320
|
}
|
|
297
321
|
get nextElementSibling(){ return this.next }
|
|
298
322
|
get next(){
|
|
299
323
|
if (!this.childIndex) return null
|
|
300
324
|
return this.parent.childNodes[this.childIndex+1] || null
|
|
301
325
|
}
|
|
302
326
|
get dataset(){
|
|
303
327
|
if (!this._dataset) this._dataset=getDataset(this);
|
|
304
328
|
return this._dataset;
|
|
305
329
|
}
|
|
306
330
|
get classList(){
|
|
307
331
|
if (!this._classList) this._classList=new NodeClassList(this);
|
|
308
332
|
return this._classList;
|
|
309
333
|
}
|
|
310
334
|
get style(){
|
|
311
335
|
if (!this.__style) this.__style=buildStyle(this.attributes)
|
|
312
336
|
return this.__style
|
|
313
337
|
}
|
|
314
338
|
get outerHTML(){
|
|
315
339
|
const attrs=Object.entries(this.attributes).map(([key,val])=>`${key}="${val}"`).join(" ");
|
|
316
340
|
return `<${this.tagName} ${attrs}>${this.innerHTML}</${this.tagName}>`;
|
|
317
341
|
}
|
|
318
342
|
getAttribute(attrName){ return this.attributes[attrName] || null }
|
|
319
343
|
setAttribute(attrName,value){ this.attributes[attrName]=value }
|
|
320
344
|
removeAttribute(attrName){ delete this.attributes[attrName] }
|
|
321
345
|
remove(){
|
|
322
346
|
if (!this.parent) return
|
|
323
347
|
const index=this.childIndex;
|
|
324
348
|
if (index!==null) this.parent.childNodes.splice(index,1);
|
|
325
349
|
}
|
|
326
350
|
get innerHTML(){
|
|
327
351
|
return this.childNodes.map(child=>{
|
|
328
352
|
if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
|
|
329
353
|
else if (child instanceof TextNode) return child.textContent;
|
|
330
354
|
else return child
|
|
331
355
|
}).join("");
|
|
332
356
|
}
|
|
333
357
|
$$(query){return this.querySelectorAll(query)}
|
|
334
358
|
querySelectorAll(query){
|
|
335
359
|
const selectors=Query.get(query)
|
|
336
360
|
return find(selectors,this,new Set())
|
|
337
361
|
}
|
|
338
362
|
$(query){return this.querySelector(query)}
|
|
339
363
|
querySelector(query){
|
|
340
364
|
const selectors=Query.get(query)
|
|
341
365
|
return find(selectors,this,new Set(),true)[0]
|
|
342
366
|
}
|
|
343
367
|
getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
|
|
344
368
|
getElementsByTagName(query){ return this.querySelectorAll(query) }
|
|
345
369
|
getElementById(query){ return this.querySelector('#'+query) }
|
|
346
370
|
get children(){
|
|
347
371
|
return this.childNodes.filter(child=>{
|
|
348
372
|
if (!(child instanceof Node)) return false
|
|
349
373
|
if (child.tagName==='#comment') return false
|
|
350
374
|
return true
|
|
351
375
|
});
|
|
352
376
|
}
|
|
353
377
|
insertAdjacentElement(position,newElement){
|
|
354
378
|
if(newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
|
|
355
379
|
const pos=position.toLowerCase();
|
|
356
380
|
if (pos==="afterbegin") this.childNodes.unshift(newElement);
|
|
357
381
|
else if (pos==="beforeend") this.childNodes.push(newElement);
|
|
358
382
|
if (!this.parent) return newElement
|
|
359
383
|
if (pos==="beforebegin") this.parent.childNodes.unshift(newElement);
|
|
360
384
|
else if (pos==="afterend") this.parent.childNodes.splice(this.childIndex+1,0,newElement);
|
|
361
385
|
return newElement
|
|
362
386
|
}
|
|
363
387
|
insertAdjacentHTML(position,html){
|
|
364
388
|
const newNode=parseHTML(html);
|
|
365
389
|
return this.insertAdjacentElement(position,newNode);
|
|
366
390
|
}
|
|
367
391
|
insertAdjacentText(position,text){
|
|
368
392
|
return this.insertAdjacentElement(position,new TextNode(text));
|
|
369
393
|
}
|
|
370
394
|
set innerHTML(html){
|
|
371
395
|
const parsed=parseHTML(html);
|
|
372
396
|
this.childNodes=parsed.childNodes;
|
|
373
397
|
}
|
|
374
398
|
set outerHTML(html){
|
|
375
399
|
const parsed=parseHTML(html);
|
|
376
400
|
if (!this.parent) return console.log('element has no parent node')
|
|
377
401
|
const index=this.childIndex
|
|
378
402
|
if (index!==null) this.parent.childNodes.splice(index,1,...parsed.childNodes);
|
|
379
403
|
}
|
|
380
404
|
appendChild(newChild){
|
|
381
405
|
if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
|
|
382
406
|
if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
|
|
383
407
|
} else if(typeof newChild==='string') newChild=new TextNode(newChild)
|
|
384
408
|
else return newChild
|
|
385
409
|
this.childNodes.push(newChild);
|
|
386
410
|
newChild.parent=this;
|
|
387
411
|
return newChild;
|
|
388
412
|
}
|
|
389
413
|
get textContent(){
|
|
390
414
|
if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
|
|
391
415
|
return this.childNodes.map(child=>{
|
|
392
416
|
if(child instanceof SingleNode) return ''
|
|
393
417
|
if(child instanceof TextNode) return child.nodeValue
|
|
394
418
|
if(child instanceof Node) return child.textContent;
|
|
395
419
|
else return child;
|
|
396
420
|
}).join(" ");
|
|
397
421
|
}
|
|
398
422
|
set textContent(value){
|
|
399
423
|
this.childNodes=[];
|
|
400
424
|
if (value!==null && value!==undefined){
|
|
401
425
|
this.childNodes.push(value.toString());
|
|
402
426
|
}
|
|
403
427
|
}
|
|
428
|
+
}
|
|
429
|
+
class SingleNode extends Node{
|
|
404
430
|
constructor(tagName,attributes={},parent=null){
|
|
405
431
|
if(attributes['?'] && tagName==='?xml') delete attributes['?']
|
|
406
432
|
super(tagName,attributes,parent);
|
|
407
433
|
this.isSingle=true
|
|
408
434
|
}
|
|
409
435
|
get outerHTML(){
|
|
410
436
|
if (this.tagName==="#cdata-section") return `<![CDATA[${this.textContent}]]>`;
|
|
411
437
|
const attrs=Object.entries(this.attributes).map(([key,val])=>`${key}="${val}"`).join(" ");
|
|
412
438
|
return `<${this.tagName} ${attrs}${this.tagName==='?xml' ? '?' : ''}>`;
|
|
413
439
|
}
|
|
414
440
|
get innerHTML(){ return ""; }
|
|
415
441
|
set innerHTML(_){ }
|
|
416
442
|
$(_){return null}
|
|
417
443
|
$$(_){return []}
|
|
418
444
|
querySelectorAll(_){ return []; }
|
|
419
445
|
querySelector(_){ return null; }
|
|
420
446
|
getElementsByClassName(_){ return []; }
|
|
421
447
|
getElementsByTagName(_){ return []; }
|
|
422
448
|
getElementById(_){ return null; }
|
|
423
449
|
get children(){ return []; }
|
|
424
450
|
insertAdjacentElement(_,__){ }
|
|
425
451
|
insertAdjacentHTML(_,__){ }
|
|
426
452
|
insertAdjacentText(_,__){ }
|
|
427
453
|
appendChild(_){ }
|
|
428
454
|
get textContent(){ return ""; }
|
|
429
455
|
set textContent(_){ }
|
|
456
|
+
}
|
|
457
|
+
function parseAttributes(str){
|
|
430
458
|
const attrs={};
|
|
431
459
|
let key="";
|
|
432
460
|
let value="";
|
|
433
461
|
let isKey=true;
|
|
434
462
|
let quoteChar=null;
|
|
435
463
|
for (let i=0; i< str.length; i++){
|
|
436
464
|
const char=str[i];
|
|
437
465
|
if (isKey && (char==='=' || char===' ')){
|
|
438
466
|
if (char==='=') isKey=false;
|
|
439
467
|
else if (key.trim()){
|
|
440
468
|
attrs[key.trim()]=true;
|
|
441
469
|
key="";
|
|
442
470
|
}
|
|
443
471
|
continue;
|
|
444
472
|
}
|
|
445
473
|
if (!quoteChar && (char==='"' || char==="'")){
|
|
446
474
|
quoteChar=char;
|
|
447
475
|
continue;
|
|
448
476
|
} else if (quoteChar && char===quoteChar){
|
|
449
477
|
quoteChar=null;
|
|
450
478
|
attrs[key.trim()]=value.trim();
|
|
451
479
|
key=""; value=""; isKey=true;
|
|
452
480
|
continue;
|
|
453
481
|
}
|
|
454
482
|
if (isKey) key+=char;
|
|
455
483
|
else value+=char;
|
|
456
484
|
}
|
|
457
485
|
if (key.trim() &&!value) attrs[key.trim()]=true;
|
|
458
486
|
return attrs;
|
|
487
|
+
}
|
|
488
|
+
const VOID_TAGS=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","!doctype",'?xml']);
|
|
489
|
+
function parseHTML(html){
|
|
459
490
|
const root=new Node("ROOT");
|
|
460
491
|
const stack=[root];
|
|
461
492
|
let currentText="",i=0;
|
|
462
493
|
function parseSpecial(startStr,endStr,n1,n2,tag){
|
|
463
494
|
if (!html.startsWith(startStr,i)) return false
|
|
464
495
|
const end=html.indexOf(endStr,i+n1);
|
|
465
496
|
const strNode=new Node(tag,{},stack[stack.length-1]);
|
|
466
497
|
strNode.childNodes.push(html.substring(i+n1,end));
|
|
467
498
|
i=end+n2;
|
|
468
499
|
return true
|
|
469
500
|
}
|
|
470
501
|
while (i< html.length){
|
|
471
502
|
if (parseSpecial("<!--","-->",4,3,'#comment')) continue
|
|
472
503
|
if (parseSpecial("<script","</script>",8,9,'script')) continue
|
|
473
504
|
if (parseSpecial("<style","</style>",7,8,'style')) continue
|
|
474
505
|
if (html.startsWith("<![CDATA[",i)){
|
|
475
506
|
const end=html.indexOf("]]>",i+9);
|
|
476
507
|
if (end===-1) break;
|
|
477
508
|
const content=html.substring(i+9,end);
|
|
478
509
|
const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
|
|
479
510
|
cdataNode.nodeValue=content;
|
|
480
511
|
i=end+3;
|
|
481
512
|
continue;
|
|
482
513
|
}
|
|
483
514
|
if (html.startsWith("<",i)){
|
|
484
515
|
if (currentText.trim()){
|
|
485
516
|
stack[stack.length-1].childNodes.push(new TextNode(currentText.trim()));
|
|
486
517
|
currentText="";
|
|
487
518
|
}
|
|
488
519
|
let tagEnd=i+1;
|
|
489
520
|
let insideQuotes=false;
|
|
490
521
|
let quoteChar=null;
|
|
491
522
|
while (tagEnd< html.length){
|
|
492
523
|
const char=html[tagEnd];
|
|
493
524
|
if (!insideQuotes && (char==='"' || char==="'")){
|
|
494
525
|
insideQuotes=true;
|
|
495
526
|
quoteChar=char;
|
|
496
527
|
} else if (insideQuotes && char===quoteChar){
|
|
497
528
|
insideQuotes=false;
|
|
498
529
|
quoteChar=null;
|
|
499
530
|
}
|
|
500
531
|
if (!insideQuotes && char==='>') break;
|
|
501
532
|
tagEnd++;
|
|
502
533
|
}
|
|
503
534
|
const tagContent=html.substring(i+1,tagEnd);
|
|
504
535
|
if (tagContent.startsWith("/")) stack.pop();
|
|
505
536
|
else{
|
|
506
537
|
let isSelfClosing=tagContent.endsWith('/');
|
|
507
538
|
const tagNameEnd=tagContent.search(/\s|>|\//);
|
|
508
539
|
const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
|
|
509
540
|
const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
|
|
510
541
|
const attributes=parseAttributes(attributesString);
|
|
511
542
|
if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
|
|
512
543
|
else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
|
|
513
544
|
}
|
|
514
545
|
i=tagEnd+1;
|
|
515
546
|
} else{
|
|
516
547
|
currentText+=html[i];
|
|
517
548
|
i++;
|
|
518
549
|
}
|
|
519
550
|
}
|
|
520
551
|
if (currentText.trim()) stack[stack.length-1].childNodes.push(new TextNode(currentText.trim()));
|
|
521
552
|
return root;
|
|
553
|
+
}
|
|
554
|
+
module.exports = { parseHTML, Node, Query, TextNode, SingleNode }
|