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/readme.md CHANGED
@@ -1,200 +1,352 @@
1
- # Als-Document
1
+ # Als-document
2
2
 
3
- *If something wrong or not working properly, please write me to: sh.mashkanta@gmail.com*
3
+ Builded from scatch and tested.
4
+ All tested with als-test.
4
5
 
6
+ Als-document is a library which includes 3 instrements:
7
+ * Html parser - for fast html string parsing and building dom tree
8
+ * Html query parser - for destructing css/html query to selectors inside array of objects
9
+ * Html selector - for selecting elements with css/html query inside parsed dom tree
5
10
 
6
- Document is a class which gets html as string and return new object with DOM tree.
7
- You can add or remove elements from DOM tree and modify each element.
8
- You can select elements and collections and read and modify them with given instruments.
11
+ - [Als-document](#als-document)
12
+ - [Selector](#selector)
13
+ - [Basics](#basics)
14
+ - [FrontEnd usage](#frontend-usage)
15
+ - [BackEnd usage](#backend-usage)
16
+ - [Extra abilities](#extra-abilities)
17
+ - [HtmlParser](#htmlparser)
18
+ - [Syntax](#syntax)
19
+ - [Frontend example](#frontend-example)
20
+ - [Backend example](#backend-example)
21
+ - [Query](#query)
22
+ - [Syntax](#syntax-1)
23
+ - [Example](#example)
24
+ - [Attribs and check function](#attribs-and-check-function)
9
25
 
10
- **update**
11
- * querySelector for attributes fixed
12
26
 
27
+ ## Selector
13
28
 
14
- ## Write and Read files
15
- Document have 2 static methods to read and write files.
16
- The syntax:
29
+ Selector is a class which uses HtmlParser for parsing html and Query for parsing html/css query.
30
+ Everything does Query class support, can be selected (so far available all, except pseudos).
31
+ Selector can be used on frontend and on backend.
32
+
33
+
34
+ ### Basics
35
+
36
+ **Syntax**
17
37
  ```javascript
18
- Document.writeFile(filePath,obj,encoding = 'utf-8')
19
- Document.readFile(filePath,encoding = 'utf-8')
38
+ let doc = new HtmlSelector(htmlString:string):instanceof HtmlSelector
39
+ doc.$(query:string):object | null
40
+ doc.$$(query:string):array
41
+ doc.$(query:string).$(query:qeury).$$(query:qeury)
20
42
  ```
21
43
 
22
- * ``filepath`` - filepath can be string (absolute path to file) or array for joining.
23
- * ``obj`` - obj can be string or object for stringify.
24
- * ``encoding`` - encoding for read or write file
44
+ ``query`` - html/css query for selecting
45
+ ``$`` - method return first element or null
46
+ ``$$`` - return collection or empty array
47
+ Each element, has ``$`` and ``$$`` methods for selecting descendants
48
+
25
49
 
26
- Example:
50
+ **Example**
27
51
  ```javascript
28
- let {Document} = require('als-document')
29
- let html = Document.readFile([__dirname,'index.html'])
52
+ let doc = new HtmlSelector(htmlString)
53
+ let body = doc.$('body') // querySelector('body'):element|null
54
+ let divs = body.$$('div') // body.querySelectorAll('div'):array
55
+ console.log(divs)
30
56
  ```
31
57
 
32
58
 
33
- ## Creating new object
59
+ #### FrontEnd usage
60
+ ```html
61
+ <script src="/node_modules/als-document/document.js"></script>
62
+ <script>
63
+ let htmlText = `
64
+ <div>
65
+ <span>Another one</span>
66
+ <div>Some text</div>
67
+ </div>
68
+ `
69
+ let doc = new HtmlSelector(htmlText)
70
+ let span = doc.$('div>span')
71
+ </script>
72
+ ```
34
73
 
35
- Document constructor get single string parameter - the outerHTML for converting to virtual DOM tree.
36
74
 
75
+ #### BackEnd usage
37
76
  ```javascript
38
- let document = new Document(html) // html has to be string
39
- document.domTree // includes virtual DOM tree as array of elements
77
+ let {HtmlSelector} = require('als-document')
78
+ let htmlText = `
79
+ <div>
80
+ <span>Another one</span>
81
+ <div>Some text</div>
82
+ </div>
83
+ `
84
+ let doc = new HtmlSelector(htmlText)
85
+ let span = doc.$('div>span')
40
86
  ```
41
87
 
42
88
 
43
- ## QuerySelector for single element
44
- Then document object has created, you can select elements or collections.
45
- For selecting single element, use ``$(selector)`` and for selecting collections ``$$(selector)``.
46
-
47
- **Selecting element**
89
+ ### Extra abilities
90
+ Usualy, browser selector can select by tag name,class, attribute or id.
91
+ HtmlSelector, has extra selecting options, since id,style,class,events and innerHTML - can be selected as attribute.
48
92
 
93
+ For example you can do those things:
49
94
  ```javascript
50
- document.$('div') // select first div in document
51
- document.$('div.some') // select first div element with some class
95
+ doc.$('[style*="display"]') //style includes "display"
96
+ doc.$('[onclick*="console.log"]') //event includes "console"
97
+ doc.$('[class*="btn"][class*="danger"]') //class includes "btn" and "danger"
98
+ doc.$('[id^="tab"]') //id starts with "tab-"
99
+ doc.$('[inner$="00"]') //innerText ends with ".00"
52
100
  ```
101
+ ## HtmlParser
53
102
 
54
- At this time, selector supports this:
55
- * Selects all elements - ``*``
56
- * element - ``div``
57
- * class - ``.some-class``
58
- * id - ``#some-id``
59
- * parent - ``div > p``
60
- * next - ``div + p``
61
- * previous - ``p ~ ul``
62
- * attribute - ``[some-attribute="some value"]``
63
- * ``[prop]``
64
- * ``[prop~=value]``
65
- * ``[prop|=value]``
66
- * ``[prop^="value"]``
67
- * ``[prop$="value"]``
68
- * ``[prop*="value"]``
103
+ HtmlParser is a class which build dom tree from html string.
69
104
 
105
+ * HtmlParser removes all html comments and they not included in dom tree.
106
+ * Contrary to regular dom, attribute includes class,id and style as attribute in addition to classList, id and style(as array).
70
107
 
71
- The folowing, **won't work**: ``div p``.
72
108
 
109
+ ### Syntax
73
110
 
74
- Each returned element, has the folowing:
75
111
  ```javascript
76
- {
77
- parent, // parent element
78
- prev, // previous element (null if no exists)
79
- next, // next element (null if no exists)
80
- innerText, // innner text of element and it's childNodes separated by |
81
- children, // array of childNodes(elements and text nodes) - includes text element too
82
- tagName, // tag name of element
83
- id, // id of element if exists (not included in attributes)
84
- attributes, // object of attributes (id not included)
85
- classList, // array of classes and add and remove methods
86
- $(selector),
87
- $$(selector),
88
- json(), // remove all methods and circular objects from object
89
- remove(), // remove this element
90
- add(element/outerHtml,place),
91
- add0(element/outerHtml),
92
- add1(element/outerHtml),
93
- add2(element/outerHtml),
94
- add3(element/outerHtml),
95
- }
112
+ let parsed = new HtmlParser(htmlString:string):instanceof HtmlParser
113
+ parsed.root : circular object
114
+
115
+ // static method
116
+ HtmlParser.parse(html):object // parsed.root
96
117
  ```
97
118
 
98
- Text node has the folowing:
119
+ Each element, except root and text elements has:
120
+ * attribs - element's attributes
121
+ * parent - parent element
122
+ * next - next element or null
123
+ * prev - previous element or null
124
+ * children - array of children include text nodes
125
+ * type - tag or text or root
126
+ * classList - array with classes
127
+ * index - start index of element inside elements list
128
+ * id - element's id or null
129
+ * endIndex - end index of element inside elements list
130
+ * level - level in dom tree
131
+ * text - parsed text for tag and for text element
132
+ * innerText:getter - concats all children's text together | ''
133
+ * innerHTML:getter - return innerHTML for element
134
+ * outerHTML:getter - return outerHTML for element
135
+ * ancestors:getter - return array of ancestors
136
+ * getAttribute(name) - return value of attribute or null
137
+ * style:[] - array of styles with camelCase property name
138
+
139
+ Example for parsed.document
140
+
99
141
  ```javascript
100
142
  {
101
- text,
102
- prev,
103
- next
143
+ type:'root',
144
+ children:[
145
+ {
146
+ attribs: {},
147
+ index: 0,
148
+ prev:null,
149
+ next:{...}
150
+ tag: "!DOCTYPE html",
151
+ type: "tag",
152
+ ...
153
+ },
154
+ {
155
+ attribs: {lang:'en'},
156
+ prev:{...},
157
+ next:null,
158
+ classList:[],
159
+ children:[
160
+ {
161
+ attribs: {},
162
+ children:[...]
163
+ prev:null,
164
+ next:{...}
165
+ classList:[],
166
+ index: 2,
167
+ tag: "head",
168
+ parent:{tag:'html',...} // reference to parent
169
+ type: "tag",
170
+ ...
171
+ },
172
+ {
173
+ attribs: {},
174
+ children:[...]
175
+ index: 10,
176
+ prev:{...},
177
+ next:null,
178
+ classList:[],
179
+ tag: "body",
180
+ parent:{tag:'html',...} // reference to parent
181
+ type: "tag",
182
+ ...
183
+ },
184
+ ]
185
+ index: 1,
186
+ tag: "html",
187
+ parent:{type:'root',...} // reference to parent
188
+ type: "tag",
189
+ ...
190
+ }
191
+ ]
104
192
  }
105
193
  ```
106
194
 
107
- Comment node:
108
- ```javascript
109
- tagName:comment,
110
- comment // comment it self
111
- ```
112
195
 
113
- You can add or remove classes with classList methods.
114
- Example:
115
- ```javascript
116
- let element = document.$('div')
117
- element.classList.remove('some')
118
- element.classList.add('another')
119
- element.classList.add('onemore')
120
- ```
196
+ ### Frontend example
121
197
 
122
- Also you can change element's id:
123
- ```javascript
124
- let element = document.$('div')
125
- element.id = 'new-id'
126
- ```
198
+ ```html
199
+ <script src="/node_modules/als-document/parser/parser.js"></script>
200
+ <script>
201
+ let result = new HtmlParser(htmlString)
202
+ console.log(result.root)
127
203
 
128
- ## Element methods
204
+ // Or with static method
205
+ console.log(HtmlParser.parse(html))
129
206
 
130
- ```javascript
131
- json() // remove all methods and circular objects from object
132
- remove() // remove this element
133
- add(element/outerHtml,place) // adding AdjacentHTML or AdjacentElement to place(0-3)
134
- add0(element/outerHtml) // adding AdjacentHTML or AdjacentElement beforebegin
135
- add1(element/outerHtml) // adding AdjacentHTML or AdjacentElement afterbegin
136
- add2(element/outerHtml) // adding AdjacentHTML or AdjacentElement beforeend
137
- add3(element/outerHtml) // adding AdjacentHTML or AdjacentElement afterend
207
+ </script>
138
208
  ```
139
209
 
140
- Example:
210
+ ### Backend example
211
+
141
212
  ```javascript
142
- let document = new Document(html)
143
- let a = document.$('a')
144
- let div = document.$('div')
145
- div.add2('<div id="test">Hello world</div>')
146
- div.add3(a)
147
- a.remove()
213
+ const {HtmlParser} = require('als-htmlparser')
214
+ let result = new HtmlParser(htmlString)
215
+ console.log(result.root)
216
+
217
+ // Or with static method
218
+ console.log(HtmlParser.parse(html))
148
219
  ```
149
220
 
150
221
 
151
- Create new element with ``Document.newElement(outerHtml)``
222
+ ## Query
223
+ Query is a class for parsing conditions inside html query. Query not supporting pseudo selectors so far.
224
+ You can use Query on frontend and on backend.
152
225
 
153
- ```javascript
154
- Document.newElement('<div id="test">Hello world</div>')
226
+ Query can be used on fronten and on backend.
227
+
228
+ Frontend:
229
+ ```html
230
+ <script src="/node_modules/als-document/query/query.js"></script>
155
231
  ```
156
232
 
233
+ Backend:
234
+ ```javascript
235
+ let {Query} = require('als-document')
236
+ ```
157
237
 
158
- ## QuerySelector for Collection ``$$()``
159
- To select few elements, use ``$$(selector)`` method.
238
+ ### Syntax
160
239
 
161
240
  ```javascript
162
- document.$$('div') // return collection of all div elements
241
+ let queryObj = new Query(qeury:string): instanceof Query
242
+ let selectors = queryObj.selectors:string
243
+ // or
244
+ let selectors = Query.get(q1:string):string
163
245
  ```
164
246
 
165
- The collection is array which has the elements and two methods: ``each`` and ``parse``.
247
+ ``query`` - html/css query
166
248
 
167
- ``each`` method gets callback function with 3 parameters: element it self, index of the element in collection and collection itself.
168
249
 
169
- Here example:
250
+ ### Example
251
+
170
252
  ```javascript
171
- let array = []
172
- document.$$('div').each((element,index,collection) => {
173
- if(element.innerText.includes('some text'))
174
- array.push(element)
175
- })
253
+ let q1 = 'html>body>div.tabs~.some[type $= "radio and some"]>p+div>.some-id .tab-content~input[disabled] div.some'
254
+ let result = new Query(q1).selectors
255
+ let result1 = Query.get(q1)
256
+ // result and result1 has to be same
257
+ console.log(result)
176
258
  ```
177
259
 
178
- ``parse`` method, gets two parameters: ``part`` and ``fn`` and return array with results.
179
- * ``part`` is a part of element. It can be innerText, id, tagName or any property inside attributes.
180
- * ``fn`` is a filter function which gets content of part. If return true, content will be included.
181
-
182
- Example:
260
+ Result:
183
261
  ```javascript
184
- new Document(htmlText).$$('div')
185
- .parse('innerText',
186
- content=> (content.length > 0) ? true : false)
262
+ [
263
+ {
264
+ "query": "div.some",
265
+ "tag": "div",
266
+ "classList": [
267
+ "some"
268
+ ],
269
+ "ancestors": [
270
+ {
271
+ "query": ".some-id",
272
+ "classList": [
273
+ "some-id"
274
+ ],
275
+ "parents": [
276
+ {
277
+ "query": "div",
278
+ "tag": "div"
279
+ }
280
+ ],
281
+ "prev": {
282
+ "query": "p",
283
+ "tag": "p",
284
+ "parents": [
285
+ {
286
+ "query": ".some[0]",
287
+ "classList": [
288
+ "some"
289
+ ],
290
+ "attribs": [
291
+ {
292
+ check:(f),
293
+ "query": "[type$=\"radio and some\"]",
294
+ "name": "type",
295
+ "value": "radio and some",
296
+ "sign": "$="
297
+ }
298
+ ]
299
+ }
300
+ ],
301
+ "prevAny": {
302
+ "query": "div.tabs",
303
+ "tag": "div",
304
+ "classList": [
305
+ "tabs"
306
+ ],
307
+ "parents": [
308
+ {
309
+ "query": "html",
310
+ "tag": "html"
311
+ },
312
+ {
313
+ "query": "body",
314
+ "tag": "body"
315
+ }
316
+ ]
317
+ },
318
+ "group": "html>body>div.tabs~.some[0]>p"
319
+ },
320
+ "group": "html>body>div.tabs~.some[0]>p+div>.some-id"
321
+ },
322
+ {
323
+ "query": "input[1]",
324
+ "tag": "input",
325
+ "attribs": [
326
+ {
327
+ "query": "[disabled]",
328
+ "name": "disabled"
329
+ }
330
+ ],
331
+ "prevAny": {
332
+ "query": ".tab-content",
333
+ "classList": [
334
+ "tab-content"
335
+ ]
336
+ },
337
+ "group": ".tab-content~input[1]"
338
+ }
339
+ ],
340
+ "group": "html>body>div.tabs~.some[type $= \"radio and some\"]>p+div>.some-id .tab-content~input[disabled] div.some"
341
+ }
342
+ ]
187
343
  ```
188
344
 
189
- ## Building html
345
+ ### Attribs and check function
346
+ if attribute has value, attrib object will contain check function with one parameter for value to check.
190
347
 
191
- For building html again, use ``build`` method.
192
- Example:
193
348
  ```javascript
194
- let element = document.$('div')
195
- element.classList.add('another')
196
- element.classList.remove('some')
197
- element.id = 'new-id'
198
- document.build() // return new html text
199
- document.build([__dirname,'new-index.html']) // will create a file with new html text
349
+ let s = Query.get('[test^="some"]')[0]
350
+ console.log(s.attribs[0].check('some value test')) // true
200
351
  ```
352
+
@@ -0,0 +1,74 @@
1
+ ## Selector
2
+
3
+ Selector is a class which uses HtmlParser for parsing html and Query for parsing html/css query.
4
+ Everything does Query class support, can be selected (so far available all, except pseudos).
5
+ Selector can be used on frontend and on backend.
6
+
7
+
8
+ ### Basics
9
+
10
+ **Syntax**
11
+ ```javascript
12
+ let doc = new HtmlSelector(htmlString:string):instanceof HtmlSelector
13
+ doc.$(query:string):object | null
14
+ doc.$$(query:string):array
15
+ doc.$(query:string).$(query:qeury).$$(query:qeury)
16
+ ```
17
+
18
+ ``query`` - html/css query for selecting
19
+ ``$`` - method return first element or null
20
+ ``$$`` - return collection or empty array
21
+ Each element, has ``$`` and ``$$`` methods for selecting descendants
22
+
23
+
24
+ **Example**
25
+ ```javascript
26
+ let doc = new HtmlSelector(htmlString)
27
+ let body = doc.$('body') // querySelector('body'):element|null
28
+ let divs = body.$$('div') // body.querySelectorAll('div'):array
29
+ console.log(divs)
30
+ ```
31
+
32
+
33
+ #### FrontEnd usage
34
+ ```html
35
+ <script src="/node_modules/als-document/document.js"></script>
36
+ <script>
37
+ let htmlText = `
38
+ <div>
39
+ <span>Another one</span>
40
+ <div>Some text</div>
41
+ </div>
42
+ `
43
+ let doc = new HtmlSelector(htmlText)
44
+ let span = doc.$('div>span')
45
+ </script>
46
+ ```
47
+
48
+
49
+ #### BackEnd usage
50
+ ```javascript
51
+ let {HtmlSelector} = require('als-document')
52
+ let htmlText = `
53
+ <div>
54
+ <span>Another one</span>
55
+ <div>Some text</div>
56
+ </div>
57
+ `
58
+ let doc = new HtmlSelector(htmlText)
59
+ let span = doc.$('div>span')
60
+ ```
61
+
62
+
63
+ ### Extra abilities
64
+ Usualy, browser selector can select by tag name,class, attribute or id.
65
+ HtmlSelector, has extra selecting options, since id,style,class,events and innerHTML - can be selected as attribute.
66
+
67
+ For example you can do those things:
68
+ ```javascript
69
+ doc.$('[style*="display"]') //style includes "display"
70
+ doc.$('[onclick*="console.log"]') //event includes "console"
71
+ doc.$('[class*="btn"][class*="danger"]') //class includes "btn" and "danger"
72
+ doc.$('[id^="tab"]') //id starts with "tab-"
73
+ doc.$('[inner$="00"]') //innerText ends with ".00"
74
+ ```
@@ -0,0 +1,125 @@
1
+ class HtmlSelector {
2
+ constructor(html) {
3
+ if(typeof html == 'string') {
4
+ html = new HtmlParser(html)
5
+ this.html = html
6
+ this.elements = html.elements
7
+ this.makeSelectable()
8
+ } else console.log('Parameter is not string')
9
+ }
10
+
11
+ makeSelectable() {
12
+ this.elements.forEach(element => {
13
+ if(element.type == 'tag' && element.status !== 'close') {
14
+ element.$$ = (query) => this.$$(query,element.index,element.endIndex)
15
+ element.$ = (query) => this.$(query,element.index,element.endIndex)
16
+ }
17
+ })
18
+ }
19
+
20
+ $(query,start=0,end=this.elements.length) {
21
+ return this.$$(query,start,end,true)
22
+ }
23
+
24
+ $$(query,start=0,end=this.elements.length,single=false) {
25
+ let result = []
26
+ this.selectors = new Query(query).selectors
27
+ this.query = query
28
+ this.selectors.forEach(selector => {
29
+ for(let i=start; i<end; i++) {
30
+ let el = this.elements[i]
31
+ if(this.checkElement(el,selector) && !result.includes(el)) result.push(el)
32
+ if(single && result.length == 1) break
33
+ }
34
+ });
35
+ if(single && result.length == 1) return result[0]
36
+ else if(single && result.length == 0) return null
37
+ else return result
38
+ }
39
+
40
+ checkElement(el,selector) {
41
+ if(selector == undefined) return true
42
+ if(el == null) return false
43
+ let {tag,classList,attribs,id,prev,ancestors,parents,prevAny} = selector
44
+
45
+ if(el.status == 'close' || el.type == 'text') return false
46
+ if(id !== undefined && el.id == undefined) return false
47
+ if(id && id !== el.id) return false
48
+ if(tag && el.tag == undefined) return false
49
+ else if(tag && tag !== el.tag) return false
50
+ if(classList !== undefined && el.classList == undefined) return false
51
+ else if(classList !== undefined && Array.isArray(el.classList)) {
52
+ if(classList.every(e => el.classList.includes(e)) == false) return false
53
+ }
54
+ if(this.checkAttribs(attribs,el) == false) return false
55
+ if(this.checkElement(el.prev,prev) == false) return false
56
+ if(this.checkAncestors(el.ancestors,ancestors) == false) return false
57
+ if(this.checkParents(el.ancestors,parents) == false) return false
58
+ if(el.parent) {
59
+ if(this.prevAny(el.parent.children,el.childIndex,prevAny) == false) return false
60
+ }
61
+ return true
62
+ }
63
+
64
+ prevAny(children=[],index,prevAny) {
65
+ let size = children.length
66
+ if((size == 0 || index == 0) && prevAny) return false
67
+ for(let i=index; i>=0; i--) {
68
+ if(this.checkElement(children[i],prevAny)) return true
69
+ }
70
+ return false
71
+ }
72
+
73
+ checkAncestors(ancestors=[],selectorAncestors=[]) {
74
+ let count = 0
75
+ if(selectorAncestors.length == 0) return true
76
+ let endIndex = ancestors.length-1
77
+ let selectorIndex = selectorAncestors.length-1
78
+ while(selectorIndex>=0) {
79
+ for(let i=endIndex; i>=0; i--) {
80
+ endIndex=i-1
81
+ if(this.checkElement(ancestors[i],selectorAncestors[selectorIndex]) == true) {
82
+ count++
83
+ break
84
+ }
85
+ }
86
+ selectorIndex--
87
+ }
88
+ if(count == selectorAncestors.length) return true
89
+ else return false
90
+ }
91
+
92
+ checkParents(ancestors=[],selectorParents=[]) {
93
+ if(selectorParents.length == 0) return true
94
+ if(ancestors.length < selectorParents.length) return false
95
+ let index = ancestors.length-1
96
+ for(let i=selectorParents.length-1; i>=0; i--) {
97
+ if(this.checkElement(ancestors[index],selectorParents[i]) == false) return false
98
+ index--
99
+ }
100
+ return true
101
+ }
102
+
103
+ checkAttribs(attribs=[],el) {
104
+ let elAttribs = el.attribs
105
+ let names = Object.keys(elAttribs)
106
+ let passedTests = 0
107
+ if(attribs) for(let i=0; i<attribs.length; i++) {
108
+ let {name,value,check} = attribs[i]
109
+ if(name == 'inner' && value !== undefined && check && el.innerText) {
110
+ if(check(el.innerText)) passedTests++
111
+ }
112
+
113
+ if(!names.includes(name)) continue
114
+ else if(value == undefined) passedTests++
115
+ else if(value && elAttribs[name]) {
116
+ if(check(elAttribs[name]) == false) continue
117
+ else passedTests++
118
+ }
119
+ }
120
+ if(passedTests == attribs.length) return true
121
+ else return false
122
+ }
123
+ }
124
+
125
+ try {module.exports = HtmlSelector} catch{}