als-document 0.1.1 → 0.5.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 CHANGED
@@ -1,585 +1,562 @@
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
- }
1
+ class Query {
2
+ static get(query) {
3
+ let q = new Query(query)
4
+ return q.selectors
31
5
  }
32
6
 
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)
7
+ constructor(query) {
8
+ this.query = query
9
+ this.selectors = []
10
+ this.getQueries(query.split(','))
11
+ }
12
+
13
+ getQueries(selectors) {
14
+ selectors.forEach(selector => {
15
+ let originalSelector = selector
16
+ selector = this.removeSpaces(selector)
17
+ this.stringValues = []
18
+ selector = selector.replace(/\[.*?\]/g,(value) => {
19
+ this.stringValues.push(value)
20
+ return `[${this.stringValues.length-1}]`
21
+ })
22
+ let [element,ancestors] = this.splitAndCutLast(selector,' ') // \s - ancestor
23
+ element = this.getFamily(element)
24
+ if(ancestors.length > 0)
25
+ element.ancestors = ancestors.map(ancestor => this.getFamily(ancestor))
26
+ element.group = originalSelector
27
+ this.selectors.push(element)
39
28
  });
40
29
  }
41
30
 
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
- });
31
+ splitAndCutLast(string,splitBy) {
32
+ let array = string.split(splitBy)
33
+ let last
34
+ if(array.length == 1) {
35
+ last = array[0]
36
+ array = []
37
+ } else last = array.splice(array.length-1,array.length-1)[0]
38
+ return [last,array]
53
39
  }
54
40
 
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)
41
+ getFamily(group,element,prev,prevAny,sign) {
42
+ if(group.match(/\~|\+/) !== null) {
43
+ let [last,prevBrothers] = this.splitAndCutLast(group,/\~|\+/)
44
+ let signs = group.replace(last,'')
45
+ prevBrothers.forEach(el => signs = signs.replace(el,''))
46
+ signs = signs.match(/\~|\+/g)
47
+ if(signs.length == 1) {
48
+ sign = signs[0]
49
+ } else if(signs.length > 1){
50
+ sign = signs.splice(signs.length-1,signs.length-1)[0]
51
+ prevBrothers[0] = prevBrothers.map((b,i) => {
52
+ if(i<prevBrothers.length-1) b += signs[i]
53
+ return b
54
+ }).join('')
55
+ prevBrothers[0] = this.getFamily(prevBrothers[0])
64
56
  }
65
- });
57
+ if(sign == '~') prevAny = prevBrothers[0] // ~ - any prev brother
58
+ else if(sign == '+') prev = prevBrothers[0] // + - prev brother
59
+ element = last
60
+ } else element = group
61
+ let family
62
+ if(prev || prevAny) {
63
+ family = this.getParents(element)
64
+ if(prev) family.prev = this.getParents(prev)
65
+ if(prevAny) family.prevAny = this.getParents(prevAny)
66
+ } else family = this.getParents(element)
67
+ if(family.query !== group) family.group = group
68
+ return family
66
69
  }
67
70
 
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 scriptName = `{script${i}}`
77
- let newScript = script.replace(scriptContent,scriptName)
78
- this.events[scriptName] = scriptContent
79
- this.htmlText = this.htmlText.replace(script,newScript)
80
- }
81
- });
82
- });
71
+ getParents(selector) {
72
+ if(typeof selector == 'string') {
73
+ let [element,parents] = this.splitAndCutLast(selector,'>')
74
+ element = this.buildElement(element)
75
+ parents = parents.map(parent => this.buildElement(parent))
76
+ if(parents.length > 0) element.parents = parents
77
+ return element
78
+ } else return selector
83
79
  }
84
80
 
85
- addScripts() {
86
- this.domList.forEach((element,i) => {
87
- if(element.tagName == 'script' && !element.close) {
88
- let text = this.domList[i+1].text
89
- if(this.scripts[text] !== undefined) {
90
- this.domList[i+1].text = this.scripts[text]
91
- delete this.scripts[text]
92
- }
93
- }
94
- });
95
- }
96
81
 
97
- addEvents() {
98
- this.domList.forEach((element,i) => {
99
- if(element.attributes !== undefined) {
100
- for(let attName in element.attributes) {
101
- let attValue = element.attributes[attName]
102
- if(this.events[attValue] !== undefined) {
103
- this.domList[i].attributes[attName] = this.events[attValue]
104
- delete this.events[attValue]
105
- }
106
- }
107
- }
108
- });
82
+ buildElement(element,id=null,tag=null,classList=[]) {
83
+ let query = element
84
+ element = element.replace(/\#(\w-?)*/,$id => {
85
+ id = $id.replace(/^\#/,''); return ''
86
+ })
87
+ element = element.replace(/\.(\w-?)*/,$class => {
88
+ classList.push($class.replace(/^\./,'')); return ''
89
+ })
90
+ element = element.replace(/(\w-?)*/,$tag => {
91
+ tag = $tag == '' ? null : $tag; return ''
92
+ })
93
+ let attribs = this.getAttributes(element)
94
+ element = {query}
95
+ if(id) element.id = id
96
+ if(tag) element.tag = tag
97
+ if(classList.length > 0) element.classList = classList
98
+ if(attribs.length > 0) element.attribs = attribs
99
+ return element
109
100
  }
110
101
 
111
- parseTags() {
112
- let outerHTML = this.htmlText
113
- let outerHtmlForCut = outerHTML
114
- let tags = outerHTML.match(/\<(\w*-?)*?(.*?)?([\S\s]*?)\>/gm)
115
- let index = 0
116
- if(tags !== null) tags.forEach((tag,i) => {
117
- let node = outerHtmlForCut.split(tag)[0]
118
- if(node.trim() !== '') {
119
- this.domList.push({text:node,index})
120
- index++
121
- }
122
- let obj = this.buildElementObj(tag)
123
- let objName = obj.tagName+obj.order
124
- obj.index = index
125
- if(obj.order.length == 0) { // single tag
126
- this.domList.push(obj)
127
- } else this.domList.push({[objName]:obj,name:objName,index})
128
- if(obj.close) {
129
- let j = this.findOpen(objName)
130
- if(j >=0) {
131
- let closeTag = index
132
- let openTagObj = this.domList[j][this.domList[j].name]
133
- this.domList[j] = {...openTagObj,closeTag}
134
- }
135
- this.domList[obj.index] = obj
102
+ getAttributes(element) {
103
+ let attribs = this.stringValues.filter((value,index) => {
104
+ let searchValue = `[${index}]`
105
+ if(element.match(searchValue)) return true
106
+ else return false
107
+ })
108
+ attribs = attribs.map(attrib => {
109
+ let query = attrib
110
+ attrib = attrib.replace('[','').replace(']','')
111
+ let [name,value] = attrib.split(/[\~\|\^\$\*]?\=/)
112
+ let sign = attrib.replace(name,'').replace(value,'')
113
+ attrib = {query}
114
+ if(name) attrib.name = name
115
+ if(value) attrib.value = value.trim().replace(/^\"/,'').replace(/\"$/,'')
116
+ if(sign) {
117
+ attrib.sign = sign
118
+ attrib.check = this.getAttribFn(sign).bind(attrib)
136
119
  }
137
- outerHtmlForCut = outerHtmlForCut.replace(node+tag,'')
138
- index++
120
+ return attrib
139
121
  });
122
+ return attribs
140
123
  }
141
- // Build element
142
- buildElementObj(string,obj={}) {
143
- let singlTags = Document.singlTags
144
- if(string.match(/\<\/(\w*-?)*/) !== null) obj.close = true
145
- else obj.close = false
146
- obj.tagName = string.match(/\<\/?(\w*-?)*/,'gm')[0].replace(/\<\/?/,'')
147
- if(!singlTags.includes(obj.tagName)) {
148
- obj.tagName = obj.tagName.replace('/','')
149
- let tagName = obj.tagName
150
- this.getType(tagName,obj)
151
- obj.order = this.types[tagName].count
152
- } else obj.order = ''
153
- obj.attributes = this.getAttributes(string)
154
- obj = this.buildClass(obj)
155
- if(obj.attributes.id !== undefined) {
156
- obj.id = obj.attributes.id
157
- delete obj.attributes.id
124
+
125
+ getAttribFn(sign) {
126
+ if(sign == '=') return function(value) {return value == this.value ? true : false}
127
+ if(sign == '*=') return function(value) {return value.includes(this.value) ? true : false}
128
+ if(sign == '^=') return function(value) {return value.startsWith(this.value) ? true : false}
129
+ if(sign == '$=') return function(value) {return value.endsWith(this.value) ? true : false}
130
+ if(sign == '|=') return function(value) {
131
+ return value.trim().split(' ').length == 1
132
+ && (value.startsWith(this.value) || value.startsWith(this.value+'-'))
133
+ ? true : false
134
+ }
135
+ if(sign == '~=') return function(value) { // includes whole word only
136
+ return this.value.trim().split(' ').length == 1 && value.includes(this.value) ? true : false
158
137
  }
159
- return obj
160
138
  }
161
139
 
162
- getType(tagName,obj) {
163
- if(this.types[tagName] == undefined)
164
- this.types[tagName] = {count:0,lastOperation:1}
165
- else if(obj.close && this.types[tagName].lastOperation == 1)
166
- this.types[tagName].lastOperation = 0
167
- else if(obj.close && this.types[tagName].lastOperation == 0)
168
- this.types[tagName].count--
169
- else if(!obj.close && this.types[tagName].lastOperation == 0)
170
- this.types[tagName].lastOperation = 1
171
- else if(!obj.close && this.types[tagName].lastOperation == 1)
172
- this.types[tagName].count++
173
- }
174
-
175
- getAttributes(string,attributes = {}) {
176
- let matches = string.match(/\s(\/?\w*\-?)*((\S)*?\s?=[\S\s]*?\"[\S\s]*?\")?/g)
177
- if(matches !== null) matches.forEach(attribute => {
178
- attribute = attribute.trim()
179
- if(attribute !== '') {
180
- let attName = attribute.split('=')[0]
181
- let attValue = attribute.replace(attName,'').replace('=','').replace(/\"/g,'')
182
- if(attName !== '/') attributes[attName] = attValue.trim()
183
- }
184
- })
185
- return attributes
140
+ removeSpaces(selector) {
141
+ selector = selector.replace(/\s{2}/g,' ') // remove double spaces
142
+ selector = selector.replace(/\s?\^?\$?\|?\~?\*?\=\s*/g,(m) => m.trim()) // remove spaces inside []
143
+ selector = selector.replace(/\s?(\+|\~|\>)\s?/g,(m) => m.trim()) // remove spaces around +|~|>
144
+ return selector
186
145
  }
187
-
188
- buildClass(obj) {
189
- if(obj.attributes.class !== undefined) {
190
- obj.classList = obj.attributes.class.split(' ')
191
- } else obj.classList = []
192
- obj.classList.add = newClass => {
193
- obj.classList.push(newClass)
194
- obj.attributes.class += ' '+newClass
195
- return obj
196
- }
197
- obj.classList.remove = clas => {
198
- let index = obj.classList.indexOf(clas)
199
- if(index >=0) {
200
- obj.attributes.class = obj.attributes.class.replace(obj.classList[index],'')
201
- obj.classList.splice(index,1)
202
- }
203
- return obj
146
+ }
147
+
148
+ class HtmlParser {
149
+ static parse(html) {
150
+ let result = new HtmlParser(html)
151
+ return result.root
152
+ }
153
+ constructor(html='') {
154
+ if(this.checkHtml(html)) {
155
+ this.indexes = []
156
+ this.events = []
157
+ this.html = this.htmlString = html
158
+ this.removeScripts()
159
+ this.removeStyles()
160
+ this.removeEventStrings()
161
+ this.htmlString = this.htmlString.replace(/\<\!\-\-([\S\s]*?)\-\-\>/gm,'') // remove all comments
162
+ this.root = this.parse()
204
163
  }
205
- return obj
206
164
  }
207
- // Build dom tree
208
- getPairs(start=0,end=this.domList.length,domTree=this.domTree) {
209
- for(let index=start; index<end; index++) {
210
- let node = this.domList[index]
211
- if(node.closeTag !== undefined && !this.added.includes(index)) {
212
- if(this.firstTag == undefined) this.firstTag = index
213
- this.buildTag(index,domTree)
214
- break;
215
- }
216
- }
165
+
166
+ checkHtml(html,isGood=false) {
167
+ if(html == '') console.log('html parameter is empty')
168
+ else if(typeof html !== 'string') console.log(`html parameter has to be string. Recieved ${typeof html}`)
169
+ else isGood = true
170
+ return isGood
217
171
  }
218
172
 
219
- buildTag(openIndex,domTree,parent=this.domTree) {
220
- this.added.push(openIndex)
221
- let content = this.domList[openIndex]
222
- content.children = []
223
- content.innerText = ''
224
- this.getChildNodes(openIndex,content.closeTag,content)
225
- content.$ = (selector) => this.$(selector,content)
226
- content.$$ = (selector) => this.$$(selector,content)
227
- content.parent = parent
228
- content = Document.buildMethods(content)
229
- this.buildOperations(content)
230
- if(content.innerText !== '') {
231
- if(parent.innerText == undefined) parent.innerText = ''
232
- parent.innerText += '|'+content.innerText
233
- }
234
- content.json = () => Document.removeMethods(content)
235
- delete content.closeTag
236
- delete content.close
237
- delete content.index
238
- delete content.order
239
- domTree.push(content)
240
- }
241
-
242
- static buildMethods(content) {
243
- content.children.forEach((child,index) => { // prev and next
244
- if(index == 0) child.prev = null
245
- else child.prev = content.children[index-1]
246
- if(index == content.children.length-1) child.next = null
247
- else child.next = content.children[index+1]
173
+ removeScripts(scripts=[]) {
174
+ let $scripts = this.htmlString.match(/\<script(.*?)\>[\S\s]*?\<\/script\>/gm)
175
+ if($scripts !== null) $scripts.forEach((script,i) => {
176
+ let inner = script.replace(/^\<script(.*?)\>/,'').replace(/\<\/script\>$/,'')
177
+ scripts.push(inner)
178
+ this.htmlString = this.htmlString.replace(inner,`{{{{script ${scripts.length-1}`)
179
+ });
180
+ this.scripts = scripts
181
+ }
182
+ removeStyles(styles=[]) {
183
+ let $styles = this.htmlString.match(/\<style\>[\S\s]*?\<\/style\>/gm)
184
+ if($styles !== null) $styles.forEach((style,i) => {
185
+ let inner = style.replace(/^\<style\>/,'').replace(/\<\/style\>$/,'')
186
+ styles.push(inner)
187
+ this.htmlString = this.htmlString.replace(inner,`{{{{style ${styles.length-1}`)
248
188
  });
249
- return content
189
+ this.styles = styles
250
190
  }
251
191
 
252
- static changeByIndex(array,value,item,before = true) {
253
- let index = array.indexOf(value);
254
- if(index > -1) {
255
- if(item == undefined) array.splice(index, 1); // remove value
256
- else if(item !== undefined) {
257
- if(before) array.splice(index, 0, item); // add item before value
258
- else array.splice(index+1, 0, item); // add item after value
259
- }
192
+ removeEventStrings() {
193
+ let eventsWithHtml = this.htmlString.match(/on\w*\s*?\=\s*?["|'|`](.*?(<[^>]*>)(\'|\`).*?)["|'|`]/g)
194
+ if(eventsWithHtml !== null) {
195
+ eventsWithHtml.forEach(event => {
196
+ let array = event.split('=')
197
+ let value = array.filter((v,i) => i>0 && i!=='').join('=')
198
+ value = value.replace(/^\"/,'').replace(/\"$/,'')
199
+ this.events.push(value)
200
+ let newEvent = event.replace(value,`{{{{event ${this.events.length-1}`)
201
+ this.htmlString = this.htmlString.replace(event,newEvent)
202
+ })
260
203
  }
261
204
  }
262
205
 
263
- static buildInnerText(element) {
264
- element.innerText = ''
265
- if(element.children !== undefined) element.children.forEach(child => {
266
- if(child.text !== undefined && child.text !== '') element.innerText += child.text
267
- if(child.innerText !== undefined && child.innerText !== '') element.innerText += '|'+child.innerText
206
+ parse(htmlString=this.htmlString) {
207
+ // Parse tags
208
+ let elements = htmlString.match(/<[^>]*>/g)
209
+ elements.forEach((tag,index) => {
210
+ elements[index] = this.parseElement(tag)
211
+ htmlString = htmlString.replace(tag,`<tag${index}>`)
268
212
  });
213
+ // Parse inner text
214
+ let inners = htmlString.match(/tag[\s\S]*?\</g)
215
+ for (let i = inners.length-1; i >=0 ; i--) {
216
+ let inner = inners[i];
217
+ let tagIndex = inner.match(/(\d*)\>/)[1]
218
+ inner = inner.replace(/tag.*\>/,'').slice(0, -1).trim()
219
+ if(inner.length > 0) {
220
+ elements.splice(parseInt(tagIndex)+1, 0, inner);
221
+ }
222
+ }
223
+ this.elements = elements
224
+ let root = this.getPairs()
225
+ return root
226
+ }
227
+
228
+ lookForPair(element,startIndex,parent,level) {
229
+ element.parent = parent
230
+ element.children = []
231
+ let {tag} = element
232
+ let count = 0
233
+ let endIndex
234
+ for (let index = startIndex+1; index < this.elements.length; index++) {
235
+ const el = this.elements[index];
236
+ if(el.tag == tag) {
237
+ if(el.status == 'close') {
238
+ if(count == 0) {
239
+ endIndex = index
240
+ el.level = level
241
+ break
242
+ } else count--
243
+ } else if(el.status == 'open') count++
244
+ }
245
+ }
246
+ if(!endIndex) endIndex = startIndex+1
247
+ element.endIndex = endIndex
248
+ let child = this.getPairs(element,startIndex+1,endIndex,level+1)
249
+ return child
269
250
  }
270
251
 
271
- buildOperations(content) {
272
- content.remove = function() {
273
- Document.changeByIndex(content.parent.children,content)
274
- Document.buildInnerText(content.parent)
252
+ getPairs(parent={type:'root',children:[]},startIndex = 0,endIndex=this.elements.length,level=0,childIndex=0) {
253
+ for(let index = startIndex; index < endIndex; index++) {
254
+ if(this.indexes.includes(index)) continue
255
+ const element = this.elements[index];
256
+ let child
257
+ if(typeof element == 'string') child = element
258
+ else if(element.type == 'tag') {
259
+ if(element.status == 'single') {
260
+ child = element
261
+ child.parent = parent
262
+ }
263
+ else if(element.status == 'open') {
264
+ child = this.lookForPair(element,index,parent,level)
265
+ }
266
+ }
267
+ this.addChild(parent,child,index,level,childIndex/2)
268
+ childIndex++
275
269
  }
276
- content.add = function(element,place) {
277
- if(typeof element == 'string') element = Document.newElement(element)
278
- else element.remove()
279
- if(place == 1) content.children.unshift(element)
280
- if(place == 2) content.children.push(element)
281
- if(place == 1 || place == 2) build(content)
282
-
283
- if(place == 0) Document.changeByIndex(content.parent.children,content,element,true)
284
- if(place == 3) Document.changeByIndex(content.parent.children,content,element,false)
285
- if(place == 0 || place == 3) build(content.parent)
286
-
287
- function build(parent) {
288
- element.parent = parent
289
- element = Document.buildMethods(parent)
290
- Document.buildInnerText(parent)
270
+ return parent
271
+ }
272
+
273
+ addScriptsAndStyles(parent,child,index) {
274
+ if(parent.tag == 'script') {
275
+ let scriptIndex = child.match(/(?<=\{\{\{\{script\s)(\d*)?/)
276
+ if(scriptIndex !== null) {
277
+ let i = parseInt(scriptIndex[0])
278
+ if(typeof i == 'number') child = this.scripts[i]
291
279
  }
292
- return element
293
280
  }
294
- content.add0 = element => content.add(element,0)
295
- content.add1 = element => content.add(element,1)
296
- content.add2 = element => content.add(element,2)
297
- content.add3 = element => content.add(element,3)
298
- }
299
-
300
- static newElement(outerHtml) {
301
- let document = new Document(outerHtml)
302
- return document.domTree[0].json()
303
- }
304
-
305
- build(filePath,array=this.domTree,html = '',space='') {
306
- let extraSpace = space+' '
307
- let nl = `
308
- `
309
- array.forEach(element => {
310
- if(element.text !== undefined) html += space+element.text+nl
311
- else if(element.tagName == 'comment' ) {
312
- html += space+element.comment + nl
313
- } else {
314
- let attributes = ''
315
- for(let propName in element.attributes) {
316
- let value = element.attributes[propName]
317
- let attribute = ` ${propName}="${value}"`
318
- if(attribute.length + attributes.length > 80) attribute = nl+space+attribute
319
- attributes += attribute
320
- }
321
- let id = ''
322
- if(element.id !== undefined) id = ` id="${element.id}"`
323
- html += `${space}<${element.tagName}${id}${attributes}>${nl}`
324
- if(element.children !== undefined)
325
- if(element.children.length >0) html = this.build(filePath,element.children,html,extraSpace)
326
- if(element.tagName !== undefined && !Document.singlTags.includes(element.tagName))
327
- html += `${space}</${element.tagName}>${nl}`
281
+ if(parent.tag == 'style') {
282
+ let stylesIndex = child.match(/(?<=\{\{\{\{style\s)(\d*)?/)
283
+ if(stylesIndex !== null) {
284
+ let i = parseInt(stylesIndex[0])
285
+ if(typeof i == 'number') child = this.styles[i]
328
286
  }
329
- });
330
- if(array == this.domTree) {
331
- html = this.doctype+nl+html
332
- if(filePath !== undefined) Document.writeFile(filePath,html)
287
+ }
288
+ this.elements[index] = child
289
+ return child
290
+ }
291
+
292
+ addChild(parent,child,index,level,childIndex) {
293
+ if(typeof child == 'string') {
294
+ child = this.addScriptsAndStyles(parent,child,index)
295
+ child = {type:'text',text:child}
296
+ this.elements[index] = child
333
297
  }
334
- return html
335
- }
336
-
337
- getChildNodes(openIndex,closeIndex,content) {
338
- for(let i = openIndex+1; i<closeIndex; i++) {
339
- let node = this.domList[i]
340
- if(!this.added.includes(i)) {
341
- if(!node.close && node.text == undefined) this.buildTag(node.index,content.children,content)
342
- else if(!node.close && node.text !== undefined) {
343
- content.innerText += node.text
344
- content.children.push(node);
345
- delete node.index
346
- }
347
- this.added.push(i)
298
+ if(child) {
299
+ delete child.status
300
+ this.getElements(child)
301
+ this.innerHTML(child)
302
+ this.outerHTML(child)
303
+ this.innerText(child)
304
+ this.getAncestors(child)
305
+ this.getAttribute(child)
306
+ child.childIndex = childIndex
307
+ child.index = index
308
+ child.level = level
309
+ child.prev = null
310
+ child.next = null
311
+ if(parent.children.length > 0) {
312
+ let prevI = parent.children.length-1
313
+ parent.children[prevI].next = child
314
+ child.prev = parent.children[prevI]
348
315
  }
316
+ parent.children.push(child)
349
317
  }
318
+ this.indexes.push(index)
350
319
  }
351
-
352
- findOpen(key,array=this.domList) {
353
- return array.findIndex(function(obj, index) {
354
- if(obj[key] !== undefined && obj[key].close == false && obj[key].closeTag == undefined)
355
- return true;
356
- });
357
- }
358
- // querySelector
359
- $(selector,obj) {return this.$$(selector,obj,true)}
360
320
 
361
- $$(selector='',obj=this.domTree[0],first=false) {
362
- if(typeof selector !== 'string') return
363
- this.selector = selector
364
- this.selectors = []
365
- this.getSelectors(selector)
366
- let array = this.findElements(obj)
367
- if(first) return array[0]
368
- else return this.buildCollection(array)
369
- }
370
-
371
- getSelectors() {
372
- this.getPropSelctors() // remove all attributes from selector, build them, replace as [i] and put into selectors.attributes[]
373
- let groups = this.selector.split(',')
374
- groups.forEach(group => {
375
- let result = {}
376
- group = group.trim()
377
- let family = group.split('>')
378
- let prevBrother = group.split('+')
379
- let nextBrother = group.split('~')
380
- if(family.length > 1) {
381
- result.parent = this.buildSingleSelector(family[0])
382
- result.element = this.buildSingleSelector(family[1])
383
- } else if(prevBrother.length > 1) {
384
- result.prev = this.buildSingleSelector(prevBrother[0])
385
- result.element = this.buildSingleSelector(prevBrother[1])
386
- } else if(nextBrother.length > 1) {
387
- result.next = this.buildSingleSelector(nextBrother[0])
388
- result.element = this.buildSingleSelector(nextBrother[1])
389
- } else result.element = this.buildSingleSelector(group)
390
- this.selectors.push(result)
391
- });
321
+ getAttribute(element) {
322
+ element.getAttribute = function(name) {
323
+ let array = Object.keys(this.attribs).filter(key => key == name)
324
+ return array.length > 0 ? this.attribs[array[0]] : null
325
+ }
392
326
  }
393
327
 
394
- getPropSelctors() {
395
- let signs = ['~','|','^','$','*']
396
- let props = this.selector.match(/\[.*\]/g)
397
- this.attributes = []
398
- if(props !== null) props.forEach((prop,i) => {
399
- this.selector = this.selector.replace(prop,`[${i}]`)
400
- let attribute = prop.replace('[','').replace(']','')
401
- let array = attribute.split('=')
402
- let propName = array[0]
403
- let propValue = (array[1] == undefined) ? '' : array[1].replace(/"/g,'')
404
- let sign
405
- if(signs.includes(propName.slice(-1))) {
406
- sign = propName.slice(-1)
407
- propName = propName.slice(0, -1)
328
+ getAncestors(element) {
329
+ Object.defineProperty(element, 'ancestors', { get() {
330
+ let ancestors = []
331
+ let parent = this.parent
332
+ if(parent) while(parent.parent) {
333
+ ancestors.unshift(parent)
334
+ parent = parent.parent
408
335
  }
409
- this.attributes.push({propName,propValue,sign})
410
- });
336
+ return ancestors
337
+ }});
411
338
  }
412
339
 
413
- buildSingleSelector(selector,result={}) {
414
- selector = selector.trim()
415
- let props = selector.match(/\[.*\]/g)
416
- if(props !== null) props.forEach(prop => {
417
- selector = selector.replace(prop,'')
418
- let index = prop.replace('[','').replace(']','')
419
- result.attributes = this.attributes[index]
420
- });
421
- let id = selector.match(/#(\w*-?)*/)
422
- if(id !== null) {
423
- result.id = id[0].replace('#','')
424
- selector = selector.replace(id[0],'')
425
- }
426
- let classes = selector.match(/\.(\w*-?)*/g)
427
- if(classes !== null) {
428
- result.classes = []
429
- classes.forEach(clas => {
430
- result.classes.push(clas.replace('.',''))
431
- selector = selector.replace(clas,'')
432
- });
433
- }
434
- let tag = selector.match(/(\w*-?)*/)
435
- if(tag !== null) {
436
- if(tag[0] !== '') {
437
- result.tag = tag[0]
438
- selector = selector.replace(tag[0],'')
340
+ getElements(element,elements=this.elements) {
341
+ Object.defineProperty(element, 'elements', { get() {
342
+ return elements.slice(this.index,this.endIndex+1)
343
+ }});
344
+ }
345
+
346
+ outerHTML(element) {
347
+ Object.defineProperty(element, 'outerHTML', { get() {
348
+ let {elements} = this
349
+ return elements[0].text + this.innerHTML + elements[elements.length-1].text
350
+ }});
351
+ }
352
+
353
+ innerText(element) {
354
+ Object.defineProperty(element, 'innerText', { get() {
355
+ if(element.children)
356
+ return element.children.map(child => child.type == 'text' ? child.text : '').join('')
357
+ else return ''
358
+ }})
359
+ }
360
+
361
+ innerHTML(element) {
362
+ Object.defineProperty(element, 'innerHTML', { get() {
363
+ let tab = ' ',result = '',firstLevel,space=''
364
+ let {elements} = this
365
+ let endIndex = elements.length
366
+ for (let i = 1; i < endIndex-1; i++) {
367
+ let element = elements[i]
368
+ let {level,tag,text} = element
369
+ if(firstLevel == undefined) firstLevel = level
370
+ if(i == endIndex-1) space = ''
371
+ else if(level) space = Array.from(Array(level-firstLevel).keys()).map(n => tab).join('')
372
+ result += space + text
373
+ if(endIndex > 3) result += '\n'
439
374
  }
440
- }
441
- return result
375
+ return result
376
+ }});
442
377
  }
443
378
 
444
- findElements(obj,array = []) {
445
- array = this.checkSelectors(obj,array)
446
- if(obj.children !== undefined) {
447
- obj.children.forEach(element => {
448
- this.findElements(element,array)
449
- });
450
- }
451
- return array
379
+ parseElement(tagString) {
380
+ let text = tagString
381
+ let type = 'tag'
382
+ if(tagString == '<!DOCTYPE html>') return {tag:'!DOCTYPE html',status:'single',attribs:{},type}
383
+ let status = 'close'
384
+ let tag = tagString.match(/(?<=\<\/)(\w*\-?)*/)
385
+ if(tag == null) {
386
+ tag = tagString.match(/(?<=\<)(\w*\-?)*/)
387
+ if(tag) {
388
+ tag = tag[0]
389
+ if(this.singleTags.includes(tag)) status='single'
390
+ else status = 'open'
391
+ }
392
+ } else tag = tag[0]
393
+ let {classList,attribs,style,id} = this.parseAttributes(tagString)
394
+ let obj = {tag,status,attribs,type,classList,text,style,id}
395
+ return obj
452
396
  }
397
+ singleTags = ['comment','area','base','br','col','command','embed','hr','img','input','keygen','link','meta','param','source','track','wbr']
398
+
399
+ parseAttributes(tagString,classList=[],attribs={},style={},id=null) {
400
+ let attributes = tagString.match(/(?<=\s)(\w*\-?)*(\s*?\=\s*?\"[\s\S]*?\")?/g)
401
+ if(attributes) attributes.forEach(attribString => {
402
+ let [name,value] = attribString.split('=')
403
+ if(value !== undefined && name !== '') {
404
+ value = value.trim().replace(/^\"/,'').replace(/\"$/m,'')
405
+
406
+ let eventIndex = value.match(/(?<=\{\{\{\{event\s)(\d*)?/)
407
+ if(eventIndex !== null) value = this.events[eventIndex[0]]
453
408
 
454
- checkSelectors(element,array) {
455
- this.selectors.forEach(group => {
456
- let addIt = 0
457
- if(group.parent !== undefined) addIt += this.checkElement(element.parent,group.parent)
458
- if(group.prev !== undefined) addIt += this.checkElement(element.prev,group.prev)
459
- if(group.next !== undefined) addIt += this.checkElement(element.next,group.next)
460
- if(group.element !== undefined) addIt += this.checkElement(element,group.element)
461
- if(addIt == 0) array.push(element)
409
+ if(name == 'class') classList = value.split(/\s\s?\s?/)
410
+ else if(name == 'style') style = this.parseInlineCss(value)
411
+ else if(name == 'id') id = value
412
+ attribs[name] = value
413
+ } else if(name !== '') attribs[name] = undefined
462
414
  });
463
- return array
464
- }
465
-
466
- checkElement(element,selectors,addIt = 0) {
467
- if(element == undefined) return -1
468
- if(selectors.tag !== undefined)
469
- if(element.tagName !== selectors.tag) addIt--
470
- if(selectors.id !== undefined)
471
- if(element.id !== selectors.id) addIt--
472
- if(selectors.classes !== undefined ) {
473
- if(element.classList !== undefined) selectors.classes.forEach(clas => {
474
- if(!element.classList.includes(clas)) addIt--
475
- });
476
- else addIt--
477
- }
478
- if(selectors.attributes !== undefined) addIt += this.checkAttributes(
479
- element.attributes,
480
- selectors.attributes.propName,
481
- selectors.attributes.propValue,
482
- selectors.attributes.sign
483
- )
484
- return addIt
485
- }
486
-
487
- checkAttributes(props,propName,propValue,sign,addIt = 0) {
488
- if(props == undefined) return -1
489
- else {
490
- if(props[propName] == undefined) addIt--
491
- if(propValue == '' && props[propName] == undefined) addIt--
492
- if(propValue.length>0 && props[propName] !== undefined) {
493
- if(sign == undefined) {
494
- if(props[propName] !== propValue) addIt--
495
- } else if(sign == '~') {
496
- let r = new RegExp(`\\b${propValue}`)
497
- if(props[propName].match(r) == null) addIt--
498
- } else if(sign == '|') {
499
- if(!props[propName].startsWith(propValue) || props[propName] !== propValue) addIt--
500
- } else if(sign == '^') {
501
- if(!props[propName].startsWith(propValue)) addIt--
502
- } else if(sign == '$') {
503
- if(!props[propName].endsWith(propValue)) addIt--
504
- } else if(sign == '*') {
505
- if(!props[propName].includes(propValue)) addIt--
415
+ return {classList,attribs,style,id}
416
+ }
417
+
418
+ parseInlineCss(textCss) {
419
+ let rules = textCss.split(';')
420
+ let styles = {}
421
+ rules.forEach(rule => {
422
+ let [prop,value] = rule.trim().split(':')
423
+ if(rule !== '') {
424
+ if(prop.match(/\w*\-\w*(-\w*)?/) !== null) {
425
+ let words = prop.split('-')
426
+ prop = words.map((w,i) => i==0 ? w : w[0].toUpperCase() + w.slice(1)).join('')
506
427
  }
428
+ styles[prop] = value.trim()
507
429
  }
508
- }
509
- return addIt
430
+ });
431
+ return styles
510
432
  }
433
+ }
511
434
 
512
- // Collection array
513
- buildCollection(collection) {
514
- collection.each = function(fn) {return Document.each(this,fn)}
515
- collection.parse = function(part,fn) {return Document.parse(this,part,fn)}
516
- return collection
435
+
436
+ class HtmlSelector {
437
+ constructor(html) {
438
+ if(typeof html == 'string') {
439
+ html = new HtmlParser(html)
440
+ this.html = html
441
+ this.elements = html.elements
442
+ this.makeSelectable()
443
+ } else console.log('Parameter is not string')
444
+ }
445
+
446
+ makeSelectable() {
447
+ this.elements.forEach(element => {
448
+ if(element.type == 'tag' && element.status !== 'close') {
449
+ element.$$ = (query) => this.$$(query,element.index,element.endIndex)
450
+ element.$ = (query) => this.$(query,element.index,element.endIndex)
451
+ }
452
+ })
517
453
  }
518
454
 
519
- static each(collection,fn) {
520
- for(let i = 0; i<collection.length; i++) {
521
- fn(collection[i],i,collection)
455
+ $(query,start=0,end=this.elements.length) {
456
+ return this.$$(query,start,end,true)
457
+ }
458
+
459
+ $$(query,start=0,end=this.elements.length,single=false) {
460
+ let result = []
461
+ this.selectors = new Query(query).selectors
462
+ this.query = query
463
+ this.selectors.forEach(selector => {
464
+ for(let i=start; i<end; i++) {
465
+ let el = this.elements[i]
466
+ if(this.checkElement(el,selector) && !result.includes(el)) result.push(el)
467
+ if(single && result.length == 1) break
468
+ }
469
+ });
470
+ if(single && result.length == 1) return result[0]
471
+ else if(single && result.length == 0) return null
472
+ else return result
473
+ }
474
+
475
+ checkElement(el,selector) {
476
+ if(selector == undefined) return true
477
+ if(el == null) return false
478
+ let {tag,classList,attribs,id,prev,ancestors,parents,prevAny} = selector
479
+
480
+ if(el.status == 'close' || el.type == 'text') return false
481
+ if(id !== undefined && el.id == undefined) return false
482
+ if(id && id !== el.id) return false
483
+ if(tag && el.tag == undefined) return false
484
+ else if(tag && tag !== el.tag) return false
485
+ if(classList !== undefined && el.classList == undefined) return false
486
+ else if(classList !== undefined && Array.isArray(el.classList)) {
487
+ if(classList.every(e => el.classList.includes(e)) == false) return false
522
488
  }
523
- return collection
524
- }
525
-
526
- static parse(collection,part,fn,array=[]) {
527
- if(part !== undefined) {
528
- collection.each((element,index,collection) => {
529
- let content
530
- if(element[part] !== undefined) content = element[part]
531
- else if(element.attributes !== undefined)
532
- if(element.attributes[part] !== undefined)
533
- content = element.attributes[part]
534
-
535
- if(content !== undefined) {
536
- if(typeof content == 'string')
537
- content = content.replace(/&nbsp;/g,' ')
538
- if(fn !== undefined) {
539
- if(fn(content)) array.push(content)
540
- } else array.push(content)
541
- }
542
- })
543
- } else {
544
- collection.forEach(element => {
545
- array.push(Document.removeMethods(element))
546
- });
489
+ if(this.checkAttribs(attribs,el) == false) return false
490
+ if(this.checkElement(el.prev,prev) == false) return false
491
+ if(this.checkAncestors(el.ancestors,ancestors) == false) return false
492
+ if(this.checkParents(el.ancestors,parents) == false) return false
493
+ if(el.parent) {
494
+ if(this.prevAny(el.parent.children,el.childIndex,prevAny) == false) return false
547
495
  }
548
- return array
496
+ return true
549
497
  }
550
498
 
551
- static removeMethods(element) {
552
- element = {...element}
553
- let deleteThis = ['parent','next','prev','classList','json','$','$$','remove','add','add0','add1','add2','add3']
554
- deleteThis.forEach(item => {
555
- delete element[item]
556
- });
557
- if(element.children !== undefined) element.children.forEach((child,i) => {
558
- element.children[i] = Document.removeMethods(child)
559
- });
560
- return element
499
+ prevAny(children=[],index,prevAny) {
500
+ let size = children.length
501
+ if((size == 0 || index == 0) && prevAny) return false
502
+ for(let i=index; i>=0; i--) {
503
+ if(this.checkElement(children[i],prevAny)) return true
504
+ }
505
+ return false
561
506
  }
562
507
 
563
- static writeFile(path,obj,encoding = 'utf-8') {
564
- let {writeFileSync} = require('fs')
565
- path = Document.path(path)
566
- if(typeof obj !== 'string') try {
567
- obj = JSON.stringify(obj,null,4)
568
- writeFileSync(path,obj,encoding)
569
- } catch(e) {console.log(e)}
570
- else writeFileSync(path,obj,encoding)
508
+ checkAncestors(ancestors=[],selectorAncestors=[]) {
509
+ let count = 0
510
+ if(selectorAncestors.length == 0) return true
511
+ let endIndex = ancestors.length-1
512
+ let selectorIndex = selectorAncestors.length-1
513
+ while(selectorIndex>=0) {
514
+ for(let i=endIndex; i>=0; i--) {
515
+ endIndex=i-1
516
+ if(this.checkElement(ancestors[i],selectorAncestors[selectorIndex]) == true) {
517
+ count++
518
+ break
519
+ }
520
+ }
521
+ selectorIndex--
522
+ }
523
+ if(count == selectorAncestors.length) return true
524
+ else return false
571
525
  }
572
526
 
573
- static readFile(path,encoding = 'utf-8') {
574
- path = Document.path(path)
575
- let {readFileSync} = require('fs')
576
- return readFileSync(path,encoding)
527
+ checkParents(ancestors=[],selectorParents=[]) {
528
+ if(selectorParents.length == 0) return true
529
+ if(ancestors.length < selectorParents.length) return false
530
+ let index = ancestors.length-1
531
+ for(let i=selectorParents.length-1; i>=0; i--) {
532
+ if(this.checkElement(ancestors[index],selectorParents[i]) == false) return false
533
+ index--
534
+ }
535
+ return true
577
536
  }
578
537
 
579
- static path(path) {
580
- let {join} = require('path')
581
- if(Array.isArray(path)) return join(...path)
582
- else return path
538
+ checkAttribs(attribs=[],el) {
539
+ let elAttribs = el.attribs
540
+ let names = Object.keys(elAttribs)
541
+ let passedTests = 0
542
+ if(attribs) for(let i=0; i<attribs.length; i++) {
543
+ let {name,value,check} = attribs[i]
544
+ if(name == 'inner' && value !== undefined && check && el.innerText) {
545
+ if(check(el.innerText)) passedTests++
546
+ }
547
+
548
+ if(!names.includes(name)) continue
549
+ else if(value == undefined) passedTests++
550
+ else if(value && elAttribs[name]) {
551
+ if(check(elAttribs[name]) == false) continue
552
+ else passedTests++
553
+ }
554
+ }
555
+ if(passedTests == attribs.length) return true
556
+ else return false
583
557
  }
584
558
  }
585
- module.exports = Document
559
+
560
+
561
+
562
+ try {module.exports = {HtmlSelector,Query,HtmlParser}} catch{}