Imported Upstream version 1.0.1
Thorsten Alteholz
8 years ago
0 | #nth-check [![Build Status](https://travis-ci.org/fb55/nth-check.png)](https://travis-ci.org/fb55/nth-check) | |
1 | ||
2 | A performant nth-check parser & compiler. | |
3 | ||
4 | ###About | |
5 | ||
6 | This module can be used to parse & compile nth-checks, as they are found in CSS 3's `nth-child()` and `nth-last-of-type()`. | |
7 | ||
8 | `nth-check` focusses on speed, providing optimized functions for different kinds of nth-child formulas, while still following the [spec](http://www.w3.org/TR/css3-selectors/#nth-child-pseudo). | |
9 | ||
10 | ###API | |
11 | ||
12 | ```js | |
13 | var nthCheck = require("nth-check"); | |
14 | ``` | |
15 | ||
16 | #####`nthCheck(formula)` | |
17 | ||
18 | First parses, then compiles the formula. | |
19 | ||
20 | #####`nthCheck.parse(formula)` | |
21 | ||
22 | Parses the expression, throws a `SyntaxError` if it fails, otherwise returns an array containing two elements. | |
23 | ||
24 | __Example:__ | |
25 | ||
26 | ```js | |
27 | nthCheck.parse("2n+3") //[2, 3] | |
28 | ``` | |
29 | ||
30 | #####`nthCheck.compile([a, b])` | |
31 | ||
32 | Takes an array with two elements (as returned by `.parse`) and returns a highly optimized function. | |
33 | ||
34 | If the formula doesn't match any elements, it returns [`boolbase`](https://github.com/fb55/boolbase)'s `falseFunc`, otherwise, a function accepting an _index_ is returned, which returns whether or not a passed _index_ matches the formula. (Note: The spec starts counting at `1`, the returned function at `0`). | |
35 | ||
36 | __Example:__ | |
37 | ```js | |
38 | var check = nthCheck.compile([2, 3]); | |
39 | ||
40 | check(0) //false | |
41 | check(1) //false | |
42 | check(2) //true | |
43 | check(3) //false | |
44 | check(4) //true | |
45 | check(5) //false | |
46 | check(6) //true | |
47 | ``` | |
48 | ||
49 | --- | |
50 | License: BSD |
0 | module.exports = compile; | |
1 | ||
2 | var BaseFuncs = require("boolbase"), | |
3 | trueFunc = BaseFuncs.trueFunc, | |
4 | falseFunc = BaseFuncs.falseFunc; | |
5 | ||
6 | /* | |
7 | returns a function that checks if an elements index matches the given rule | |
8 | highly optimized to return the fastest solution | |
9 | */ | |
10 | function compile(parsed){ | |
11 | var a = parsed[0], | |
12 | b = parsed[1] - 1; | |
13 | ||
14 | //when b <= 0, a*n won't be possible for any matches when a < 0 | |
15 | //besides, the specification says that no element is matched when a and b are 0 | |
16 | if(b < 0 && a <= 0) return falseFunc; | |
17 | ||
18 | //when a is in the range -1..1, it matches any element (so only b is checked) | |
19 | if(a ===-1) return function(pos){ return pos <= b; }; | |
20 | if(a === 0) return function(pos){ return pos === b; }; | |
21 | //when b <= 0 and a === 1, they match any element | |
22 | if(a === 1) return b < 0 ? trueFunc : function(pos){ return pos >= b; }; | |
23 | ||
24 | //when a > 0, modulo can be used to check if there is a match | |
25 | var bMod = b % a; | |
26 | if(bMod < 0) bMod += a; | |
27 | ||
28 | if(a > 1){ | |
29 | return function(pos){ | |
30 | return pos >= b && pos % a === bMod; | |
31 | }; | |
32 | } | |
33 | ||
34 | a *= -1; //make `a` positive | |
35 | ||
36 | return function(pos){ | |
37 | return pos <= b && pos % a === bMod; | |
38 | }; | |
39 | }⏎ |
0 | var parse = require("./parse.js"), | |
1 | compile = require("./compile.js"); | |
2 | ||
3 | module.exports = function nthCheck(formula){ | |
4 | return compile(parse(formula)); | |
5 | }; | |
6 | ||
7 | module.exports.parse = parse; | |
8 | module.exports.compile = compile;⏎ |
0 | { | |
1 | "name": "nth-check", | |
2 | "version": "1.0.1", | |
3 | "description": "performant nth-check parser & compiler", | |
4 | "main": "index.js", | |
5 | "scripts": { | |
6 | "test": "node test" | |
7 | }, | |
8 | "repository": { | |
9 | "type": "git", | |
10 | "url": "https://github.com/fb55/nth-check" | |
11 | }, | |
12 | "keywords": [ | |
13 | "nth-child", | |
14 | "nth", | |
15 | "css" | |
16 | ], | |
17 | "author": "Felix Boehm <me@feedic.com>", | |
18 | "license": "BSD", | |
19 | "bugs": { | |
20 | "url": "https://github.com/fb55/nth-check/issues" | |
21 | }, | |
22 | "homepage": "https://github.com/fb55/nth-check", | |
23 | "dependencies": { | |
24 | "boolbase": "~1.0.0" | |
25 | } | |
26 | } |
0 | module.exports = parse; | |
1 | ||
2 | //following http://www.w3.org/TR/css3-selectors/#nth-child-pseudo | |
3 | ||
4 | //[ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? | |
5 | var re_nthElement = /^([+\-]?\d*n)?\s*(?:([+\-]?)\s*(\d+))?$/; | |
6 | ||
7 | /* | |
8 | parses a nth-check formula, returns an array of two numbers | |
9 | */ | |
10 | function parse(formula){ | |
11 | formula = formula.trim().toLowerCase(); | |
12 | ||
13 | if(formula === "even"){ | |
14 | return [2, 0]; | |
15 | } else if(formula === "odd"){ | |
16 | return [2, 1]; | |
17 | } else { | |
18 | var parsed = formula.match(re_nthElement); | |
19 | ||
20 | if(!parsed){ | |
21 | throw new SyntaxError("n-th rule couldn't be parsed ('" + formula + "')"); | |
22 | } | |
23 | ||
24 | var a; | |
25 | ||
26 | if(parsed[1]){ | |
27 | a = parseInt(parsed[1], 10); | |
28 | if(isNaN(a)){ | |
29 | if(parsed[1].charAt(0) === "-") a = -1; | |
30 | else a = 1; | |
31 | } | |
32 | } else a = 0; | |
33 | ||
34 | return [ | |
35 | a, | |
36 | parsed[3] ? parseInt((parsed[2] || "") + parsed[3], 10) : 0 | |
37 | ]; | |
38 | } | |
39 | } |
0 | var nthCheck = require("./"), | |
1 | assert = require("assert"); | |
2 | ||
3 | var invalid = ["-", "asdf", "2n+-0", "2+0", "- 1n", "-1 n"]; | |
4 | ||
5 | function parseInvalid(){ | |
6 | invalid.forEach(function(formula){ | |
7 | assert.throws(function(){ | |
8 | nthCheck.parse(formula); | |
9 | }, | |
10 | SyntaxError, | |
11 | formula | |
12 | ); | |
13 | }); | |
14 | } | |
15 | ||
16 | var valid = { | |
17 | "1": [ 0, 1 ], | |
18 | "2": [ 0, 2 ], | |
19 | "3": [ 0, 3 ], | |
20 | "5": [ 0, 5 ], | |
21 | " 1 ": [ 0, 1 ], | |
22 | " 5 ": [ 0, 5 ], | |
23 | "+2n + 1": [ 2, 1 ], | |
24 | "-1": [ 0, -1 ], | |
25 | "-1n + 3": [ -1, 3 ], | |
26 | "-1n+3": [ -1, 3 ], | |
27 | "-n+2": [ -1, 2 ], | |
28 | "-n+3": [ -1, 3 ], | |
29 | "0n+3": [ 0, 3 ], | |
30 | "1n": [ 1, 0 ], | |
31 | "1n+0": [ 1, 0 ], | |
32 | "2n": [ 2, 0 ], | |
33 | "2n + 1": [ 2, 1 ], | |
34 | "2n+1": [ 2, 1 ], | |
35 | "3n": [ 3, 0 ], | |
36 | "3n+0": [ 3, 0 ], | |
37 | "3n+1": [ 3, 1 ], | |
38 | "3n+2": [ 3, 2 ], | |
39 | "3n+3": [ 3, 3 ], | |
40 | "3n-1": [ 3, -1 ], | |
41 | "3n-2": [ 3, -2 ], | |
42 | "3n-3": [ 3, -3 ], | |
43 | even: [ 2, 0 ], | |
44 | n: [ 1, 0 ], | |
45 | "n+2": [ 1, 2 ], | |
46 | odd: [ 2, 1 ], | |
47 | ||
48 | //surprisingly, neither sizzle, qwery or nwmatcher cover these cases | |
49 | "-4n+13": [-4, 13], | |
50 | "-2n + 12": [-2, 12] | |
51 | }; | |
52 | ||
53 | function parseValid(){ | |
54 | Object.keys(valid).forEach(function(formula){ | |
55 | assert.deepEqual(nthCheck.parse(formula), valid[formula], formula); | |
56 | }); | |
57 | } | |
58 | ||
59 | function testValid(){ | |
60 | Object.keys(valid).forEach(function(formula){ | |
61 | testFormula(valid[formula], formula); | |
62 | }); | |
63 | } | |
64 | ||
65 | var valArray = Array.apply(null, Array(2e3)).map(function(_, i){return i;}); | |
66 | ||
67 | function testFormula(formula, name){ | |
68 | var filtered = valArray.filter(nthCheck.compile(formula)), | |
69 | iterated = stupidNth(formula); | |
70 | ||
71 | try { | |
72 | assert.deepEqual(filtered, iterated, name); | |
73 | } catch(e){ | |
74 | e.expected = JSON.stringify(iterated) + " " + name; | |
75 | e.actual = JSON.stringify(filtered) + " " + name; | |
76 | throw e; | |
77 | } | |
78 | } | |
79 | ||
80 | function stupidNth(formula, limit){ | |
81 | var a = formula[0], | |
82 | b = formula[1]; | |
83 | ||
84 | if(a === 0 && b > 0) return [b - 1]; | |
85 | ||
86 | //taken from qwery | |
87 | return valArray.filter(function(val){ | |
88 | for(var i = b, l = valArray.length; ((a > 0) ? (i <= l) : (i >= 1)); i += a){ | |
89 | if(val === valArray[i - 1]) return true; | |
90 | } | |
91 | }); | |
92 | } | |
93 | ||
94 | process.stdout.write("- parser"); | |
95 | process.stdout.write("\n - parse invalid:\t"); | |
96 | parseInvalid(); | |
97 | process.stdout.write("X\n - parse valid:\t"); | |
98 | parseValid(); | |
99 | process.stdout.write("X\n- check values: \t"); | |
100 | testValid(); | |
101 | process.stdout.write("X\n"); |