|
1 | 1 | 'use strict'; |
2 | 2 |
|
| 3 | +var walkers = require('./ast-walkers'), |
| 4 | + matchNode = require('./match-node'), |
| 5 | + Collector = require('./collector'); |
| 6 | + |
3 | 7 | var select = exports; |
4 | 8 |
|
5 | 9 |
|
6 | | -select.selectors = function (selector, ast) { |
7 | | - var result = []; |
8 | | - selector.selectors.forEach(function (selector) { |
9 | | - append(result, select.ruleSet(selector, ast)); |
| 10 | +select.selectors = function (selectors, ast) { |
| 11 | + var collect = Collector(); |
| 12 | + selectors.selectors.forEach(function (ruleSet) { |
| 13 | + collect(select.ruleSet(ruleSet, ast)); |
10 | 14 | }); |
11 | | - return result; |
| 15 | + return collect.result; |
12 | 16 | }; |
13 | 17 |
|
14 | 18 |
|
15 | | -select.ruleSet = function (selector, ast) { |
16 | | - return select.rule(selector.rule, ast); |
| 19 | +select.ruleSet = function (ruleSet, ast) { |
| 20 | + return select.rule(ruleSet.rule, ast); |
17 | 21 | }; |
18 | 22 |
|
19 | 23 |
|
20 | | -select.rule = function (selector, ast, parentNode) { |
21 | | - var result = []; |
22 | | - |
23 | | - switch (selector.nestingOperator) { |
24 | | - case null: |
25 | | - case undefined: |
26 | | - case '>': |
27 | | - walk(ast, parentNode); |
28 | | - break; |
29 | | - |
30 | | - case '+': |
31 | | - if (ast.children && ast.children.length) { |
32 | | - walk(ast.children[0], ast); |
33 | | - } |
34 | | - break; |
35 | | - |
36 | | - case '~': |
37 | | - (ast.children || []).forEach(function (node) { |
38 | | - walk(node, ast); |
39 | | - }); |
40 | | - break; |
41 | | - |
42 | | - default: |
43 | | - throw Error('Undefined nesting operator: ' + selector.nestingOperator); |
| 24 | +select.rule = function (rule, ast) { |
| 25 | + var collect = Collector(); |
| 26 | + search(rule, ast, 0, null); |
| 27 | + return collect.result; |
| 28 | + |
| 29 | + function search (rule, node, nodeIndex, parent) { |
| 30 | + ({ |
| 31 | + // `undefined` is the operator on the top rule selector. |
| 32 | + undefined: walkers.topScan, |
| 33 | + // `null` stands for the descendant combinator. |
| 34 | + null: walkers.descendant, |
| 35 | + '>': walkers.child, |
| 36 | + '+': walkers.adjacentSibling, |
| 37 | + '~': walkers.generalSibling |
| 38 | + })[rule.nestingOperator]( |
| 39 | + node, nodeIndex, parent, match.bind(null, rule) |
| 40 | + ); |
44 | 41 | } |
45 | 42 |
|
46 | | - return result; |
47 | | - |
48 | | - function walk (node, parent) { |
49 | | - if (matches(selector, node, parent == null)) { |
50 | | - if (!selector.rule) { |
51 | | - append(result, [node]); |
52 | | - } |
53 | | - else if (!selector.rule.nestingOperator || |
54 | | - selector.rule.nestingOperator == '>') { |
55 | | - if (node.children) { |
56 | | - node.children.forEach(function (childNode) { |
57 | | - append(result, select.rule(selector.rule, childNode, node)); |
58 | | - }); |
59 | | - } |
| 43 | + function match (rule, node, nodeIndex, parent) { |
| 44 | + if (matchNode(rule, node, parent)) { |
| 45 | + if (rule.rule) { |
| 46 | + search(rule.rule, node, nodeIndex, parent); |
60 | 47 | } |
61 | 48 | else { |
62 | | - if (parent) { |
63 | | - append(result, select.rule(selector.rule, { |
64 | | - children: parent.children.slice(parent.children.indexOf(node) + 1) |
65 | | - }, parent)); |
66 | | - } |
| 49 | + collect(node); |
67 | 50 | } |
68 | 51 | } |
69 | | - |
70 | | - if (!selector.nestingOperator && node.children) { |
71 | | - node.children.forEach(function (child) { |
72 | | - walk(child, node); |
73 | | - }); |
74 | | - } |
75 | 52 | } |
76 | 53 | }; |
77 | | - |
78 | | - |
79 | | -// True if node matches head of selector rule. |
80 | | -function matches (rule, node, isRoot) { |
81 | | - var match = true; |
82 | | - |
83 | | - // Match type. |
84 | | - match = match && (!rule.tagName || rule.tagName == '*' || |
85 | | - rule.tagName == node.type); |
86 | | - |
87 | | - // Match attributes. |
88 | | - match = match && (rule.attrs || []).every(function (attr) { |
89 | | - switch (attr.operator) { |
90 | | - case undefined: |
91 | | - return attr.name in node; |
92 | | - |
93 | | - case '=': |
94 | | - // First, check for special values. |
95 | | - switch (attr.value) { |
96 | | - case 'null': |
97 | | - if (attr.name in node && node[attr.name] == null) return true; |
98 | | - break; |
99 | | - |
100 | | - case 'true': |
101 | | - if (node[attr.name] === true) return true; |
102 | | - break; |
103 | | - |
104 | | - case 'false': |
105 | | - if (node[attr.name] === false) return true; |
106 | | - break; |
107 | | - } |
108 | | - return node[attr.name] == attr.value; |
109 | | - |
110 | | - case '^=': |
111 | | - return typeof node[attr.name] == 'string' && |
112 | | - node[attr.name].slice(0, attr.value.length) == attr.value; |
113 | | - |
114 | | - case '*=': |
115 | | - return typeof node[attr.name] == 'string' && |
116 | | - node[attr.name].indexOf(attr.value) >= 0; |
117 | | - |
118 | | - case '$=': |
119 | | - return typeof node[attr.name] == 'string' && |
120 | | - node[attr.name].slice(-attr.value.length) == attr.value; |
121 | | - |
122 | | - default: |
123 | | - throw Error('Undefined attribute operator: ' + attr.operator); |
124 | | - } |
125 | | - }); |
126 | | - |
127 | | - // Match pseudo classes. |
128 | | - match = match && (rule.pseudos || []).every(function (pseudo) { |
129 | | - switch (pseudo.name) { |
130 | | - case 'root': |
131 | | - return isRoot; |
132 | | - |
133 | | - case 'not': |
134 | | - return !matches(pseudo.value.rule, node); |
135 | | - |
136 | | - default: |
137 | | - throw Error('Undefined pseudo-class: ' + pseudo.name); |
138 | | - } |
139 | | - }); |
140 | | - |
141 | | - return match; |
142 | | -} |
143 | | - |
144 | | - |
145 | | -function append (array, elements) { |
146 | | - elements.forEach(function (el) { |
147 | | - if (array.indexOf(el) < 0) { |
148 | | - array.push(el); |
149 | | - } |
150 | | - }); |
151 | | -} |
0 commit comments