als-document 0.6.1 → 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.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
+ }