als-document 0.6.11 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/document.js +21 -10
- package/document.mjs +804 -0
- package/package.json +3 -6
- package/readme.md +10 -3
- package/test/{html1.js → data/html1.js} +1 -2
- package/test/{html2.js → data/html2.js} +1 -1
- package/test/front-test/html.html +5650 -0
- package/test/front-test/html.js +3218 -0
- package/test/front-test/test.html +18 -0
- package/test/front-test/test.js +129 -0
- package/test/test.html +17 -0
- package/test/test.js +15 -19
- package/test/tests/document-test.js +402 -0
- package/test/tests/parser-test.js +99 -0
- package/test/tests/query-test.js +71 -0
- package/test/tests/selector-test.js +169 -0
- package/test/utils/__dirname.js +6 -0
- package/test/utils/als-simple-test/test.mjs +153 -0
- package/test/utils/iframe.js +4 -0
- package/document/document.js +0 -192
- package/document/test.js +0 -678
- package/document.min.js +0 -1
- package/index.js +0 -6
- package/parser/parser.js +0 -340
- package/parser/test.js +0 -233
- package/query/query.js +0 -147
- package/query/readme.md +0 -134
- package/query/test.js +0 -143
- package/selector/selector.js +0 -126
- package/selector/test.js +0 -410
package/document.mjs
ADDED
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
export class HtmlSelector {
|
|
2
|
+
constructor(html) {
|
|
3
|
+
if(typeof html == 'string') {
|
|
4
|
+
this.html = new HtmlParser(html)
|
|
5
|
+
this.elements.forEach((element,i) => {this.makeSelectable(element)});
|
|
6
|
+
} else console.log('Parameter is not string')
|
|
7
|
+
}
|
|
8
|
+
get elements() {return this.html.elements}
|
|
9
|
+
|
|
10
|
+
makeSelectable(element) {
|
|
11
|
+
if(element.type == 'tag' && element.status !== 'close') {
|
|
12
|
+
element.$$ = (query) => this.$$(query,element.index,element.endIndex)
|
|
13
|
+
element.$ = (query) => this.$(query,element.index,element.endIndex)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
$(query,start=0,end=this.elements.length) {
|
|
18
|
+
return this.$$(query,start,end,true)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
$$(query,start=0,end=this.elements.length,single=false) {
|
|
22
|
+
let result = []
|
|
23
|
+
this.selectors = new Query(query).selectors
|
|
24
|
+
this.query = query
|
|
25
|
+
this.selectors.forEach(selector => {
|
|
26
|
+
for(let i=start; i<end; i++) {
|
|
27
|
+
let el = this.elements[i]
|
|
28
|
+
if(this.checkElement(el,selector) && !result.includes(el)) result.push(el)
|
|
29
|
+
if(single && result.length == 1) break
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
if(single && result.length == 1) return result[0]
|
|
33
|
+
else if(single && result.length == 0) return null
|
|
34
|
+
else return result
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
checkElement(el,selector) {
|
|
38
|
+
if(selector == undefined) return true
|
|
39
|
+
if(el == null) return false
|
|
40
|
+
let {tag,classList,attribs,id,prev,ancestors,parents,prevAny} = selector
|
|
41
|
+
|
|
42
|
+
if(el.status == 'close' || el.type == 'text') return false
|
|
43
|
+
if(id !== undefined && el.id == undefined) return false
|
|
44
|
+
if(id && id !== el.id) return false
|
|
45
|
+
if(tag && el.tag == undefined) return false
|
|
46
|
+
else if(tag && tag !== el.tag) return false
|
|
47
|
+
if(classList !== undefined && el.classList == undefined) return false
|
|
48
|
+
else if(classList !== undefined && Array.isArray(el.classList)) {
|
|
49
|
+
if(classList.every(e => el.classList.includes(e)) == false) return false
|
|
50
|
+
}
|
|
51
|
+
if(this.checkAttribs(attribs,el) == false) return false
|
|
52
|
+
if(this.checkElement(el.prev,prev) == false) return false
|
|
53
|
+
if(this.checkAncestors(el.ancestors,ancestors) == false) return false
|
|
54
|
+
if(this.checkParents(el.ancestors,parents) == false) return false
|
|
55
|
+
if(el.parent) {
|
|
56
|
+
if(this.prevAny(el.parent.children,el.childIndex,prevAny) == false) return false
|
|
57
|
+
}
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
prevAny(children=[],index,prevAny) {
|
|
62
|
+
let size = children.length
|
|
63
|
+
if((size == 0 || index == 0) && prevAny) return false
|
|
64
|
+
for(let i=index; i>=0; i--) {
|
|
65
|
+
if(this.checkElement(children[i],prevAny)) return true
|
|
66
|
+
}
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
checkAncestors(ancestors=[],selectorAncestors=[]) {
|
|
71
|
+
let count = 0
|
|
72
|
+
if(selectorAncestors.length == 0) return true
|
|
73
|
+
let endIndex = ancestors.length-1
|
|
74
|
+
let selectorIndex = selectorAncestors.length-1
|
|
75
|
+
while(selectorIndex>=0) {
|
|
76
|
+
for(let i=endIndex; i>=0; i--) {
|
|
77
|
+
endIndex=i-1
|
|
78
|
+
if(this.checkElement(ancestors[i],selectorAncestors[selectorIndex]) == true) {
|
|
79
|
+
count++
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
selectorIndex--
|
|
84
|
+
}
|
|
85
|
+
if(count == selectorAncestors.length) return true
|
|
86
|
+
else return false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
checkParents(ancestors=[],selectorParents=[]) {
|
|
90
|
+
if(selectorParents.length == 0) return true
|
|
91
|
+
if(ancestors.length < selectorParents.length) return false
|
|
92
|
+
let index = ancestors.length-1
|
|
93
|
+
for(let i=selectorParents.length-1; i>=0; i--) {
|
|
94
|
+
if(this.checkElement(ancestors[index],selectorParents[i]) == false) return false
|
|
95
|
+
index--
|
|
96
|
+
}
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
checkAttribs(attribs=[],el) {
|
|
101
|
+
let elAttribs = el.attribs
|
|
102
|
+
let names = Object.keys(elAttribs)
|
|
103
|
+
let passedTests = 0
|
|
104
|
+
if(attribs) for(let i=0; i<attribs.length; i++) {
|
|
105
|
+
let {name,value,check} = attribs[i]
|
|
106
|
+
if(name == 'inner' && value !== undefined && check && el.inner) {
|
|
107
|
+
if(check(el.inner)) passedTests++
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if(!names.includes(name)) continue
|
|
111
|
+
else if(value == undefined) passedTests++
|
|
112
|
+
else if(value && elAttribs[name]) {
|
|
113
|
+
if(check(elAttribs[name]) == false) continue
|
|
114
|
+
else passedTests++
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if(passedTests == attribs.length) return true
|
|
118
|
+
else return false
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export class Query {
|
|
122
|
+
static get(query) {
|
|
123
|
+
let q = new Query(query)
|
|
124
|
+
return q.selectors
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
constructor(query) {
|
|
128
|
+
this.query = query
|
|
129
|
+
this.selectors = []
|
|
130
|
+
this.getQueries(query.split(','))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
getQueries(selectors) {
|
|
134
|
+
selectors.forEach(selector => {
|
|
135
|
+
let originalSelector = selector
|
|
136
|
+
selector = this.removeSpaces(selector)
|
|
137
|
+
this.stringValues = []
|
|
138
|
+
selector = selector.replace(/\[.*?\]/g,(value) => {
|
|
139
|
+
this.stringValues.push(value)
|
|
140
|
+
return `[${this.stringValues.length-1}]`
|
|
141
|
+
})
|
|
142
|
+
let [element,ancestors] = this.splitAndCutLast(selector,' ') // \s - ancestor
|
|
143
|
+
element = this.getFamily(element)
|
|
144
|
+
if(ancestors.length > 0)
|
|
145
|
+
element.ancestors = ancestors.map(ancestor => this.getFamily(ancestor))
|
|
146
|
+
element.group = originalSelector
|
|
147
|
+
this.selectors.push(element)
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
splitAndCutLast(string,splitBy) {
|
|
152
|
+
let array = string.split(splitBy)
|
|
153
|
+
let last
|
|
154
|
+
if(array.length == 1) {
|
|
155
|
+
last = array[0]
|
|
156
|
+
array = []
|
|
157
|
+
} else last = array.splice(array.length-1,array.length-1)[0]
|
|
158
|
+
return [last,array]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getFamily(group,element,prev,prevAny,sign) {
|
|
162
|
+
if(group.match(/\~|\+/) !== null) {
|
|
163
|
+
let [last,prevBrothers] = this.splitAndCutLast(group,/\~|\+/)
|
|
164
|
+
let signs = group.replace(last,'')
|
|
165
|
+
prevBrothers.forEach(el => signs = signs.replace(el,''))
|
|
166
|
+
signs = signs.match(/\~|\+/g)
|
|
167
|
+
if(signs.length == 1) {
|
|
168
|
+
sign = signs[0]
|
|
169
|
+
} else if(signs.length > 1){
|
|
170
|
+
sign = signs.splice(signs.length-1,signs.length-1)[0]
|
|
171
|
+
prevBrothers[0] = prevBrothers.map((b,i) => {
|
|
172
|
+
if(i<prevBrothers.length-1) b += signs[i]
|
|
173
|
+
return b
|
|
174
|
+
}).join('')
|
|
175
|
+
prevBrothers[0] = this.getFamily(prevBrothers[0])
|
|
176
|
+
}
|
|
177
|
+
if(sign == '~') prevAny = prevBrothers[0] // ~ - any prev brother
|
|
178
|
+
else if(sign == '+') prev = prevBrothers[0] // + - prev brother
|
|
179
|
+
element = last
|
|
180
|
+
} else element = group
|
|
181
|
+
let family
|
|
182
|
+
if(prev || prevAny) {
|
|
183
|
+
family = this.getParents(element)
|
|
184
|
+
if(prev) family.prev = this.getParents(prev)
|
|
185
|
+
if(prevAny) family.prevAny = this.getParents(prevAny)
|
|
186
|
+
} else family = this.getParents(element)
|
|
187
|
+
if(family.query !== group) family.group = group
|
|
188
|
+
return family
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
getParents(selector) {
|
|
192
|
+
if(typeof selector == 'string') {
|
|
193
|
+
let [element,parents] = this.splitAndCutLast(selector,'>')
|
|
194
|
+
element = this.buildElement(element)
|
|
195
|
+
parents = parents.map(parent => this.buildElement(parent))
|
|
196
|
+
if(parents.length > 0) element.parents = parents
|
|
197
|
+
return element
|
|
198
|
+
} else return selector
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
buildElement(element,id=null,tag=null,classList=[]) {
|
|
203
|
+
let query = element
|
|
204
|
+
element = element.replace(/\#(\w-?)*/,$id => {
|
|
205
|
+
id = $id.replace(/^\#/,''); return ''
|
|
206
|
+
})
|
|
207
|
+
element = element.replace(/\.(\w-?)*/,$class => {
|
|
208
|
+
classList.push($class.replace(/^\./,'')); return ''
|
|
209
|
+
})
|
|
210
|
+
element = element.replace(/(\w-?)*/,$tag => {
|
|
211
|
+
tag = $tag == '' ? null : $tag; return ''
|
|
212
|
+
})
|
|
213
|
+
let attribs = this.getAttributes(element)
|
|
214
|
+
element = {query}
|
|
215
|
+
if(id) element.id = id
|
|
216
|
+
if(tag) element.tag = tag
|
|
217
|
+
if(classList.length > 0) element.classList = classList
|
|
218
|
+
if(attribs.length > 0) element.attribs = attribs
|
|
219
|
+
return element
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
getAttributes(element) {
|
|
223
|
+
let attribs = this.stringValues.filter((value,index) => {
|
|
224
|
+
let searchValue = `[${index}]`
|
|
225
|
+
if(element.match(searchValue)) return true
|
|
226
|
+
else return false
|
|
227
|
+
})
|
|
228
|
+
attribs = attribs.map(attrib => {
|
|
229
|
+
let query = attrib
|
|
230
|
+
attrib = attrib.replace('[','').replace(']','')
|
|
231
|
+
let [name,value] = attrib.split(/[\~\|\^\$\*]?\=/)
|
|
232
|
+
let sign = attrib.replace(name,'').replace(value,'')
|
|
233
|
+
attrib = {query}
|
|
234
|
+
if(name) attrib.name = name
|
|
235
|
+
if(value) attrib.value = value.trim().replace(/^\"/,'').replace(/\"$/,'')
|
|
236
|
+
if(sign) {
|
|
237
|
+
attrib.sign = sign
|
|
238
|
+
attrib.check = this.getAttribFn(sign).bind(attrib)
|
|
239
|
+
}
|
|
240
|
+
return attrib
|
|
241
|
+
});
|
|
242
|
+
return attribs
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
getAttribFn(sign) {
|
|
246
|
+
if(sign == '=') return function(value) {return value == this.value ? true : false}
|
|
247
|
+
if(sign == '*=') return function(value) {return value.includes(this.value) ? true : false}
|
|
248
|
+
if(sign == '^=') return function(value) {return value.startsWith(this.value) ? true : false}
|
|
249
|
+
if(sign == '$=') return function(value) {return value.endsWith(this.value) ? true : false}
|
|
250
|
+
if(sign == '|=') return function(value) {
|
|
251
|
+
return value.trim().split(' ').length == 1
|
|
252
|
+
&& (value.startsWith(this.value) || value.startsWith(this.value+'-'))
|
|
253
|
+
? true : false
|
|
254
|
+
}
|
|
255
|
+
if(sign == '~=') return function(value) { // includes whole word only
|
|
256
|
+
return this.value.trim().split(' ').length == 1 && value.includes(this.value) ? true : false
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
removeSpaces(selector) {
|
|
261
|
+
selector = selector.replace(/\s{2}/g,' ') // remove double spaces
|
|
262
|
+
selector = selector.replace(/\s?\^?\$?\|?\~?\*?\=\s*/g,(m) => m.trim()) // remove spaces inside []
|
|
263
|
+
selector = selector.replace(/\s?(\+|\~|\>)\s?/g,(m) => m.trim()) // remove spaces around +|~|>
|
|
264
|
+
return selector
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
export class HtmlParser {
|
|
268
|
+
static parse(html) {
|
|
269
|
+
let result = new HtmlParser(html)
|
|
270
|
+
return result.root
|
|
271
|
+
}
|
|
272
|
+
constructor(html='') {
|
|
273
|
+
if(this.checkHtml(html)) {
|
|
274
|
+
this.indexes = []
|
|
275
|
+
this.events = []
|
|
276
|
+
this.html = this.htmlString = html
|
|
277
|
+
this.htmlString = this.htmlString.replace(/\<\!\-\-([\S\s]*?)\-\-\>/gm,'') // remove all comments
|
|
278
|
+
this.removeScripts()
|
|
279
|
+
this.removeStyles()
|
|
280
|
+
this.removeEventStrings()
|
|
281
|
+
this.root = this.parse()
|
|
282
|
+
this.clean()
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
clean() {
|
|
287
|
+
let toDelete = ['events','indexes','scripts','styles','htmlString','html']
|
|
288
|
+
toDelete.forEach(name => {
|
|
289
|
+
delete this[name]
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
checkHtml(html,isGood=false) {
|
|
294
|
+
if(html == '') console.log('html parameter is empty')
|
|
295
|
+
else if(typeof html !== 'string') console.log(`html parameter has to be string. Recieved ${typeof html}`)
|
|
296
|
+
else isGood = true
|
|
297
|
+
return isGood
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
removeScripts(scripts=[]) {
|
|
301
|
+
let $scripts = this.htmlString.match(/\<script(.*?)\>[\S\s]*?\<\/script\>/gm)
|
|
302
|
+
if($scripts !== null) $scripts.forEach((script,i) => {
|
|
303
|
+
let inner = script.replace(/^\<script(.*?)\>/,'').replace(/\<\/script\>$/,'')
|
|
304
|
+
scripts.push(inner)
|
|
305
|
+
this.htmlString = this.htmlString.replace(inner,`{{{{script ${scripts.length-1}`)
|
|
306
|
+
});
|
|
307
|
+
this.scripts = scripts
|
|
308
|
+
}
|
|
309
|
+
removeStyles(styles=[]) {
|
|
310
|
+
let $styles = this.htmlString.match(/\<style\>[\S\s]*?\<\/style\>/gm)
|
|
311
|
+
if($styles !== null) $styles.forEach((style,i) => {
|
|
312
|
+
let inner = style.replace(/^\<style\>/,'').replace(/\<\/style\>$/,'')
|
|
313
|
+
styles.push(inner)
|
|
314
|
+
this.htmlString = this.htmlString.replace(inner,`{{{{style ${styles.length-1}`)
|
|
315
|
+
});
|
|
316
|
+
this.styles = styles
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
removeEventStrings() {
|
|
320
|
+
let eventsWithHtml = this.htmlString.match(/\son\w*\s*?\=\s*?\"(.*?)\"/g)
|
|
321
|
+
if(eventsWithHtml !== null) {
|
|
322
|
+
eventsWithHtml.forEach(event => {
|
|
323
|
+
let array = event.split('=')
|
|
324
|
+
let value = array.filter((v,i) => i>0 && i!=='').join('=')
|
|
325
|
+
value = value.replace(/^\"/,'').replace(/\"$/,'')
|
|
326
|
+
this.events.push(value)
|
|
327
|
+
let newEvent = event.replace(value,`{{{{event ${this.events.length-1}`)
|
|
328
|
+
this.htmlString = this.htmlString.replace(event,newEvent)
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
parse(htmlString=this.htmlString) {
|
|
334
|
+
// Parse tags
|
|
335
|
+
let elements = htmlString.match(/<("[^"]*"|'[^']*'|[^'">])*>/g)
|
|
336
|
+
elements.forEach((tag,index) => {
|
|
337
|
+
elements[index] = this.parseElement(tag)
|
|
338
|
+
htmlString = htmlString.replace(tag,`<tag${index}>`)
|
|
339
|
+
});
|
|
340
|
+
// Parse inner text
|
|
341
|
+
let inners = htmlString.match(/tag[\s\S]*?\</g)
|
|
342
|
+
for (let i = inners.length-1; i >=0 ; i--) {
|
|
343
|
+
let inner = inners[i];
|
|
344
|
+
let tagIndex = inner.match(/(\d*)\>/)[1]
|
|
345
|
+
inner = inner.replace(/tag.*\>/,'').slice(0, -1).trim()
|
|
346
|
+
if(inner.length > 0) {
|
|
347
|
+
elements.splice(parseInt(tagIndex)+1, 0, inner);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
this.elements = elements
|
|
351
|
+
let root = this.getPairs()
|
|
352
|
+
root.level = 0
|
|
353
|
+
root.elements = this.elements
|
|
354
|
+
this.innerHTML(root)
|
|
355
|
+
return root
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
lookForPair(element,startIndex,parent,level) {
|
|
359
|
+
element.parent = parent
|
|
360
|
+
element.children = []
|
|
361
|
+
let {tag} = element
|
|
362
|
+
let count = 0
|
|
363
|
+
let endIndex
|
|
364
|
+
for (let index = startIndex+1; index < this.elements.length; index++) {
|
|
365
|
+
const el = this.elements[index];
|
|
366
|
+
if(el.tag == tag) {
|
|
367
|
+
if(el.status == 'close') {
|
|
368
|
+
if(count == 0) {
|
|
369
|
+
endIndex = index
|
|
370
|
+
el.level = level
|
|
371
|
+
break
|
|
372
|
+
} else count--
|
|
373
|
+
} else if(el.status == 'open') count++
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if(!endIndex) endIndex = startIndex+1
|
|
377
|
+
element.endIndex = endIndex
|
|
378
|
+
let child = this.getPairs(element,startIndex+1,endIndex,level+1)
|
|
379
|
+
return child
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
getPairs(parent={type:'root',children:[]},startIndex = 0,endIndex=this.elements.length,level=0,childIndex=0) {
|
|
383
|
+
for(let index = startIndex; index < endIndex; index++) {
|
|
384
|
+
if(this.indexes.includes(index)) continue
|
|
385
|
+
let element = this.elements[index];
|
|
386
|
+
let child
|
|
387
|
+
if(typeof element == 'string') child = element
|
|
388
|
+
else if(element.type == 'tag') {
|
|
389
|
+
if(element.status == 'single') {
|
|
390
|
+
child = element
|
|
391
|
+
child.parent = parent
|
|
392
|
+
} else if(element.status == 'open') {
|
|
393
|
+
child = this.lookForPair(element,index,parent,level)
|
|
394
|
+
} else element.index = index
|
|
395
|
+
}
|
|
396
|
+
this.addChild(parent,child,index,level)
|
|
397
|
+
childIndex++
|
|
398
|
+
}
|
|
399
|
+
return parent
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
addScriptsAndStyles(parent,child,index) {
|
|
403
|
+
if(parent.tag == 'script') {
|
|
404
|
+
let scriptIndex = child.match(/(?<=\{\{\{\{script\s)(\d*)?/)
|
|
405
|
+
if(scriptIndex !== null) {
|
|
406
|
+
let i = parseInt(scriptIndex[0])
|
|
407
|
+
if(typeof i == 'number') child = this.scripts[i]
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if(parent.tag == 'style') {
|
|
411
|
+
let stylesIndex = child.match(/(?<=\{\{\{\{style\s)(\d*)?/)
|
|
412
|
+
if(stylesIndex !== null) {
|
|
413
|
+
let i = parseInt(stylesIndex[0])
|
|
414
|
+
if(typeof i == 'number') child = this.styles[i]
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
this.elements[index] = child
|
|
418
|
+
return child
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
addChild(parent,child,index,level) {
|
|
422
|
+
if(typeof child == 'string') {
|
|
423
|
+
child = this.addScriptsAndStyles(parent,child,index)
|
|
424
|
+
child = {type:'text',text:child,parent}
|
|
425
|
+
this.elements[index] = child
|
|
426
|
+
}
|
|
427
|
+
if(child) {
|
|
428
|
+
delete child.status
|
|
429
|
+
if(child.type !== 'text') {
|
|
430
|
+
this.innerHTML(child)
|
|
431
|
+
this.outerHTML(child)
|
|
432
|
+
this.innerText(child)
|
|
433
|
+
}
|
|
434
|
+
this.getElements(child)
|
|
435
|
+
this.getAncestors(child)
|
|
436
|
+
this.getAttribute(child)
|
|
437
|
+
this.nextAndPrev(child)
|
|
438
|
+
child.index = index
|
|
439
|
+
child.level = level
|
|
440
|
+
parent.children.push(child)
|
|
441
|
+
}
|
|
442
|
+
this.indexes.push(index)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
getAttribute(element) {
|
|
446
|
+
element.getAttribute = function(name) {
|
|
447
|
+
let array = Object.keys(this.attribs).filter(key => key == name)
|
|
448
|
+
return array.length > 0 ? this.attribs[array[0]] : null
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
getAncestors(element) {
|
|
453
|
+
Object.defineProperty(element, 'ancestors', { get() {
|
|
454
|
+
let ancestors = []
|
|
455
|
+
let parent = this.parent
|
|
456
|
+
if(parent) while(parent.parent) {
|
|
457
|
+
ancestors.unshift(parent)
|
|
458
|
+
parent = parent.parent
|
|
459
|
+
}
|
|
460
|
+
return ancestors
|
|
461
|
+
}});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
nextAndPrev(element) {
|
|
465
|
+
Object.defineProperty(element, 'childIndex', {
|
|
466
|
+
get() {return this.parent.children.map(o => o.index).indexOf(this.index)}
|
|
467
|
+
});
|
|
468
|
+
Object.defineProperty(element, 'prev', {
|
|
469
|
+
get() {
|
|
470
|
+
let i = this.childIndex, brothers = this.parent.children
|
|
471
|
+
return i == 0 ? null : brothers[i-1]
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
Object.defineProperty(element, 'next', {
|
|
475
|
+
get() {
|
|
476
|
+
let i = this.childIndex, brothers = this.parent.children
|
|
477
|
+
return i == brothers.length-1 ? null : brothers[i+1]
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
getElements(element,self=this) {
|
|
483
|
+
if(element.type !== 'text')
|
|
484
|
+
Object.defineProperty(element, 'elements', {
|
|
485
|
+
configurable:true,
|
|
486
|
+
get() {
|
|
487
|
+
let endIndex = isNaN(this.endIndex) ? this.index + 1 : this.endIndex+1
|
|
488
|
+
return self.elements.slice(this.index,endIndex)
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
Object.defineProperty(element, '$elements', {
|
|
492
|
+
configurable:true,
|
|
493
|
+
get() {return self.elements}
|
|
494
|
+
});
|
|
495
|
+
Object.defineProperty(element, 'root', {
|
|
496
|
+
configurable:true,
|
|
497
|
+
get() {return self.root}
|
|
498
|
+
});
|
|
499
|
+
Object.defineProperty(element, 'html', {
|
|
500
|
+
configurable:true,
|
|
501
|
+
get() {return self}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
outerHTML(element) {
|
|
506
|
+
Object.defineProperty(element, 'outerHTML', { get() {
|
|
507
|
+
let {elements} = this
|
|
508
|
+
return elements[0].text + this.innerHTML + elements[elements.length-1].text
|
|
509
|
+
}});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
innerText(element) {
|
|
513
|
+
Object.defineProperty(element, 'innerText', { get() {
|
|
514
|
+
if(element.children)
|
|
515
|
+
return element.children.map(child =>
|
|
516
|
+
child.type == 'text' ? child.text.trim()
|
|
517
|
+
: child.type == 'tag'
|
|
518
|
+
? child.innerText
|
|
519
|
+
: ''
|
|
520
|
+
).join(' ')
|
|
521
|
+
else return ''
|
|
522
|
+
}})
|
|
523
|
+
Object.defineProperty(element, 'inner', { get() {
|
|
524
|
+
if(element.children)
|
|
525
|
+
return element.children.map(child => child.type == 'text' ? child.text : '').join('')
|
|
526
|
+
else return ''
|
|
527
|
+
}})
|
|
528
|
+
}
|
|
529
|
+
innerHTML(element) {
|
|
530
|
+
element.tab = ' '
|
|
531
|
+
element.n = '\n'
|
|
532
|
+
Object.defineProperty(element, 'innerHTML', { get() {
|
|
533
|
+
// let tab = ' ',result = '',firstLevel,space=''
|
|
534
|
+
let {tab,n} = this
|
|
535
|
+
let result = '',firstLevel,space=''
|
|
536
|
+
let {elements} = this
|
|
537
|
+
let endIndex = elements.length
|
|
538
|
+
let end = endIndex-1,start=1
|
|
539
|
+
if(this.type == 'root') {
|
|
540
|
+
end = endIndex
|
|
541
|
+
start = 0
|
|
542
|
+
}
|
|
543
|
+
for(let i = start; i < end; i++) {
|
|
544
|
+
let element = elements[i]
|
|
545
|
+
let {level,text} = element
|
|
546
|
+
if(firstLevel == undefined) firstLevel = level
|
|
547
|
+
if(i == end) space = ''
|
|
548
|
+
else if(level) space = Array.from(Array(level-firstLevel).keys()).map(n => tab).join('')
|
|
549
|
+
result += space + text
|
|
550
|
+
if(endIndex > 3) result += n
|
|
551
|
+
}
|
|
552
|
+
return result
|
|
553
|
+
}});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
parseElement(tagString) {
|
|
557
|
+
let type = 'tag'
|
|
558
|
+
if(tagString == '<!DOCTYPE html>') return {tag:'!DOCTYPE html',status:'single',attribs:{},type,text:tagString}
|
|
559
|
+
let status = 'close'
|
|
560
|
+
let tag = tagString.match(/(?<=\<\/)(\w*\-?)*/)
|
|
561
|
+
if(tag == null) {
|
|
562
|
+
tag = tagString.match(/(?<=\<)(\w*\-?)*/)
|
|
563
|
+
if(tag) {
|
|
564
|
+
tag = tag[0]
|
|
565
|
+
if(HtmlParser.singleTags.includes(tag)) status='single'
|
|
566
|
+
else status = 'open'
|
|
567
|
+
}
|
|
568
|
+
} else tag = tag[0]
|
|
569
|
+
let {classList,attribs,style,id,text} = this.parseAttributes(tagString)
|
|
570
|
+
let obj = {tag,status,attribs,type,classList,text,style,id}
|
|
571
|
+
return obj
|
|
572
|
+
}
|
|
573
|
+
static singleTags = ['comment','area','base','br','col','command','embed','hr','img','input','keygen','link','meta','param','source','track','wbr']
|
|
574
|
+
|
|
575
|
+
parseAttributes(tagString,classList=[],attribs={},style={},id=null) {
|
|
576
|
+
let text = tagString
|
|
577
|
+
let attributes = tagString.match(/(\w+)(?:\s*=\s*(['"]?)(.*?)\2)?/g)
|
|
578
|
+
if(attributes) attributes.forEach((attribString,i) => {
|
|
579
|
+
if(i == 0) return
|
|
580
|
+
let array = attribString.split('=')
|
|
581
|
+
let name = array[0]
|
|
582
|
+
if(array.length>1 && name !== '') {
|
|
583
|
+
let value = array.filter((v,i) => i>0).join('=')
|
|
584
|
+
value = value.trim().replace(/^\"|\'/,'').replace(/\"|\'$/m,'')
|
|
585
|
+
let eventIndex = value.match(/(?<=\{\{\{\{event\s)(\d*)?/)
|
|
586
|
+
if(eventIndex !== null) {
|
|
587
|
+
value = this.events[eventIndex[0]]
|
|
588
|
+
text = tagString.replace(`{{{{event ${eventIndex[0]}`,value)
|
|
589
|
+
}
|
|
590
|
+
if(name == 'class') classList = value.split(/\s\s?\s?/)
|
|
591
|
+
else if(name == 'style') style = this.parseInlineCss(value)
|
|
592
|
+
else if(name == 'id') id = value
|
|
593
|
+
attribs[name] = value
|
|
594
|
+
} else if(name !== '') attribs[name] = undefined
|
|
595
|
+
});
|
|
596
|
+
return {classList,attribs,style,id,text}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
parseInlineCss(textCss) {
|
|
600
|
+
let rules = textCss.split(';')
|
|
601
|
+
let styles = {}
|
|
602
|
+
rules.forEach(rule => {
|
|
603
|
+
let [prop,value] = rule.trim().split(':')
|
|
604
|
+
if(rule !== '') {
|
|
605
|
+
if(prop.match(/\w*\-\w*(-\w*)?/) !== null) {
|
|
606
|
+
let words = prop.split('-')
|
|
607
|
+
prop = words.map((w,i) => i==0 ? w : w[0].toUpperCase() + w.slice(1)).join('')
|
|
608
|
+
}
|
|
609
|
+
styles[prop] = value.trim()
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
return styles
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
export class Document extends HtmlSelector {
|
|
616
|
+
constructor(html) {
|
|
617
|
+
super(html)
|
|
618
|
+
this.elements.forEach((element) => {this.addMethods(element)});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
addMethods(element) {
|
|
622
|
+
if(element.type == 'tag' && element.status !== 'close') {
|
|
623
|
+
this.insert(element)
|
|
624
|
+
this.buildAttribs(element)
|
|
625
|
+
this.classlistMethods(element)
|
|
626
|
+
this.style(element)
|
|
627
|
+
this.id(element)
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
id(element,self=this) {
|
|
632
|
+
if(element.id !== undefined)
|
|
633
|
+
if(!!Object.getOwnPropertyDescriptor(element, 'id')['get']) return
|
|
634
|
+
if(element.id == null || element.id !== undefined) {
|
|
635
|
+
if(typeof element.id == 'string') delete element.id
|
|
636
|
+
Object.defineProperty(element, 'id', {
|
|
637
|
+
get() {return this.attribs.id ? this.attribs.id : null},
|
|
638
|
+
set(value) {
|
|
639
|
+
this.attribs.id = value
|
|
640
|
+
self.changeElementText(this)
|
|
641
|
+
}
|
|
642
|
+
})
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
style(element,obj={},self=this) {
|
|
647
|
+
if(element.style !== undefined) obj = {...element.style}
|
|
648
|
+
const convertProp = str => str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
|
|
649
|
+
const handler = {
|
|
650
|
+
get(target,prop,receiver) {
|
|
651
|
+
if(prop === 'target') return target
|
|
652
|
+
return Reflect.get(...arguments);
|
|
653
|
+
},
|
|
654
|
+
set(obj, prop, value) {
|
|
655
|
+
obj[prop] = value
|
|
656
|
+
element.attribs.style = Object.keys(obj).map(key => `${convertProp(key)}:${obj[key]};`).join('')
|
|
657
|
+
self.changeElementText(this)
|
|
658
|
+
return true
|
|
659
|
+
},
|
|
660
|
+
deleteProperty(obj, prop) {
|
|
661
|
+
if (prop in obj) {
|
|
662
|
+
delete obj[prop];
|
|
663
|
+
element.attribs.style = Object.keys(obj).map(key => `${convertProp(key)}:${obj[key]};`).join('')
|
|
664
|
+
}
|
|
665
|
+
self.changeElementText(this)
|
|
666
|
+
return true
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
element.style = new Proxy(obj, handler);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
classlistMethods(element,self=this) {
|
|
673
|
+
if(element.classList == undefined) element.classList = []
|
|
674
|
+
element.classList.add = function(newClass) {
|
|
675
|
+
if(element.attribs.class == undefined) element.attribs.class = ''
|
|
676
|
+
this.push(newClass)
|
|
677
|
+
element.attribs.class += ' '+newClass
|
|
678
|
+
self.changeElementText(this)
|
|
679
|
+
}
|
|
680
|
+
element.classList.remove = function(clsToRemove) {
|
|
681
|
+
this.splice(this.indexOf(clsToRemove),1)
|
|
682
|
+
element.attribs.class = this.filter(c => c !== undefined).join(' ')
|
|
683
|
+
self.changeElementText(this)
|
|
684
|
+
}
|
|
685
|
+
element.classList.toggle = function(clsToToggle) {
|
|
686
|
+
if(this.includes(clsToToggle)) this.remove(clsToToggle)
|
|
687
|
+
else this.add(clsToToggle)
|
|
688
|
+
self.changeElementText(this)
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
buildAttribs(element,self=this) {
|
|
693
|
+
if(!element.attr) element.attr = function(name,value) {
|
|
694
|
+
let notForRemove = ['class','style','id']
|
|
695
|
+
let notForChange = ['style','id']
|
|
696
|
+
if(name && value === undefined) return this.attribs[name]
|
|
697
|
+
else if(name && value === null && !notForRemove.includes(name)) {
|
|
698
|
+
delete this.attribs[name]
|
|
699
|
+
} else if(name && value !== undefined && value !== null && !notForChange.includes(name)) {
|
|
700
|
+
this.attribs[name] = value
|
|
701
|
+
if(name == 'class') {
|
|
702
|
+
this.classList = value.split(' ').filter(c => c !== ' ')
|
|
703
|
+
self.classlistMethods(this)
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
self.changeElementText(this)
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
insert(element) {
|
|
711
|
+
let self = this
|
|
712
|
+
element.insert = function(newElement,pos=0) {
|
|
713
|
+
let elements
|
|
714
|
+
let $elements = this.$elements
|
|
715
|
+
if(typeof newElement == 'string') {
|
|
716
|
+
newElement = new HtmlParser(newElement)
|
|
717
|
+
newElement.elements.forEach(el => {
|
|
718
|
+
self.html.getElements(el)
|
|
719
|
+
self.makeSelectable(el);
|
|
720
|
+
self.addMethods(el);
|
|
721
|
+
});
|
|
722
|
+
elements = newElement.elements
|
|
723
|
+
newElement = newElement.root.children[0]
|
|
724
|
+
} else {
|
|
725
|
+
let {index,endIndex} = newElement
|
|
726
|
+
let count = isNaN(endIndex - index) ? 1 : endIndex - index+1
|
|
727
|
+
elements = $elements.splice(index,count)
|
|
728
|
+
let {parent,childIndex} = newElement
|
|
729
|
+
parent.children.splice(childIndex,1)
|
|
730
|
+
}
|
|
731
|
+
self.rebildElements($elements)
|
|
732
|
+
self.addElement(this,$elements,newElement,elements,pos)
|
|
733
|
+
}
|
|
734
|
+
element.remove = function() {
|
|
735
|
+
let {$elements,elements,parent,childIndex,index} = this
|
|
736
|
+
$elements.splice(index,elements.length)
|
|
737
|
+
parent.children.splice(childIndex,1)
|
|
738
|
+
parent.endIndex = parent.endIndex-elements.length
|
|
739
|
+
self.rebildElements($elements)
|
|
740
|
+
}
|
|
741
|
+
Object.defineProperty(element, 'before', { set(newEl) {this.insert(newEl,0)}})
|
|
742
|
+
Object.defineProperty(element, 'after', { set(newEl) {this.insert(newEl,3)}})
|
|
743
|
+
Object.defineProperty(element, 'last', { set(newEl) {this.insert(newEl,2)}})
|
|
744
|
+
Object.defineProperty(element, 'first', { set(newEl) {this.insert(newEl,1)}})
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
addElement(element,$elements,newElement,elements,pos) {
|
|
748
|
+
let {parent,level,index,endIndex} = element
|
|
749
|
+
if(pos == 0) { // beforebegin
|
|
750
|
+
$elements.splice(index,0,...elements) // add elements to $elements
|
|
751
|
+
parent.children.splice(element.childIndex,0,newElement) // add as child before
|
|
752
|
+
} else if(pos == 3) { // afterend
|
|
753
|
+
$elements.splice(endIndex+1,0,...elements) // add elements to $elements
|
|
754
|
+
parent.children.splice(element.childIndex+1,0,newElement) // add element as child after
|
|
755
|
+
} else if(pos == 1 || pos == 2) {
|
|
756
|
+
level = level+1
|
|
757
|
+
parent = element
|
|
758
|
+
if(pos == 1) { // afterbegin
|
|
759
|
+
$elements.splice(index+1,0,...elements) // add elements to $elements
|
|
760
|
+
element.children.unshift(newElement) // add element as first child
|
|
761
|
+
} else if(pos == 2) { // beforeend
|
|
762
|
+
$elements.splice(endIndex-1,0,...elements) // add elements to $elements
|
|
763
|
+
element.children.push(newElement) // add element as last child
|
|
764
|
+
}
|
|
765
|
+
element.endIndex = element.endIndex + elements.length
|
|
766
|
+
}
|
|
767
|
+
this.rebildElements($elements)
|
|
768
|
+
this.rebuildChild(elements[0],parent,element.html,level)
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
rebildElements($elements) {
|
|
772
|
+
for(let i=0; i<$elements.length; i++) {
|
|
773
|
+
let {endIndex,index} = $elements[i]
|
|
774
|
+
if(endIndex) {
|
|
775
|
+
let gap = endIndex - index
|
|
776
|
+
$elements[i].endIndex = i + gap
|
|
777
|
+
}
|
|
778
|
+
$elements[i].index = i
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
rebuildChild(child,parent,html,level) {
|
|
783
|
+
if(child.endIndex !== undefined) { // add level to close tag
|
|
784
|
+
child.elements[child.elements.length-1].level = level
|
|
785
|
+
}
|
|
786
|
+
html.getElements(child,html) // bind element to this.$elements
|
|
787
|
+
child.parent = parent
|
|
788
|
+
child.level = level
|
|
789
|
+
if(child.children)
|
|
790
|
+
if(child.children.length > 0)
|
|
791
|
+
child.children.forEach(grandChild => {
|
|
792
|
+
this.rebuildChild(grandChild,child,html,level+1)
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
changeElementText(element) {
|
|
797
|
+
let {type,status,attribs,tag} = element
|
|
798
|
+
if(type == 'tag' && status !== 'close') {
|
|
799
|
+
let atts = Object.keys(attribs)
|
|
800
|
+
atts = atts.length>0 ? ' '+atts.map(name => `${name}${attribs[name] ? `="${attribs[name]}"`: ''}`).join(' ') : ''
|
|
801
|
+
element.text = `<${tag}${atts}>`
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|