als-document 0.12.0 → 1.0.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/document.js CHANGED
@@ -1,590 +1,42 @@
1
- class Document {
2
- static singlTags = ['comment','area','base','br','col','command','embed','hr','img','input','keygen','link','meta','param','source','track','wbr']
3
- constructor(htmlText) {
4
- if(typeof htmlText !== 'string') return
5
- this.types = {}
6
- this.domTree = []
7
- this.added = []
8
- this.domList = []
9
- this.comments = []
10
- this.scripts = {}
11
- this.events = {}
12
- this.htmlText = htmlText
13
- this.doctype = ''
14
- this.getDoctype()
15
- this.removeComments()
16
- this.removeScripts()
17
- this.removeEvents()
18
- this.parseTags()
19
- this.addScripts()
20
- this.addEvents()
21
- this.addComments()
22
- this.getPairs()
23
- }
24
- // Get domList
25
- getDoctype() {
26
- let doctype = this.htmlText.match(/\<\!DOCTYPE([\S\s]*?)\>/gm,'')
27
- if(doctype !== null) {
28
- this.doctype = doctype[0]
29
- this.htmlText = this.htmlText.replace(/\<\!DOCTYPE([\S\s]*?)\>/gm,'')
30
- }
31
- }
32
-
33
- removeComments() {
34
- let comments = this.htmlText.match(/\<\!\-\-([\S\s]*?)\-\-\>/gm,'')
35
- if(comments !== null) comments.forEach((comment,i) => {
36
- let commentTag = `<comment comment-id="${i}">`
37
- this.comments.push(comment)
38
- this.htmlText = this.htmlText.replace(comment,commentTag)
39
- });
40
- }
41
-
42
- addComments() {
43
- this.domList.forEach((element,i) => {
44
- if(element.tagName == 'comment') {
45
- let id = this.domList[i].attributes['comment-id']
46
- let comment = this.comments[id]
47
- if(comment !== undefined) {
48
- this.domList[i].comment = comment
49
- delete this.comments[i]
50
- }
51
- }
52
- });
53
- }
54
-
55
- removeScripts() {
56
- let scripts = this.htmlText.match(/\<script(.*?)\>[\S\s]*?\<\/script\>/gm)
57
- if(scripts !== null) scripts.forEach((script,i) => {
58
- let scriptContent = script.replace(/\<script(.*?)\>/,'').replace('</script>','')
59
- if(scriptContent.trim() !== '') {
60
- let scriptName = `{script${i}}`
61
- let newScript = script.replace(scriptContent,scriptName)
62
- this.scripts[scriptName] = scriptContent
63
- this.htmlText = this.htmlText.replace(script,newScript)
64
- }
65
- });
66
- }
67
-
68
- removeEvents() {
69
- let events = ['abort','afterprint','animationend','animationiteration','animationstart','beforeprint','beforeunload','blur','canplay','canplaythrough','change','click','contextmenu','copy','cut','dblclick','drag','dragend','dragenter','dragleave','dragover','dragstart','drop','durationchange','ended','error','focus','focusin','focusout','fullscreenchange','fullscreenerror','hashchange','input','invalid','keydown','keypress','keyup','load','loadeddata','loadedmetadata','loadstart','message','mousedown','mouseenter','mouseleave','mousemove','mouseover','mouseout','mouseup','mousewheel','offline','online','open','pagehide','pageshow','paste','pause','play','playing','popstate','progress','ratechange','resize','reset','scroll','search','seeked','seeking','select','show','stalled','storage','submit','suspend','timeupdate','toggle','touchcancel','touchend','touchmove','touchstart','transitionend','unload','volumechange','waiting','wheel']
70
- events.forEach(event => {
71
- let r = new RegExp(`on${event}\\s?\\=\\s?"[\\S\\s]*?\\"`,'g')
72
- let scripts = this.htmlText.match(r)
73
- if(scripts !== null) scripts.forEach((script,i) => {
74
- let scriptContent = script.replace(`on${event}`,'').replace('=','').replace(/\"/g,'')
75
- if(scriptContent.trim() !== '') {
76
- let index = Object.keys(this.events).length
77
- let scriptName = `{event${index}}`
78
- let newScript = script.replace(scriptContent,scriptName)
79
- this.events[scriptName] = scriptContent
80
- this.htmlText = this.htmlText.replace(script,newScript)
81
- }
82
- });
83
- });
84
- }
85
-
86
- addScripts() {
87
- this.domList.forEach((element,i) => {
88
- if(element.tagName == 'script' && !element.close) {
89
- let text = this.domList[i+1].text
90
- if(this.scripts[text] !== undefined) {
91
- this.domList[i+1].text = this.scripts[text]
92
- delete this.scripts[text]
93
- }
94
- }
95
- });
96
- }
97
-
98
- addEvents() {
99
- this.domList.forEach((element,i) => {
100
- if(element.attributes !== undefined) {
101
- for(let attName in element.attributes) {
102
- let attValue = element.attributes[attName]
103
- if(this.events[attValue] !== undefined) {
104
- if(this.domList[i].attributes[attName] == attValue) {
105
- this.domList[i].attributes[attName] = this.events[attValue]
106
- delete this.events[attValue]
107
- }
108
- }
109
- }
110
- }
111
- });
112
- }
113
-
114
- parseTags() {
115
- let outerHTML = this.htmlText
116
- let outerHtmlForCut = outerHTML
117
- let tags = outerHTML.match(/\<(\w*-?)*?(.*?)?([\S\s]*?)\>/gm)
118
- let index = 0
119
- if(tags !== null) tags.forEach((tag,i) => {
120
- let node = outerHtmlForCut.split(tag)[0]
121
- if(node.trim() !== '') {
122
- this.domList.push({text:node,index})
123
- index++
124
- }
125
- let obj = this.buildElementObj(tag)
126
- let objName = obj.tagName+obj.order
127
- obj.index = index
128
- if(obj.order.length == 0) { // single tag
129
- this.domList.push(obj)
130
- } else this.domList.push({[objName]:obj,name:objName,index})
131
- if(obj.close) {
132
- let j = this.findOpen(objName)
133
- if(j >=0) {
134
- let closeTag = index
135
- let openTagObj = this.domList[j][this.domList[j].name]
136
- this.domList[j] = {...openTagObj,closeTag}
137
- }
138
- this.domList[obj.index] = obj
139
- }
140
- outerHtmlForCut = outerHtmlForCut.replace(node+tag,'')
141
- index++
142
- });
143
- }
144
- // Build element
145
- buildElementObj(string,obj={}) {
146
- let singlTags = Document.singlTags
147
- if(string.match(/\<\/(\w*-?)*/) !== null) obj.close = true
148
- else obj.close = false
149
- obj.tagName = string.match(/\<\/?(\w*-?)*/,'gm')[0].replace(/\<\/?/,'')
150
- if(!singlTags.includes(obj.tagName)) {
151
- obj.tagName = obj.tagName.replace('/','')
152
- let tagName = obj.tagName
153
- this.getType(tagName,obj)
154
- obj.order = this.types[tagName].count
155
- } else obj.order = ''
156
- obj.attributes = this.getAttributes(string)
157
- obj.getAttribute = (attName) => obj.attributes[attName]
158
- obj.setAttribute = (attName,attValue) => {obj.attributes[attName] = attValue}
159
- obj = this.buildClass(obj)
160
- if(obj.attributes.id !== undefined) {
161
- obj.id = obj.attributes.id
162
- delete obj.attributes.id
163
- }
164
- return obj
165
- }
166
-
167
- getType(tagName,obj) {
168
- if(this.types[tagName] == undefined)
169
- this.types[tagName] = {count:0,lastOperation:1}
170
- else if(obj.close && this.types[tagName].lastOperation == 1)
171
- this.types[tagName].lastOperation = 0
172
- else if(obj.close && this.types[tagName].lastOperation == 0)
173
- this.types[tagName].count--
174
- else if(!obj.close && this.types[tagName].lastOperation == 0)
175
- this.types[tagName].lastOperation = 1
176
- else if(!obj.close && this.types[tagName].lastOperation == 1)
177
- this.types[tagName].count++
178
- }
179
-
180
- getAttributes(string,attributes = {}) {
181
- let matches = string.match(/\s(\/?\w*\-?)*((\S)*?\s?=[\S\s]*?\"[\S\s]*?\")?/g)
182
- if(matches !== null) matches.forEach(attribute => {
183
- attribute = attribute.trim()
184
- if(attribute !== '') {
185
- let attName = attribute.split('=')[0]
186
- let attValue = attribute.replace(attName,'').replace('=','').replace(/\"/g,'')
187
- if(attName !== '/') attributes[attName] = attValue.trim()
188
- }
189
- })
190
- return attributes
191
- }
192
-
193
- buildClass(obj) {
194
- if(obj.attributes.class !== undefined) {
195
- obj.classList = obj.attributes.class.split(' ')
196
- } else obj.classList = []
197
- obj.classList.add = newClass => {
198
- obj.classList.push(newClass)
199
- obj.attributes.class += ' '+newClass
200
- return obj
201
- }
202
- obj.classList.remove = clas => {
203
- let index = obj.classList.indexOf(clas)
204
- if(index >=0) {
205
- obj.attributes.class = obj.attributes.class.replace(obj.classList[index],'')
206
- obj.classList.splice(index,1)
207
- }
208
- return obj
209
- }
210
- return obj
211
- }
212
- // Build dom tree
213
- getPairs(start=0,end=this.domList.length,domTree=this.domTree) {
214
- for(let index=start; index<end; index++) {
215
- let node = this.domList[index]
216
- if(node.closeTag !== undefined && !this.added.includes(index)) {
217
- if(this.firstTag == undefined) this.firstTag = index
218
- this.buildTag(index,domTree)
219
- break;
220
- }
221
- }
222
- }
223
-
224
- buildTag(openIndex,domTree,parent=this.domTree) {
225
- this.added.push(openIndex)
226
- let content = this.domList[openIndex]
227
- content.children = []
228
- content.innerText = ''
229
- this.getChildNodes(openIndex,content.closeTag,content)
230
- content.$ = (selector) => this.$(selector,content)
231
- content.$$ = (selector) => this.$$(selector,content)
232
- content.parent = parent
233
- content = Document.buildMethods(content)
234
- this.buildOperations(content)
235
- if(content.innerText !== '') {
236
- if(parent.innerText == undefined) parent.innerText = ''
237
- parent.innerText += '|'+content.innerText
238
- }
239
- content.json = () => Document.removeMethods(content)
240
- delete content.closeTag
241
- delete content.close
242
- delete content.index
243
- delete content.order
244
- domTree.push(content)
245
- }
246
-
247
- static buildMethods(content) {
248
- content.children.forEach((child,index) => { // prev and next
249
- if(index == 0) child.prev = null
250
- else child.prev = content.children[index-1]
251
- if(index == content.children.length-1) child.next = null
252
- else child.next = content.children[index+1]
253
- });
254
- return content
255
- }
256
-
257
- static changeByIndex(array,value,item,before = true) {
258
- let index = array.indexOf(value);
259
- if(index > -1) {
260
- if(item == undefined) array.splice(index, 1); // remove value
261
- else if(item !== undefined) {
262
- if(before) array.splice(index, 0, item); // add item before value
263
- else array.splice(index+1, 0, item); // add item after value
264
- }
265
- }
266
- }
267
-
268
- static buildInnerText(element) {
269
- element.innerText = ''
270
- if(element.children !== undefined) element.children.forEach(child => {
271
- if(child.text !== undefined && child.text !== '') element.innerText += child.text
272
- if(child.innerText !== undefined && child.innerText !== '') element.innerText += '|'+child.innerText
273
- });
274
- }
275
-
276
- buildOperations(content) {
277
- content.remove = function() {
278
- Document.changeByIndex(content.parent.children,content)
279
- Document.buildInnerText(content.parent)
280
- }
281
- content.add = function(element,place) {
282
- if(typeof element == 'string') element = Document.newElement(element)
283
- else element.remove()
284
- if(place == 1) content.children.unshift(element)
285
- if(place == 2) content.children.push(element)
286
- if(place == 1 || place == 2) build(content)
287
-
288
- if(place == 0) Document.changeByIndex(content.parent.children,content,element,true)
289
- if(place == 3) Document.changeByIndex(content.parent.children,content,element,false)
290
- if(place == 0 || place == 3) build(content.parent)
291
-
292
- function build(parent) {
293
- element.parent = parent
294
- element = Document.buildMethods(parent)
295
- Document.buildInnerText(parent)
296
- }
297
- return element
298
- }
299
- content.add0 = element => content.add(element,0)
300
- content.add1 = element => content.add(element,1)
301
- content.add2 = element => content.add(element,2)
302
- content.add3 = element => content.add(element,3)
303
- }
304
-
305
- static newElement(outerHtml) {
306
- let document = new Document(outerHtml)
307
- return document.domTree[0].json()
308
- }
309
-
310
- build(filePath,array=this.domTree,html = '',space='') {
311
- let extraSpace = space+' '
312
- let nl = `
313
- `
314
- array.forEach(element => {
315
- if(element.text !== undefined) html += space+element.text+nl
316
- else if(element.tagName == 'comment' ) {
317
- html += space+element.comment + nl
318
- } else {
319
- let attributes = ''
320
- for(let propName in element.attributes) {
321
- let value = element.attributes[propName]
322
- let attribute = ` ${propName}="${value}"`
323
- if(attribute.length + attributes.length > 80) attribute = nl+space+attribute
324
- attributes += attribute
325
- }
326
- let id = ''
327
- if(element.id !== undefined) id = ` id="${element.id}"`
328
- html += `${space}<${element.tagName}${id}${attributes}>${nl}`
329
- if(element.children !== undefined)
330
- if(element.children.length >0) html = this.build(filePath,element.children,html,extraSpace)
331
- if(element.tagName !== undefined && !Document.singlTags.includes(element.tagName))
332
- html += `${space}</${element.tagName}>${nl}`
333
- }
334
- });
335
- if(array == this.domTree) {
336
- html = this.doctype+nl+html
337
- if(filePath !== undefined) Document.writeFile(filePath,html)
338
- }
339
- return html
340
- }
341
-
342
- getChildNodes(openIndex,closeIndex,content) {
343
- for(let i = openIndex+1; i<closeIndex; i++) {
344
- let node = this.domList[i]
345
- if(!this.added.includes(i)) {
346
- if(!node.close && node.text == undefined) this.buildTag(node.index,content.children,content)
347
- else if(!node.close && node.text !== undefined) {
348
- content.innerText += node.text
349
- content.children.push(node);
350
- delete node.index
351
- }
352
- this.added.push(i)
353
- }
354
- }
355
- }
356
-
357
- findOpen(key,array=this.domList) {
358
- return array.findIndex(function(obj, index) {
359
- if(obj[key] !== undefined && obj[key].close == false && obj[key].closeTag == undefined)
360
- return true;
361
- });
362
- }
363
- // querySelector
364
- $(selector,obj) {return this.$$(selector,obj,true)}
365
-
366
- $$(selector='',obj=this.domTree[0],first=false) {
367
- if(typeof selector !== 'string') return
368
- this.selector = selector
369
- this.selectors = []
370
- this.getSelectors(selector)
371
- let array = this.findElements(obj)
372
- if(first) return array[0]
373
- else return this.buildCollection(array)
374
- }
375
-
376
- getSelectors() {
377
- this.getPropSelctors() // remove all attributes from selector, build them, replace as [i] and put into selectors.attributes[]
378
- let groups = this.selector.split(',')
379
- groups.forEach(group => {
380
- let result = {}
381
- group = group.trim()
382
- let family = group.split('>')
383
- let prevBrother = group.split('+')
384
- let nextBrother = group.split('~')
385
- if(family.length > 1) {
386
- result.parent = this.buildSingleSelector(family[0])
387
- result.element = this.buildSingleSelector(family[1])
388
- } else if(prevBrother.length > 1) {
389
- result.prev = this.buildSingleSelector(prevBrother[0])
390
- result.element = this.buildSingleSelector(prevBrother[1])
391
- } else if(nextBrother.length > 1) {
392
- result.next = this.buildSingleSelector(nextBrother[0])
393
- result.element = this.buildSingleSelector(nextBrother[1])
394
- } else result.element = this.buildSingleSelector(group)
395
- this.selectors.push(result)
396
- });
397
- }
398
-
399
- getPropSelctors() {
400
- let signs = ['~','|','^','$','*']
401
- let props = this.selector.match(/\[.*\]/g)
402
- this.attributes = []
403
- if(props !== null) props.forEach((prop,i) => {
404
- this.selector = this.selector.replace(prop,`[${i}]`)
405
- let attribute = prop.replace('[','').replace(']','')
406
- let array = attribute.split('=')
407
- let propName = array[0]
408
- let propValue = (array[1] == undefined) ? '' : array[1].replace(/"/g,'')
409
- let sign
410
- if(signs.includes(propName.slice(-1))) {
411
- sign = propName.slice(-1)
412
- propName = propName.slice(0, -1)
413
- }
414
- this.attributes.push({propName,propValue,sign})
415
- });
416
- }
417
-
418
- buildSingleSelector(selector,result={}) {
419
- selector = selector.trim()
420
- let props = selector.match(/\[.*\]/g)
421
- if(props !== null) props.forEach(prop => {
422
- selector = selector.replace(prop,'')
423
- let index = prop.replace('[','').replace(']','')
424
- result.attributes = this.attributes[index]
425
- });
426
- let id = selector.match(/#(\w*-?)*/)
427
- if(id !== null) {
428
- result.id = id[0].replace('#','')
429
- selector = selector.replace(id[0],'')
430
- }
431
- let classes = selector.match(/\.(\w*-?)*/g)
432
- if(classes !== null) {
433
- result.classes = []
434
- classes.forEach(clas => {
435
- result.classes.push(clas.replace('.',''))
436
- selector = selector.replace(clas,'')
437
- });
438
- }
439
- let tag = selector.match(/(\w*-?)*/)
440
- if(tag !== null) {
441
- if(tag[0] !== '') {
442
- result.tag = tag[0]
443
- selector = selector.replace(tag[0],'')
444
- }
445
- }
446
- return result
447
- }
448
-
449
- findElements(obj,array = []) {
450
- array = this.checkSelectors(obj,array)
451
- if(obj.children !== undefined) {
452
- obj.children.forEach(element => {
453
- this.findElements(element,array)
454
- });
455
- }
456
- return array
457
- }
458
-
459
- checkSelectors(element,array) {
460
- this.selectors.forEach(group => {
461
- let addIt = 0
462
- if(group.parent !== undefined) addIt += this.checkElement(element.parent,group.parent)
463
- if(group.prev !== undefined) addIt += this.checkElement(element.prev,group.prev)
464
- if(group.next !== undefined) addIt += this.checkElement(element.next,group.next)
465
- if(group.element !== undefined) addIt += this.checkElement(element,group.element)
466
- if(addIt == 0) array.push(element)
467
- });
468
- return array
469
- }
470
-
471
- checkElement(element,selectors,addIt = 0) {
472
- if(element == undefined) return -1
473
- if(selectors.tag !== undefined)
474
- if(element.tagName !== selectors.tag) addIt--
475
- if(selectors.id !== undefined)
476
- if(element.id !== selectors.id) addIt--
477
- if(selectors.classes !== undefined ) {
478
- if(element.classList !== undefined) selectors.classes.forEach(clas => {
479
- if(!element.classList.includes(clas)) addIt--
480
- });
481
- else addIt--
482
- }
483
- if(selectors.attributes !== undefined) addIt += this.checkAttributes(
484
- element.attributes,
485
- selectors.attributes.propName,
486
- selectors.attributes.propValue,
487
- selectors.attributes.sign
488
- )
489
- return addIt
490
- }
491
-
492
- checkAttributes(props,propName,propValue,sign,addIt = 0) {
493
- if(props == undefined) return -1
494
- else {
495
- if(props[propName] == undefined) addIt--
496
- if(propValue == '' && props[propName] == undefined) addIt--
497
- if(propValue.length>0 && props[propName] !== undefined) {
498
- if(sign == undefined) {
499
- if(props[propName] !== propValue) addIt--
500
- } else if(sign == '~') {
501
- let r = new RegExp(`\\b${propValue}`)
502
- if(props[propName].match(r) == null) addIt--
503
- } else if(sign == '|') {
504
- if(!props[propName].startsWith(propValue) || props[propName] !== propValue) addIt--
505
- } else if(sign == '^') {
506
- if(!props[propName].startsWith(propValue)) addIt--
507
- } else if(sign == '$') {
508
- if(!props[propName].endsWith(propValue)) addIt--
509
- } else if(sign == '*') {
510
- if(!props[propName].includes(propValue)) addIt--
511
- }
512
- }
513
- }
514
- return addIt
515
- }
516
-
517
- // Collection array
518
- buildCollection(collection) {
519
- collection.each = function(fn) {return Document.each(this,fn)}
520
- collection.parse = function(part,fn) {return Document.parse(this,part,fn)}
521
- return collection
522
- }
523
-
524
- static each(collection,fn) {
525
- for(let i = 0; i<collection.length; i++) {
526
- fn(collection[i],i,collection)
527
- }
528
- return collection
529
- }
530
-
531
- static parse(collection,part,fn,array=[]) {
532
- if(part !== undefined) {
533
- collection.each((element,index,collection) => {
534
- let content
535
- if(element[part] !== undefined) content = element[part]
536
- else if(element.attributes !== undefined)
537
- if(element.attributes[part] !== undefined)
538
- content = element.attributes[part]
539
-
540
- if(content !== undefined) {
541
- if(typeof content == 'string')
542
- content = content.replace(/&nbsp;/g,' ')
543
- if(fn !== undefined) {
544
- if(fn(content)) array.push(content)
545
- } else array.push(content)
546
- }
547
- })
548
- } else {
549
- collection.forEach(element => {
550
- array.push(Document.removeMethods(element))
551
- });
552
- }
553
- return array
554
- }
555
-
556
- static removeMethods(element) {
557
- element = {...element}
558
- let deleteThis = ['parent','next','prev','classList','json','$','$$','remove','add','add0','add1','add2','add3','getAttribute','setAttribute']
559
- deleteThis.forEach(item => {
560
- delete element[item]
561
- });
562
- if(element.children !== undefined) element.children.forEach((child,i) => {
563
- element.children[i] = Document.removeMethods(child)
564
- });
565
- return element
566
- }
567
-
568
- static writeFile(path,obj,encoding = 'utf-8') {
569
- let {writeFileSync} = require('fs')
570
- path = Document.path(path)
571
- if(typeof obj !== 'string') try {
572
- obj = JSON.stringify(obj,null,4)
573
- writeFileSync(path,obj,encoding)
574
- } catch(e) {console.log(e)}
575
- else writeFileSync(path,obj,encoding)
576
- }
577
-
578
- static readFile(path,encoding = 'utf-8') {
579
- path = Document.path(path)
580
- let {readFileSync} = require('fs')
581
- return readFileSync(path,encoding)
582
- }
583
-
584
- static path(path) {
585
- let {join} = require('path')
586
- if(Array.isArray(path)) return join(...path)
587
- else return path
588
- }
1
+ const alsDocument = (function(){
2
+ class Query{
589
3
  static get(query){
590
4
  let q=new Query(query)
591
5
  return q.selectors
592
6
  }
593
7
  constructor(query){
594
8
  this.query=query
595
9
  this.selectors=[]
596
10
  this.stringValues=[];
597
11
  this.parseSelectors(query.split(','))
598
12
  }
599
13
  parseSelectors(selectors){
600
14
  selectors.forEach(selector=>{
601
15
  let originalSelector=selector.trim()
602
16
  selector=this.removeSpaces(selector)
603
17
  this.stringValues=[]
604
18
  selector=selector.replace(/\[.*?\]/g,(value)=>{
605
19
  this.stringValues.push(value)
606
20
  return `[${this.stringValues.length-1}]`
607
21
  })
608
22
  let [element,ancestors]=this.splitAndCutLast(selector,' ')
609
23
  element=this.getFamily(element)
610
24
  if (ancestors.length>0)
611
25
  element.ancestors=ancestors.map(ancestor=>this.getFamily(ancestor))
612
26
  element.group=originalSelector
613
27
  this.selectors.push(element)
614
28
  });
615
29
  }
616
30
  splitAndCutLast(string,splitBy){
617
31
  const array=string.split(splitBy);
618
32
  const last=array.pop();
619
33
  return [last,array];
620
34
  }
621
35
  getFamily(group,element,prev,prevAny,sign){
622
36
  if (group.match(/\~|\+/)!==null){
623
37
  let [last,prevBrothers]=this.splitAndCutLast(group,/\~|\+/)
624
38
  let signs=group.replace(last,'')
625
39
  prevBrothers.forEach(el=>signs=signs.replace(el,''))
626
40
  signs=signs.match(/\~|\+/g)
627
41
  if (signs.length==1){
628
42
  sign=signs[0]
629
43
  } else if (signs.length>1){
630
44
  sign=signs.splice(signs.length-1,signs.length-1)[0]
631
45
  prevBrothers[0]=prevBrothers.map((b,i)=>{
632
46
  if (i< prevBrothers.length-1) b+=signs[i]
633
47
  return b
634
48
  }).join('')
635
49
  prevBrothers[0]=this.getFamily(prevBrothers[0])
636
50
  }
637
51
  if (sign=='~') prevAny=prevBrothers[0]
638
52
  else if (sign=='+') prev=prevBrothers[0]
639
53
  element=last
640
54
  } else element=group
641
55
  let family
642
56
  if (prev || prevAny){
643
57
  family=this.getParents(element)
644
58
  if (prev) family.prev=this.getParents(prev)
645
59
  if (prevAny) family.prevAny=this.getParents(prevAny)
646
60
  } else family=this.getParents(element)
647
61
  if (family.query!==group) family.group=group
648
62
  return family
649
63
  }
650
64
  getParents(selector){
651
65
  if (typeof selector=='string'){
652
66
  let [element,parents]=this.splitAndCutLast(selector,'>')
653
67
  element=this.buildElement(element)
654
68
  parents=parents.map(parent=>this.buildElement(parent))
655
69
  if (parents.length>0) element.parents=parents
656
70
  return element
657
71
  } else return selector
658
72
  }
659
73
  buildElement(element,id=null,tag=null,classList=[]){
660
74
  let query=element
661
75
  element=element.replace(/\#(\w-?)*/,$id=>{
662
76
  id=$id.replace(/^\#/,''); return ''
663
77
  })
664
78
  element=element.replace(/\.(\w-?)*/,$class=>{
665
79
  classList.push($class.replace(/^\./,'')); return ''
666
80
  })
667
81
  element=element.replace(/(\w\:?-?)*/,$tag=>{
668
82
  tag=$tag=='' ? null : $tag; return ''
669
83
  })
670
84
  let attribs=this.getAttributes(element)
671
85
  element={ query }
672
86
  if (id) element.id=id
673
87
  if (tag) element.tag=tag
674
88
  if (classList.length>0) element.classList=classList
675
89
  if (attribs.length>0) element.attribs=attribs
676
90
  return element
677
91
  }
678
92
  getAttributes(element){
679
93
  let attribs=this.stringValues.filter((value,index)=>{
680
94
  let searchValue=`[${index}]`
681
95
  if (element.match(searchValue)) return true
682
96
  else return false
683
97
  })
684
98
  attribs=attribs.map(attrib=>{
685
99
  let query=attrib
686
100
  attrib=attrib.replace('[','').replace(']','')
687
101
  let [name,value]=attrib.split(/[\~\|\^\$\*]?\=/)
688
102
  let sign=attrib.replace(name,'').replace(value,'')
689
103
  attrib={ query }
690
104
  if (name) attrib.name=name
691
105
  if (value) attrib.value=value.trim().replace(/^\"/,'').replace(/\"$/,'')
692
106
  if (sign){
693
107
  attrib.sign=sign
694
108
  attrib.check=this.getAttribFn(sign).bind(attrib)
695
109
  }
696
110
  return attrib
697
111
  });
698
112
  return attribs
699
113
  }
700
114
  getAttribFn(sign){
701
115
  if (sign=='=') return function (value){ return value===this.value }
702
116
  if (sign=='*=') return function (value){ return value.includes(this.value) }
703
117
  if (sign=='^=') return function (value){ return value.startsWith(this.value) }
704
118
  if (sign=='$=') return function (value){ return value.endsWith(this.value) }
705
119
  if (sign=='|=') return function (value){
706
120
  return value.trim().split(' ').length==1
707
121
  && (value.startsWith(this.value) || value.startsWith(this.value+'-'))
708
122
  ? true : false
709
123
  }
710
124
  if (sign=='~=') return function (value){
711
125
  return this.value.trim().split(' ').length==1 && value.includes(this.value) ? true : false
712
126
  }
713
127
  }
714
128
  removeSpaces(selector){
715
129
  selector=selector.replace(/\s{2}/g,' ')
716
130
  selector=selector.replace(/\s?\^?\$?\|?\~?\*?\=\s*/g,(m)=>m.trim())
717
131
  selector=selector.replace(/\s?(\+|\~|\>)\s?/g,(m)=>m.trim())
718
132
  return selector
719
133
  }
134
+ }
135
+ function checkElement(el,selector){
720
136
  if(selector==undefined) return true
721
137
  if(el==null) return false
722
138
  let{tag,classList,attribs:attributes,id,prev,ancestors,parents,prevAny}=selector
723
139
  if(typeof el==='string') return false
724
140
  if(id!==undefined && el.id===null) return false
725
141
  if(id && id!==el.id) return false
726
142
  if(tag && el.tagName===undefined) return false
727
143
  else if(tag && tag!==el.tagName) return false
728
144
  const clas=el.attributes.class
729
145
  if(classList!==undefined && (clas===undefined || clas==='')) return false
730
146
  else if(classList!==undefined){
731
147
  if(classList.every(e=>el.classList.contains(e))===false) return false
732
148
  }
733
149
  if(checkattributes(attributes,el)===false) return false
734
150
  if(checkElement(el.prev,prev)===false) return false
735
151
  if(checkAncestors(el.ancestors,ancestors)===false) return false
736
152
  if(checkParents(el.ancestors,parents)===false) return false
737
153
  if(el.parent){
738
154
  if(checkPrevAny(el.parent.children,el.childIndex,prevAny)==false) return false
739
155
  }
740
156
  return true
157
+ }
741
158
  function checkattributes(attributes=[],el){
742
159
  let elattributes=el.attributes
743
160
  let names=Object.keys(elattributes)
744
161
  let passedTests=0
745
162
  if(attributes) for(let i=0; i<attributes.length; i++){
746
163
  let{name,value,check}=attributes[i]
747
164
  if(name=='inner' && value!==undefined && check && el.inner){
748
165
  if(check(el.inner)) passedTests++
749
166
  }
750
167
  if(!names.includes(name)) continue
751
168
  else if(value==undefined) passedTests++
752
169
  else if(value && elattributes[name]){
753
170
  if(check(elattributes[name])==false) continue
754
171
  else passedTests++
755
172
  }
756
173
  }
757
174
  if(passedTests==attributes.length) return true
758
175
  else return false
176
+ }
759
177
  function checkPrevAny(children=[],index,prevAny){
760
178
  let size=children.length
761
179
  if((size==0 || index==0) && prevAny) return false
762
180
  for(let i=index; i>=0; i--){
763
181
  if(checkElement(children[i],prevAny)) return true
764
182
  }
765
183
  return false
184
+ }
766
185
  function checkAncestors(ancestors=[],selectorAncestors=[]){
767
186
  let count=0
768
187
  if(selectorAncestors.length==0) return true
769
188
  let endIndex=ancestors.length-1
770
189
  let selectorIndex=selectorAncestors.length-1
771
190
  while(selectorIndex>=0){
772
191
  for(let i=endIndex; i>=0; i--){
773
192
  endIndex=i-1
774
193
  if(checkElement(ancestors[i],selectorAncestors[selectorIndex])==true){
775
194
  count++
776
195
  break
777
196
  }
778
197
  }
779
198
  selectorIndex--
780
199
  }
781
200
  if(count==selectorAncestors.length) return true
782
201
  else return false
202
+ }
783
203
  function checkParents(ancestors=[],selectorParents=[]){
784
204
  if(selectorParents.length===0) return true
785
205
  if(ancestors.length< selectorParents.length) return false
786
206
  let index=ancestors.length-1
787
207
  for(let i=selectorParents.length-1; i>=0; i--){
788
208
  if(checkElement(ancestors[index],selectorParents[i])===false) return false
789
209
  index--
790
210
  }
791
211
  return true
212
+ }
213
+ const getDataName=prop=>'data-'+prop.toLowerCase()
214
+ function getDataset(element){
792
215
  return new Proxy(element.attributes,{
793
216
  get: (target,prop)=>{return target[getDataName(prop)]},set: (target,prop,value)=>{target[getDataName(prop)]=value; return true},deleteProperty: (target,prop)=>{
794
217
  const dataAttr=getDataName(prop)
795
218
  if (dataAttr in target){
796
219
  delete target[dataAttr];
797
220
  return true;
798
221
  }
799
222
  return false;
800
223
  }
801
224
  });
802
225
  }
803
- module.exports = Document
226
+
227
+ function find(selectors,element,collection,first=false,firstTime=true){
804
228
  for(let selector of selectors){
805
229
  if(checkElement(element,selector)) collection.add(element)
806
230
  }
807
231
  if(element.children)
808
232
  element.children.forEach(child=>{
809
233
  if(first && collection.size>0) return
810
234
  find(selectors,child,collection,first,false)
811
235
  })
812
236
  return firstTime ? [...collection] : collection
237
+ }
238
+ class TextNode{
813
239
  constructor(data){
814
240
  this.nodeName='#text';
815
241
  this.parent=null;
816
242
  this.textContent=data;
817
243
  }
818
244
  get nodeValue(){return this.textContent}
819
245
  get parentNode(){return this.parent}
246
+ }
247
+
248
+ function buildStyle(attributes){
820
249
  const styles=attributes.style || "";
821
250
  const camelToKebab=str=>str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g,'$1-$2').toLowerCase();
822
251
  const kebabToCamel=str=>str.replace(/-([a-z])/g,g=>g[1].toUpperCase());
823
252
  const baseStyleObj=styles.split(";").reduce((acc,style)=>{
824
253
  const [key,value]=style.split(":").map(s=>s.trim());
825
254
  if (key && value) acc[kebabToCamel(key)]=value;
826
255
  return acc;
827
256
  },{});
828
257
  return new Proxy(baseStyleObj,{
829
258
  get: (obj,prop)=>obj[camelToKebab(prop)] || obj[prop],set: (obj,prop,value)=>{
830
259
  obj[camelToKebab(prop)]=value;
831
260
  attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
832
261
  return true;
833
262
  },deleteProperty: (obj,prop)=>{
834
263
  delete obj[camelToKebab(prop)];
835
264
  attributes.style=Object.entries(obj).map(([k,v])=>`${camelToKebab(k)}: ${v}`).join("; ");
836
265
  return true;
837
266
  }
838
267
  });
268
+ }
269
+
270
+ class NodeClassList{
839
271
  constructor(node){ this.node=node }
840
272
  get classes(){ return (this.node.attributes.class || "").split(" ").filter(Boolean) }
841
273
  set classes(val){ this.node.attributes.class=val.join(" ") }
842
274
  contains(className){ return this.classes.includes(className) }
843
275
  add(className){
844
276
  const currentClasses=this.classes;
845
277
  if (!currentClasses.includes(className)) this.classes=[...currentClasses,className];
846
278
  }
847
279
  remove(className){ this.classes=this.classes.filter(cls=>cls!==className); }
848
280
  toggle(className){
849
281
  if (this.classes.includes(className)) this.remove(className);
850
282
  else this.add(className);
851
283
  }
852
284
  replace(oldClass,newClass){
853
285
  if (this.classes.includes(oldClass)){
854
286
  this.remove(oldClass);
855
287
  this.add(newClass);
856
288
  }
857
289
  }
290
+ }
291
+ function insertBefore(arr,index,newItem){
858
292
  const existingIndex=arr.indexOf(newItem);
859
293
  if (existingIndex!==-1) arr.splice(existingIndex,1);
860
294
  arr.splice(index,0,newItem);
295
+ }
861
296
  class Node{
862
297
  constructor(tagName,attributes={},parent=null){
863
298
  this.isSingle=false;
864
299
  this.tagName=tagName;
865
300
  this.attributes=attributes;
866
301
  this.childNodes=[];
867
302
  if (parent!==null) parent.childNodes.push(this)
868
303
  this.parent=parent;
869
304
  this._classList=null;
870
305
  this.__style=null;
871
306
  this._dataset=null
872
307
  }
873
308
  get id(){ return this.attributes.id ? this.attributes.id : null; }
874
309
  set id(newValue){ this.attributes.id=newValue; }
875
310
  get className(){return this.attributes.class || null}
876
311
  get parentNode(){ return this.parent }
877
312
  get ancestors(){
878
313
  if(!this.parent) return []
879
314
  const ancestors=[]
880
315
  let element=this.parent
881
316
  while (element.tagName!=='ROOT'){
882
317
  ancestors.push(element)
883
318
  element=element.parent
884
319
  }
885
320
  return ancestors.reverse()
886
321
  }
887
322
  get childNodeIndex(){
888
323
  if(!this.parent) return null
889
324
  return this.parent.childNodes ? this.parent.childNodes.indexOf(this) : null
890
325
  }
891
326
  get childIndex(){
892
327
  if(!this.parent) return null
893
328
  return this.parent.children ? this.parent.children.indexOf(this) : null
894
329
  }
895
330
  get previousElementSibling(){ return this.prev }
896
331
  get prev(){
897
332
  if (!this.childIndex) return null
898
333
  return this.parent.children[this.childIndex-1]
899
334
  }
900
335
  get nextElementSibling(){ return this.next }
901
336
  get next(){
902
337
  if (!this.childIndex) return null
903
338
  return this.parent.children[this.childIndex+1] || null
904
339
  }
905
340
  get dataset(){
906
341
  if (!this._dataset) this._dataset=getDataset(this);
907
342
  return this._dataset;
908
343
  }
909
344
  get classList(){
910
345
  if (!this._classList) this._classList=new NodeClassList(this);
911
346
  return this._classList;
912
347
  }
913
348
  get style(){
914
349
  if (!this.__style) this.__style=buildStyle(this.attributes)
915
350
  return this.__style
916
351
  }
917
352
  get outerHTML(){
918
353
  const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
919
354
  return `<${this.tagName}${attrs ? ' '+attrs : ''}>${this.innerHTML}</${this.tagName}>`;
920
355
  }
921
356
  getAttribute(attrName){ return this.attributes[attrName] || null }
922
357
  setAttribute(attrName,value){ this.attributes[attrName]=value }
923
358
  removeAttribute(attrName){ delete this.attributes[attrName] }
924
359
  remove(){
925
360
  if (!this.parent) return
926
361
  const index=this.childNodeIndex;
927
362
  if (index!==null) this.parent.childNodes.splice(index,1);
928
363
  }
929
364
  get innerHTML(){
930
365
  return this.childNodes.map(child=>{
931
366
  if (child instanceof Node || child instanceof SingleNode) return child.outerHTML;
932
367
  else if (child instanceof TextNode) return child.textContent;
933
368
  else return child
934
369
  }).join("");
935
370
  }
936
371
  $$(query){return this.querySelectorAll(query)}
937
372
  querySelectorAll(query){
938
373
  const selectors=Query.get(query)
939
374
  return find(selectors,this,new Set())
940
375
  }
941
376
  $(query){return this.querySelector(query)}
942
377
  querySelector(query){
943
378
  const selectors=Query.get(query)
944
379
  return find(selectors,this,new Set(),true)[0] || null
945
380
  }
946
381
  getElementsByClassName(query){ return this.querySelectorAll('.'+query) }
947
382
  getElementsByTagName(query){ return this.querySelectorAll(query) }
948
383
  getElementById(query){ return this.querySelector('#'+query) }
949
384
  get children(){
950
385
  return this.childNodes.filter(child=>{
951
386
  if (!(child instanceof Node)) return false
952
387
  if (child.tagName==='#comment') return false
953
388
  return true
954
389
  });
955
390
  }
956
391
  insertAdjacentElement(position,newElement){
957
392
  if(newElement.tagName==='ROOT' && newElement.childNodes.length>0) newElement=newElement.childNodes[0]
958
393
  const pos=position.toLowerCase();
959
394
  if (pos==="afterbegin") this.childNodes.unshift(newElement);
960
395
  else if (pos==="beforeend") this.childNodes.push(newElement);
961
396
  newElement.parent=this
962
397
  if (!this.parent) return newElement
963
398
  if (pos==="beforebegin") insertBefore(this.parent.childNodes,this.childNodeIndex,newElement)
964
399
  else if (pos==="afterend") this.parent.childNodes.splice(this.childNodeIndex+1,0,newElement);
965
400
  newElement.parent=this.parent
966
401
  return newElement
967
402
  }
968
403
  insertAdjacentHTML(position,html){
969
404
  const newNode=parseHTML(html);
970
405
  newNode.childNodes.reverse().forEach(node=>{
971
406
  this.insertAdjacentElement(position,node);
972
407
  });
973
408
  return newNode
974
409
  }
975
410
  insertAdjacentText(position,text){
976
411
  return this.insertAdjacentElement(position,new TextNode(text));
977
412
  }
978
413
  insert(position,element){
979
414
  const positions=['beforebegin','afterbegin','beforeend','afterend']
980
415
  if(positions[position]) position=positions[position]
981
416
  if(typeof element==='string'){
982
417
  element=element.trim()
983
418
  if(element.startsWith('<') && element.endsWith('>')){
984
419
  return this.insertAdjacentHTML(position,element)
985
420
  }
986
421
  return this.insertAdjacentText(position,element)
987
422
  }
988
423
  return this.insertAdjacentElement(position,element)
989
424
  }
990
425
  set innerHTML(html){
991
426
  const parsed=parseHTML(html);
992
427
  this.childNodes=parsed.childNodes;
993
428
  }
994
429
  set outerHTML(html){
995
430
  const parsed=parseHTML(html);
996
431
  if (!this.parent) return console.log('element has no parent node')
997
432
  const index=this.childIndex
998
433
  if (index!==null) this.parent.childNodes.splice(index,1,...parsed.childNodes);
999
434
  }
1000
435
  appendChild(newChild){
1001
436
  if (newChild instanceof Node || newChild instanceof TextNode || newChild instanceof SingleNode){
1002
437
  if (newChild.parent) newChild.parent.childNodes=newChild.parent.childNodes.filter(child=>child!==newChild);
1003
438
  } else if(typeof newChild==='string') newChild=new TextNode(newChild)
1004
439
  else return newChild
1005
440
  this.childNodes.push(newChild);
1006
441
  newChild.parent=this;
1007
442
  return newChild;
1008
443
  }
1009
444
  get textContent(){
1010
445
  if (this.childNodes.length===0) return this.nodeName==='#text' ? this.nodeValue : '';
1011
446
  return this.childNodes.map(child=>{
1012
447
  if(child instanceof SingleNode) return ''
1013
448
  if(child instanceof TextNode) return child.nodeValue
1014
449
  if(child instanceof Node) return child.textContent;
1015
450
  else return child;
1016
451
  }).join(" ");
1017
452
  }
1018
453
  set textContent(value){
1019
454
  this.childNodes=[];
1020
455
  if (value!==null && value!==undefined){
1021
456
  this.childNodes.push(value.toString());
1022
457
  }
1023
458
  }
459
+ }
460
+ class SingleNode extends Node{
1024
461
  constructor(tagName,attributes={},parent=null){
1025
462
  if(attributes['?'] && tagName==='?xml') delete attributes['?']
1026
463
  super(tagName,attributes,parent);
1027
464
  this.isSingle=true
1028
465
  }
1029
466
  get outerHTML(){
1030
467
  if (this.tagName==="#cdata-section") return `<![CDATA[${this.nodeValue}]]>`;
1031
468
  const attrs=Object.entries(this.attributes).map(([key,val])=>val.length ? `${key}="${val}"` : key).join(" ");
1032
469
  return `<${this.tagName} ${attrs}${this.tagName==='?xml' ? '?' : ''}>`;
1033
470
  }
1034
471
  get innerHTML(){ return ""; }
1035
472
  set innerHTML(_){ }
1036
473
  $(_){return null}
1037
474
  $$(_){return []}
1038
475
  querySelectorAll(_){ return []; }
1039
476
  querySelector(_){ return null; }
1040
477
  getElementsByClassName(_){ return []; }
1041
478
  getElementsByTagName(_){ return []; }
1042
479
  getElementById(_){ return null; }
1043
480
  get children(){ return []; }
1044
481
  insertAdjacentElement(_,__){ }
1045
482
  insertAdjacentHTML(_,__){ }
1046
483
  insertAdjacentText(_,__){ }
1047
484
  appendChild(_){ }
1048
485
  insert(_,__){ }
1049
486
  get textContent(){ return ""; }
1050
487
  set textContent(_){ }
488
+ }
489
+
490
+ class Root extends Node{
1051
491
  constructor(){
1052
492
  super('ROOT',{},null);
1053
493
  this.isSingle=false
1054
494
  }
1055
495
  get body(){return this.$('body')}
1056
496
  get head(){return this.$('head')}
1057
497
  get title(){return this.$('title')}
1058
498
  set title(title){return this.$('title').innerHTML=title}
499
+ }
500
+ function parseAttributes(str){
1059
501
  const attrs={};
1060
502
  let key="";
1061
503
  let value="";
1062
504
  let isKey=true;
1063
505
  let quoteChar=null;
1064
506
  for (let i=0; i< str.length; i++){
1065
507
  const char=str[i];
1066
508
  if (isKey && (char==='=' || char===' ')){
1067
509
  if (char==='=') isKey=false;
1068
510
  else if (key.trim()){
1069
511
  attrs[key.trim()]=true;
1070
512
  key="";
1071
513
  }
1072
514
  continue;
1073
515
  }
1074
516
  if (!quoteChar && (char==='"' || char==="'")){
1075
517
  quoteChar=char;
1076
518
  continue;
1077
519
  } else if (quoteChar && char===quoteChar){
1078
520
  quoteChar=null;
1079
521
  attrs[key.trim()]=value.trim();
1080
522
  key=""; value=""; isKey=true;
1081
523
  continue;
1082
524
  }
1083
525
  if (isKey) key+=char;
1084
526
  else value+=char;
1085
527
  }
1086
528
  if (key.trim() &&!value) attrs[key.trim()]='';
1087
529
  return attrs;
530
+ }
531
+ const VOID_TAGS=new Set(["area","base","br","col","command","embed","hr","img","input","keygen","link","meta","param","source","track","wbr","!doctype",'?xml']);
532
+ function parseHTML(html){
1088
533
  const root=new Root();
1089
534
  const stack=[root];
1090
535
  let currentText="",i=0;
1091
536
  let max=0
1092
537
  function parseScript(){
1093
538
  if (!html.startsWith("<script",i)) return false;
1094
539
  const openTagEnd=html.indexOf(">",i);
1095
540
  if (openTagEnd===-1) return false;
1096
541
  const attributesString=html.substring(i+7,openTagEnd).trim();
1097
542
  const attributes=parseAttributes(attributesString);
1098
543
  let closeTagStart=html.indexOf("</script>",openTagEnd);
1099
544
  if (closeTagStart===-1) return false;
1100
545
  const content=html.substring(openTagEnd+1,closeTagStart);
1101
546
  const scriptNode=new Node('script',attributes,stack[stack.length-1]);
1102
547
  if(content.length>0) scriptNode.childNodes.push(content);
1103
548
  i=closeTagStart+9;
1104
549
  return true;
1105
550
  }
1106
551
  function parseSpecial(startStr,endStr,n1,n2,tag){
1107
552
  if (!html.startsWith(startStr,i)) return false
1108
553
  const end=html.indexOf(endStr,i+n1);
1109
554
  const strNode=new Node(tag,{},stack[stack.length-1]);
1110
555
  strNode.childNodes.push(html.substring(i+n1,end));
1111
556
  i=end+n2;
1112
557
  return true
1113
558
  }
1114
559
  while (i< html.length){
1115
560
  if(i>=max) max=i;
1116
561
  else break;
1117
562
  if (parseScript()) continue
1118
563
  if (parseSpecial("<!--","-->",4,3,'#comment')) continue
1119
564
  if (parseSpecial("<style","</style>",7,8,'style')) continue
1120
565
  if (html.startsWith("<![CDATA[",i)){
1121
566
  const end=html.indexOf("]]>",i+9);
1122
567
  if (end===-1) break;
1123
568
  const content=html.substring(i+9,end);
1124
569
  const cdataNode=new SingleNode("#cdata-section",{},stack[stack.length-1]);
1125
570
  cdataNode.nodeValue=content;
1126
571
  i=end+3;
1127
572
  continue;
1128
573
  }
1129
574
  if (html.startsWith("<",i)){
1130
575
  if (currentText && stack[stack.length-1]){
1131
576
  const textNode=new TextNode(currentText)
1132
577
  stack[stack.length-1].childNodes.push(textNode);
1133
578
  textNode.parent=stack[stack.length-1]
1134
579
  currentText="";
1135
580
  }
1136
581
  let tagEnd=i+1;
1137
582
  let insideQuotes=false;
1138
583
  let quoteChar=null;
1139
584
  while (tagEnd< html.length){
1140
585
  const char=html[tagEnd];
1141
586
  if (!insideQuotes && (char==='"' || char==="'")){
1142
587
  insideQuotes=true;
1143
588
  quoteChar=char;
1144
589
  } else if (insideQuotes && char===quoteChar){
1145
590
  insideQuotes=false;
1146
591
  quoteChar=null;
1147
592
  }
1148
593
  if (!insideQuotes && char==='>') break;
1149
594
  tagEnd++;
1150
595
  }
1151
596
  const tagContent=html.substring(i+1,tagEnd);
1152
597
  if (tagContent.startsWith("/")) stack.pop();
1153
598
  else{
1154
599
  let isSelfClosing=tagContent.endsWith('/');
1155
600
  const tagNameEnd=tagContent.search(/\s|>|\//);
1156
601
  const tagName=tagContent.substring(0,tagNameEnd>0 ? tagNameEnd : tagEnd-i-1);
1157
602
  const attributesString=tagContent.substring(tagName.length,isSelfClosing ? tagContent.length-1 : tagContent.length).trim();
1158
603
  const attributes=parseAttributes(attributesString);
1159
604
  if (VOID_TAGS.has(tagName.toLowerCase()) || isSelfClosing) new SingleNode(tagName,attributes,stack[stack.length-1])
1160
605
  else stack.push(new Node(tagName,attributes,stack[stack.length-1]));
1161
606
  }
1162
607
  i=tagEnd+1;
1163
608
  } else{
1164
609
  currentText+=html[i];
1165
610
  i++;
1166
611
  }
1167
612
  }
1168
613
  if (currentText.trim() && stack[stack.length-1]) stack[stack.length-1].childNodes.push(new TextNode(currentText));
1169
614
  return root;
615
+ }
616
+ function buildFromCache(cached){
1170
617
  function buildNode(cache,parent=null){
1171
618
  if(typeof cache==='string') return parent.childNodes.push(cache)
1172
619
  const{isSingle,tagName,attributes,childNodes,textContent}=cache
1173
620
  if(textContent) return parent.childNodes.push(new TextNode(textContent))
1174
621
  if(isSingle) return parent.childNodes.push(new SingleNode(tagName,attributes))
1175
622
  const newDoc=tagName==='ROOT' ? new Root() : new Node(tagName,attributes,parent)
1176
623
  childNodes.forEach(childNode=>{
1177
624
  buildNode(childNode,newDoc)
1178
625
  });
1179
626
  return newDoc
1180
627
  }
1181
628
  return buildNode(cached)
629
+ }
1182
630
 
631
+ function cacheDoc(doc){
1183
632
  const props=['isSingle','tagName','attributes']
1184
633
  function addToCache(element,cache={}){
1185
634
  if(typeof element==='string') return element
1186
635
  if(element.nodeName==='#text') return{textContent:element.textContent}
1187
636
  props.forEach(prop=>{
1188
637
  if(element[prop]) cache[prop]=element[prop]
1189
638
  });
1190
639
  if(!element.childNodes) return cache
1191
640
  cache.childNodes=[]
1192
641
  element.childNodes.forEach(childNode=>{
1193
642
  cache.childNodes.push(addToCache(childNode))
1194
643
  });
1195
644
  return cache
1196
645
  }
1197
646
  return addToCache(doc)
647
+ }
648
+ return { parseHTML, Node, Query, TextNode, SingleNode, buildFromCache, cacheDoc, Root }
649
+ })()