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