1 | import { warn } from '../util';
|
2 | import * as is from '../is';
|
3 | import exprs from './expressions';
|
4 | import newQuery from './new-query';
|
5 | import Type from './type';
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | const consumeExpr = ( remaining ) => {
|
13 | let expr;
|
14 | let match;
|
15 | let name;
|
16 |
|
17 | for( let j = 0; j < exprs.length; j++ ){
|
18 | let e = exprs[ j ];
|
19 | let n = e.name;
|
20 |
|
21 | let m = remaining.match( e.regexObj );
|
22 |
|
23 | if( m != null ){
|
24 | match = m;
|
25 | expr = e;
|
26 | name = n;
|
27 |
|
28 | let consumed = m[0];
|
29 | remaining = remaining.substring( consumed.length );
|
30 |
|
31 | break;
|
32 | }
|
33 | }
|
34 |
|
35 | return {
|
36 | expr: expr,
|
37 | match: match,
|
38 | name: name,
|
39 | remaining: remaining
|
40 | };
|
41 | };
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | const consumeWhitespace = ( remaining ) => {
|
50 | let match = remaining.match( /^\s+/ );
|
51 |
|
52 | if( match ){
|
53 | let consumed = match[0];
|
54 | remaining = remaining.substring( consumed.length );
|
55 | }
|
56 |
|
57 | return remaining;
|
58 | };
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 | const parse = function( selector ){
|
66 | let self = this;
|
67 |
|
68 | let remaining = self.inputText = selector;
|
69 |
|
70 | let currentQuery = self[0] = newQuery();
|
71 | self.length = 1;
|
72 |
|
73 | remaining = consumeWhitespace( remaining );
|
74 |
|
75 | for( ;; ){
|
76 | let exprInfo = consumeExpr( remaining );
|
77 |
|
78 | if( exprInfo.expr == null ){
|
79 | warn( 'The selector `' + selector + '`is invalid' );
|
80 | return false;
|
81 | } else {
|
82 | let args = exprInfo.match.slice( 1 );
|
83 |
|
84 |
|
85 | let ret = exprInfo.expr.populate( self, currentQuery, args );
|
86 |
|
87 | if( ret === false ){
|
88 | return false;
|
89 | } else if( ret != null ){
|
90 | currentQuery = ret;
|
91 | }
|
92 | }
|
93 |
|
94 | remaining = exprInfo.remaining;
|
95 |
|
96 |
|
97 | if( remaining.match( /^\s*$/ ) ){
|
98 | break;
|
99 | }
|
100 | }
|
101 |
|
102 | let lastQ = self[self.length - 1];
|
103 |
|
104 | if( self.currentSubject != null ){
|
105 | lastQ.subject = self.currentSubject;
|
106 | }
|
107 |
|
108 | lastQ.edgeCount = self.edgeCount;
|
109 | lastQ.compoundCount = self.compoundCount;
|
110 |
|
111 | for( let i = 0; i < self.length; i++ ){
|
112 | let q = self[i];
|
113 |
|
114 |
|
115 | if( q.compoundCount > 0 && q.edgeCount > 0 ){
|
116 | warn( 'The selector `' + selector + '` is invalid because it uses both a compound selector and an edge selector' );
|
117 | return false;
|
118 | }
|
119 |
|
120 | if( q.edgeCount > 1 ){
|
121 | warn( 'The selector `' + selector + '` is invalid because it uses multiple edge selectors' );
|
122 | return false;
|
123 | } else if( q.edgeCount === 1 ){
|
124 | warn( 'The selector `' + selector + '` is deprecated. Edge selectors do not take effect on changes to source and target nodes after an edge is added, for performance reasons. Use a class or data selector on edges instead, updating the class or data of an edge when your app detects a change in source or target nodes.' );
|
125 | }
|
126 | }
|
127 |
|
128 | return true;
|
129 | };
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 | export const toString = function(){
|
137 | if( this.toStringCache != null ){
|
138 | return this.toStringCache;
|
139 | }
|
140 |
|
141 | let clean = function( obj ){
|
142 | if( obj == null ){
|
143 | return '';
|
144 | } else {
|
145 | return obj;
|
146 | }
|
147 | };
|
148 |
|
149 | let cleanVal = function( val ){
|
150 | if( is.string( val ) ){
|
151 | return '"' + val + '"';
|
152 | } else {
|
153 | return clean( val );
|
154 | }
|
155 | };
|
156 |
|
157 | let space = ( val ) => {
|
158 | return ' ' + val + ' ';
|
159 | };
|
160 |
|
161 | let checkToString = ( check, subject ) => {
|
162 | let { type, value } = check;
|
163 |
|
164 | switch( type ){
|
165 | case Type.GROUP: {
|
166 | let group = clean( value );
|
167 |
|
168 | return group.substring( 0, group.length - 1 );
|
169 | }
|
170 |
|
171 | case Type.DATA_COMPARE: {
|
172 | let { field, operator } = check;
|
173 |
|
174 | return '[' + field + space( clean( operator ) ) + cleanVal( value ) + ']';
|
175 | }
|
176 |
|
177 | case Type.DATA_BOOL: {
|
178 | let { operator, field } = check;
|
179 |
|
180 | return '[' + clean( operator ) + field + ']';
|
181 | }
|
182 |
|
183 | case Type.DATA_EXIST: {
|
184 | let { field } = check;
|
185 |
|
186 | return '[' + field + ']';
|
187 | }
|
188 |
|
189 | case Type.META_COMPARE: {
|
190 | let { operator, field } = check;
|
191 |
|
192 | return '[[' + field + space( clean( operator ) ) + cleanVal( value ) + ']]';
|
193 | }
|
194 |
|
195 | case Type.STATE: {
|
196 | return value;
|
197 | }
|
198 |
|
199 | case Type.ID: {
|
200 | return '#' + value;
|
201 | }
|
202 |
|
203 | case Type.CLASS: {
|
204 | return '.' + value;
|
205 | }
|
206 |
|
207 | case Type.PARENT:
|
208 | case Type.CHILD: {
|
209 | return queryToString(check.parent, subject) + space('>') + queryToString(check.child, subject);
|
210 | }
|
211 |
|
212 | case Type.ANCESTOR:
|
213 | case Type.DESCENDANT: {
|
214 | return queryToString(check.ancestor, subject) + ' ' + queryToString(check.descendant, subject);
|
215 | }
|
216 |
|
217 | case Type.COMPOUND_SPLIT: {
|
218 | let lhs = queryToString(check.left, subject);
|
219 | let sub = queryToString(check.subject, subject);
|
220 | let rhs = queryToString(check.right, subject);
|
221 |
|
222 | return lhs + (lhs.length > 0 ? ' ' : '') + sub + rhs;
|
223 | }
|
224 |
|
225 | case Type.TRUE: {
|
226 | return '';
|
227 | }
|
228 | }
|
229 | };
|
230 |
|
231 | let queryToString = ( query, subject ) => {
|
232 | return query.checks.reduce((str, chk, i) => {
|
233 | return str + (subject === query && i === 0 ? '$' : '') + checkToString(chk, subject);
|
234 | }, '');
|
235 | };
|
236 |
|
237 | let str = '';
|
238 |
|
239 | for( let i = 0; i < this.length; i++ ){
|
240 | let query = this[ i ];
|
241 |
|
242 | str += queryToString( query, query.subject );
|
243 |
|
244 | if( this.length > 1 && i < this.length - 1 ){
|
245 | str += ', ';
|
246 | }
|
247 | }
|
248 |
|
249 | this.toStringCache = str;
|
250 |
|
251 | return str;
|
252 | };
|
253 |
|
254 | export default { parse, toString };
|