source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/parser/MapCSSParser.jj@ 3911

Last change on this file since 3911 was 3911, checked in by bastiK, 13 years ago

fixed #5985 - MapCSS conditions don't handle unquoted numeric values

File size: 14.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2options {
3 STATIC = false;
4}
5
6PARSER_BEGIN(MapCSSParser)
7package org.openstreetmap.josm.gui.mappaint.mapcss.parser;
8
9import java.awt.Color;
10import java.util.ArrayList;
11import java.util.List;
12
13import org.openstreetmap.josm.gui.mappaint.mapcss.Condition;
14import org.openstreetmap.josm.gui.mappaint.mapcss.Expression;
15import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction;
16import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSRule;
17import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
18import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
19import org.openstreetmap.josm.gui.mappaint.mapcss.Expression.FunctionExpression;
20import org.openstreetmap.josm.gui.mappaint.mapcss.Expression.LiteralExpression;
21import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.DescendentSelector;
22import org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector;
23import org.openstreetmap.josm.tools.Pair;
24
25public class MapCSSParser {
26 MapCSSStyleSource sheet;
27}
28PARSER_END(MapCSSParser)
29
30/*************
31 * Token definitions
32 */
33
34<DEFAULT>
35TOKEN:
36{
37 < IDENT: ["a"-"z","A"-"Z","_"] ( ["a"-"z","A"-"Z","_","-","0"-"9"] )* >
38| < UINT: ["1"-"9"] ( ["0"-"9"] )* >
39| < UFLOAT: ( ["0"-"9"] )+ ( "." ( ["0"-"9"] )+ )? >
40| < STRING: "\"" ( [" ","!","#"-"[","]"-"~","\u0080"-"\uFFFF"] | "\\\"" | "\\\\" )* "\"" >
41| < #REGEX_CHAR_WITHOUT_STAR: [" "-")","+"-".","0"-"[","]"-"~","\u0080"-"\uFFFF"] | "\\/" | "\\\\" >
42| < REGEX: "/" <REGEX_CHAR_WITHOUT_STAR> ( <REGEX_CHAR_WITHOUT_STAR> | "*" )* "/" >
43| < #H: ["0"-"9","a"-"f","A"-"F"] >
44| < HEXCOLOR: "#" ( <H><H><H><H><H><H> | <H><H><H> ) >
45| < S: ( " " | "\t" | "\n" | "\r" | "\f" )+ >
46| < STAR: "*" >
47| < SLASH: "/" >
48| < LBRACE: "{" >
49| < RBRACE: "}" >
50| < LSQUARE: "[" >
51| < RSQUARE: "]" >
52| < LPAR: "(" >
53| < RPAR: ")" >
54| < GREATER_EQUAL: ">=" >
55| < LESS_EQUAL: "<=" >
56| < GREATER: ">" >
57| < LESS: "<" >
58| < EQUAL: "=" >
59| < EXCLAMATION: "!" >
60| < TILDE: "~" >
61| < COLON: ":" >
62| < DCOLON: "::" >
63| < SEMICOLON: ";" >
64| < COMMA: "," >
65| < PIPE: "|" >
66| < PIPE_Z: "|z" >
67| < PLUS: "+" >
68| < MINUS: "-" >
69| < AMPERSAND: "&" >
70| < QUESTION: "?" >
71| < DOLLAR: "$" >
72| < CARET: "^" >
73| < COMMENT_START: "/*" > : COMMENT
74| < UNEXPECTED_CHAR : ~[] > // avoid TokenMgrErrors because they are hard to recover from
75}
76
77<COMMENT>
78TOKEN:
79{
80 < COMMENT_END: "*/" > : DEFAULT
81}
82
83<COMMENT>
84SKIP:
85{
86 < ~[] >
87}
88
89/*************
90 * Parser definitions
91 *
92 * rule
93 * _______________________|______________________________
94 * | |
95 * selector declaration
96 * _________|___________________ _________|____________
97 * | | | |
98 *
99 * way|z11-12[highway=residential] { color: red; width: 3 }
100 *
101 * |_____||___________________| |_________|
102 * | | |
103 * zoom condition instruction
104 *
105 * more general:
106 *
107 * way|z13-[a=b][c=d]::subpart, way|z-3[u=v]:closed::subpart2 { p1 : val; p2 : val; }
108 *
109 * 'val' can be a literal, or an expression like "prop(width, default) + 0.8".
110 *
111 */
112
113int uint() :
114{
115 Token i;
116}
117{
118 i=<UINT> { return Integer.parseInt(i.image); }
119}
120
121int int_() :
122{
123 int i;
124}
125{
126 <MINUS> i=uint() { return -i; } | i=uint() { return i; }
127}
128
129float ufloat() :
130{
131 Token f;
132}
133{
134 ( f=<UFLOAT> | f=<UINT> )
135 { return Float.parseFloat(f.image); }
136}
137
138float float_() :
139{
140 float f;
141}
142{
143 <MINUS> f=ufloat() { return -f; } | f=ufloat() { return f; }
144}
145
146String string() :
147{
148 Token t;
149}
150{
151 t=<STRING>
152 { return t.image.substring(1, t.image.length() - 1).replace("\\\"", "\"").replace("\\\\", "\\"); }
153}
154
155String string_or_ident() :
156{
157 Token t;
158 String s;
159}
160{
161 t=<IDENT> { return t.image; } | s=string() { return s; }
162}
163
164String regex() :
165{
166 Token t;
167}
168{
169 t=<REGEX>
170 { return t.image.substring(1, t.image.length() - 1); }
171}
172
173/**
174 * white-space
175 */
176void s() :
177{
178}
179{
180 ( <S> )?
181}
182
183/**
184 * mix of white-space and comments
185 */
186void w() :
187{
188}
189{
190 ( <S> | <COMMENT_START> <COMMENT_END> )*
191}
192
193/**
194 * comma delimited list of floats (at least 2, all >= 0)
195 */
196List<Float> float_array() :
197{
198 float f;
199 List<Float> fs = new ArrayList<Float>();
200}
201{
202 f=ufloat() { fs.add(f); }
203 (
204 <COMMA> s()
205 f=ufloat() { fs.add(f); }
206 )+
207 {
208 return fs;
209 }
210}
211
212/**
213 * root
214 */
215void sheet(MapCSSStyleSource sheet):
216{
217 MapCSSRule r;
218 Token com = null;
219}
220{
221 { this.sheet = sheet; }
222 w()
223 (
224 try {
225 r=rule() { if (r != null) { sheet.rules.add(r); } } w()
226 } catch (ParseException ex) {
227 error_skipto(RBRACE);
228 w();
229 }
230 )*
231 <EOF>
232}
233
234MapCSSRule rule():
235{
236 List<Selector> selectors = new ArrayList<Selector>();
237 Selector sel;
238 List<Instruction> decl;
239}
240{
241 sel=child_selector() { selectors.add(sel); }
242 (
243 <COMMA> w()
244 sel=child_selector() { selectors.add(sel); }
245 )*
246 decl=declaration()
247 { return new MapCSSRule(selectors, decl); }
248}
249
250Selector child_selector() :
251{
252 boolean child = false;
253 Selector sel1, sel2 = null;
254}
255{
256 sel1=selector() w()
257 (
258 ( <GREATER> { child = true; } | <LESS> { child = false; } ) w()
259 sel2=selector() w()
260 )?
261 { return sel2 != null ? new DescendentSelector(sel1, sel2, child) : sel1; }
262}
263
264Selector selector() :
265{
266 Token base;
267 Condition c;
268 Pair<Integer, Integer> r = null;
269 List<Condition> conditions = new ArrayList<Condition>();
270 String sub = null;
271}
272{
273 ( base=<IDENT> | base=<STAR> )
274 ( r=zoom() )?
275 ( ( c=condition() | c=pseudoclass() ) { conditions.add(c); } )*
276 ( sub=subpart() )?
277 { return new GeneralSelector(base.image, r, conditions, sub); }
278}
279
280Pair<Integer, Integer> zoom() :
281{
282 Integer min = 0;
283 Integer max = Integer.MAX_VALUE;
284}
285{
286 <PIPE_Z>
287 (
288 <MINUS> max=uint()
289 |
290 min=uint() ( <MINUS> ( max=uint() )? )?
291 )
292 { return new Pair<Integer, Integer>(min, max); }
293}
294
295Condition condition() :
296{
297 Condition c;
298 Expression e;
299}
300{
301 <LSQUARE> s()
302 (
303 LOOKAHEAD( simple_key_condition() s() <RSQUARE> )
304 c=simple_key_condition() s() <RSQUARE> { return c; }
305 |
306 LOOKAHEAD( simple_key_value_condition() s() <RSQUARE> )
307 c=simple_key_value_condition() s() <RSQUARE> { return c; }
308 |
309 e=expression() <RSQUARE> { return new Condition.ExpressionCondition(e); }
310 )
311}
312
313String tag_key() :
314{
315 String s;
316 Token t;
317}
318{
319 s=string() { return s; }
320 |
321 t=<IDENT> { s = t.image; } ( <COLON> t=<IDENT> { s += ':' + t.image; } )* { return s; }
322}
323
324Condition simple_key_condition() :
325{
326 boolean not = false;
327 boolean yes = false;
328 String key;
329}
330{
331 ( <EXCLAMATION> { not = true; } )?
332 key=tag_key()
333 ( <QUESTION> { yes = true; } )?
334 { return new Condition.KeyCondition(key, not, yes); }
335}
336
337Condition simple_key_value_condition() :
338{
339 String key;
340 String val;
341 float f;
342 int i;
343 Condition.Op op;
344}
345{
346 key=tag_key() s()
347 (
348 LOOKAHEAD(2)
349 <EQUAL> <TILDE> { op=Condition.Op.REGEX; } s() val=regex()
350 |
351 (
352 <EXCLAMATION> <EQUAL> { op=Condition.Op.NEQ; }
353 |
354 <EQUAL> { op=Condition.Op.EQ; }
355 |
356 <TILDE> <EQUAL> { op=Condition.Op.ONE_OF; } val=string_or_ident()
357 |
358 <CARET> <EQUAL> { op=Condition.Op.BEGINS_WITH; } val=string_or_ident()
359 |
360 <DOLLAR> <EQUAL> { op=Condition.Op.ENDS_WITH; } val=string_or_ident()
361 |
362 <STAR> <EQUAL> { op=Condition.Op.CONTAINS; } val=string_or_ident()
363 )
364 s()
365 (
366 LOOKAHEAD(2)
367 i=int_() { val=Integer.toString(i); }
368 |
369 f=float_() { val=Float.toString(f); }
370 |
371 val=string_or_ident()
372 )
373 |
374 (
375 <GREATER_EQUAL> { op=Condition.Op.GREATER_OR_EQUAL; }
376 |
377 <GREATER> { op=Condition.Op.GREATER; }
378 |
379 <LESS_EQUAL> { op=Condition.Op.LESS_OR_EQUAL; }
380 |
381 <LESS> { op=Condition.Op.LESS; }
382 )
383 s()
384 f=float_() { val=Float.toString(f); }
385 )
386 { return new Condition.KeyValueCondition(key, val, op); }
387}
388
389Condition pseudoclass() :
390{
391 Token t;
392 boolean not = false;
393}
394{
395 ( <EXCLAMATION> { not = true; } )?
396 <COLON>
397 t=<IDENT>
398 { return new Condition.PseudoClassCondition(t.image, not); }
399}
400
401String subpart() :
402{
403 Token t;
404}
405{
406 <DCOLON>
407 ( t=<IDENT> | t=<STAR> )
408 { return t.image; }
409}
410
411List<Instruction> declaration() :
412{
413 List<Instruction> ins = new ArrayList<Instruction>();
414 Instruction i;
415 Token key;
416 Object val;
417}
418{
419 <LBRACE> w()
420 (
421 key=<IDENT> w() <COLON> w()
422 (
423 LOOKAHEAD( float_array() w() ( <SEMICOLON> | <RBRACE> ) )
424 val=float_array()
425 { ins.add(new Instruction.AssignmentInstruction(key.image, val)); }
426 w()
427 ( <RBRACE> { return ins; } | <SEMICOLON> w() )
428 |
429 LOOKAHEAD( expression() ( <SEMICOLON> | <RBRACE> ) )
430 val=expression()
431 { ins.add(new Instruction.AssignmentInstruction(key.image, val)); }
432 ( <RBRACE> { return ins; } | <SEMICOLON> w() )
433 |
434 val=readRaw() w() { ins.add(new Instruction.AssignmentInstruction(key.image, val)); }
435 )
436 )*
437 <RBRACE>
438 { return ins; }
439}
440
441Expression expression():
442{
443 List<Expression> args = new ArrayList<Expression>();
444 Expression e;
445 String op = null;
446}
447{
448 (
449 <EXCLAMATION> { op = "not"; } w() e=primary() { args.add(e); } w()
450 |
451 <MINUS> { op = "minus"; } w() e=primary() { args.add(e); } w()
452 |
453
454 (
455 e=primary() { args.add(e); } w()
456 (
457 ( <PLUS> { op = "plus"; } w() e=primary() { args.add(e); } w() )+
458 |
459 ( <STAR> { op = "times"; } w() e=primary() { args.add(e); } w() )+
460 |
461 ( <MINUS> { op = "minus"; } w() e=primary() { args.add(e); } w() )+
462 |
463 ( <SLASH> { op = "divided_by"; } w() e=primary() { args.add(e); } w() )+
464 |
465 <GREATER_EQUAL> { op = "greater_equal"; } w() e=primary() { args.add(e); } w()
466 |
467 <LESS_EQUAL> { op = "less_equal"; } w() e=primary() { args.add(e); } w()
468 |
469 <GREATER> { op = "greater"; } w() e=primary() { args.add(e); } w()
470 |
471 <EQUAL> ( <EQUAL> )? { op = "equal"; } w() e=primary() { args.add(e); } w()
472 |
473 <LESS> { op = "less"; } w() e=primary() { args.add(e); } w()
474 |
475 <AMPERSAND> <AMPERSAND> { op = "and"; } w() e=primary() { args.add(e); } w()
476 |
477 <PIPE> <PIPE> { op = "or"; } w() e=primary() { args.add(e); } w()
478 |
479 <QUESTION> { op = "cond"; } w() e=primary() { args.add(e); } w() <COLON> w() e=primary() { args.add(e); } w()
480 )?
481 )
482 )
483 {
484 if (op == null)
485 return args.get(0);
486 return new FunctionExpression(op, args);
487 }
488}
489
490Expression primary() :
491{
492 Expression nested;
493 FunctionExpression fn;
494 Object lit;
495}
496{
497 LOOKAHEAD(2) // both function and identifier start with an identifier
498 fn=function() { return fn; }
499 |
500 lit=literal() { return new LiteralExpression(lit); }
501 |
502 <LPAR> w() nested=expression() <RPAR> { return nested; }
503}
504
505FunctionExpression function() :
506{
507 Token tmp;
508 Expression arg;
509 String name;
510 List<Expression> args = new ArrayList<Expression>();
511}
512{
513 tmp=<IDENT> { name = tmp.image; } w()
514 <LPAR> w()
515 (
516 arg=expression() { args.add(arg); }
517 ( <COMMA> w() arg=expression() { args.add(arg); } )*
518 )?
519 <RPAR>
520 { return new FunctionExpression(name, args); }
521}
522
523Object literal() :
524{
525 Object val;
526 Token t;
527 float f;
528}
529{
530 val=string_or_ident() { return val; }
531 |
532 <PLUS> f=ufloat() { return new Instruction.RelativeFloat(f); }
533 |
534 f=ufloat() { return f; }
535 |
536 t=<HEXCOLOR>
537 {
538 String clr = t.image.substring(1);
539 if (clr.length() == 3) {
540 clr = new String(new char[] {clr.charAt(0),clr.charAt(0),clr.charAt(1),clr.charAt(1),clr.charAt(2),clr.charAt(2)});
541 }
542 if (clr.length() != 6)
543 throw new AssertionError();
544 return new Color(Integer.parseInt(clr, 16));
545 }
546}
547
548JAVACODE
549void error_skipto(int kind) {
550 if (token.kind == EOF)
551 throw new ParseException("Reached end of file while parsing");
552 ParseException e = generateParseException();
553 System.err.println("Skipping to the next rule, because of an error:");
554 System.err.println(e);
555 if (sheet != null) {
556 sheet.logError(new ParseException(e.getMessage()));
557 }
558 Token t;
559 do {
560 t = getNextToken();
561 } while (t.kind != kind && t.kind != EOF);
562 if (t.kind == EOF)
563 throw new ParseException("Reached end of file while parsing");
564}
565
566JAVACODE
567/**
568 * read everything to the next semicolon
569 */
570String readRaw() {
571 Token t;
572 StringBuilder s = new StringBuilder();
573 while (true) {
574 t = getNextToken();
575 if ((t.kind == S || t.kind == STRING || t.kind == UNEXPECTED_CHAR) &&
576 t.image.contains("\n")) {
577 ParseException e = new ParseException(String.format("Warning: end of line while reading an unquoted string at line %s column %s.", t.beginLine, t.beginColumn));
578 System.err.println(e);
579 if (sheet != null) {
580 sheet.logError(e);
581 }
582 }
583 if (t.kind == SEMICOLON || t.kind == EOF)
584 break;
585 s.append(t.image);
586 }
587 if (t.kind == EOF)
588 throw new ParseException("Reached end of file while parsing");
589 return s.toString();
590}
591
Note: See TracBrowser for help on using the repository browser.