1 package net.systemeD.halcyon.styleparser {
5 import net.systemeD.halcyon.ExtendedLoader;
6 import net.systemeD.halcyon.ExtendedURLLoader;
7 import net.systemeD.halcyon.DebugURLRequest;
8 import net.systemeD.halcyon.connection.Entity;
9 import net.systemeD.halcyon.ImageBank;
11 import net.systemeD.halcyon.connection.*;
13 /** A complete stylesheet, as loaded from a MapCSS file. It contains all selectors, declarations,
14 and embedded images. </p><p>
16 The RuleSet class has two principal methods: getStyles, which calculates the styles that apply
17 to an entity (returned as a StyleList); and parse, which parses a MapCSS stylesheet into
18 a complete RuleSet. */
20 public class RuleSet {
22 public var loaded:Boolean=false; // is the RuleSet fully loaded and available for use?
23 private var redrawCallback:Function=null; // function to call when CSS loaded
24 private var iconCallback:Function=null; // function to call when all icons loaded
25 private var iconsToLoad:uint=0; // number of icons left to load (fire iconCallback when ==0)
26 private var evalsToLoad:uint=0; // number of evals left to load (fire redrawCallback when ==0)
28 private var minscale:uint;
29 private var maxscale:uint;
30 public var choosers:Array;
31 public var evals:Array;
33 private static const WHITESPACE:RegExp =/^ \s+ /sx;
34 private static const COMMENT:RegExp =/^ \/\* .+? \*\/ \s* /sx; /* */
35 private static const CLASS:RegExp =/^ ([\.:]\w+) \s* /sx;
36 private static const NOT_CLASS:RegExp =/^ !([\.:]\w+) \s* /sx;
37 private static const ZOOM:RegExp =/^ \| \s* z([\d\-]+) \s* /isx;
38 private static const GROUP:RegExp =/^ , \s* /isx;
39 private static const CONDITION:RegExp =/^ \[(.+?)\] \s* /sx;
40 private static const OBJECT:RegExp =/^ (\w+) \s* /sx;
41 private static const DECLARATION:RegExp =/^ \{(.+?)\} \s* /sx;
42 private static const SUBPART:RegExp =/^ ::(\w+) \s* /sx;
43 private static const UNKNOWN:RegExp =/^ (\S+) \s* /sx;
45 private static const ZOOM_MINMAX:RegExp =/^ (\d+)\-(\d+) $/sx;
46 private static const ZOOM_MIN:RegExp =/^ (\d+)\- $/sx;
47 private static const ZOOM_MAX:RegExp =/^ \-(\d+) $/sx;
48 private static const ZOOM_SINGLE:RegExp =/^ (\d+) $/sx;
50 private static const CONDITION_TRUE:RegExp =/^ \s* ([:\w]+) \s* = \s* yes \s* $/isx;
51 private static const CONDITION_FALSE:RegExp =/^ \s* ([:\w]+) \s* = \s* no \s* $/isx;
52 private static const CONDITION_SET:RegExp =/^ \s* ([:\w]+) \s* $/sx;
53 private static const CONDITION_UNSET:RegExp =/^ \s* !([:\w]+) \s* $/sx;
54 private static const CONDITION_EQ:RegExp =/^ \s* ([:\w]+) \s* = \s* (.+) \s* $/sx;
55 private static const CONDITION_NE:RegExp =/^ \s* ([:\w]+) \s* != \s* (.+) \s* $/sx;
56 private static const CONDITION_GT:RegExp =/^ \s* ([:\w]+) \s* > \s* (.+) \s* $/sx;
57 private static const CONDITION_GE:RegExp =/^ \s* ([:\w]+) \s* >= \s* (.+) \s* $/sx;
58 private static const CONDITION_LT:RegExp =/^ \s* ([:\w]+) \s* < \s* (.+) \s* $/sx;
59 private static const CONDITION_LE:RegExp =/^ \s* ([:\w]+) \s* <= \s* (.+) \s* $/sx;
60 private static const CONDITION_REGEX:RegExp =/^ \s* ([:\w]+) \s* =~\/ \s* (.+) \/ \s* $/sx;
62 private static const ASSIGNMENT_EVAL:RegExp =/^ \s* (\S+) \s* \: \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
63 private static const ASSIGNMENT:RegExp =/^ \s* (\S+) \s* \: \s* (.+?) \s* $/sx;
64 private static const SET_TAG_EVAL:RegExp =/^ \s* set \s+(\S+)\s* = \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
65 private static const SET_TAG:RegExp =/^ \s* set \s+(\S+)\s* = \s* (.+?) \s* $/isx;
66 private static const SET_TAG_TRUE:RegExp =/^ \s* set \s+(\S+)\s* $/isx;
67 private static const EXIT:RegExp =/^ \s* exit \s* $/isx;
69 private static const oZOOM:uint=2;
70 private static const oGROUP:uint=3;
71 private static const oCONDITION:uint=4;
72 private static const oOBJECT:uint=5;
73 private static const oDECLARATION:uint=6;
74 private static const oSUBPART:uint=7;
76 private static const DASH:RegExp=/\-/g;
77 private static const COLOR:RegExp=/color$/;
78 private static const BOLD:RegExp=/^bold$/i;
79 private static const ITALIC:RegExp=/^italic|oblique$/i;
80 private static const UNDERLINE:RegExp=/^underline$/i;
81 private static const CAPS:RegExp=/^uppercase$/i;
82 private static const CENTER:RegExp=/^center$/i;
83 private static const FALSE:RegExp=/^(no|false|0)$/i;
85 private static const HEX:RegExp=/^#([0-9a-f]+)$/i;
86 private static const CSSCOLORS:Object = {
88 antiquewhite:0xfaebd7,
95 blanchedalmond:0xffebcd,
104 cornflowerblue:0x6495ed,
110 darkgoldenrod:0xb8860b,
114 darkmagenta:0x8b008b,
115 darkolivegreen:0x556b2f,
120 darkseagreen:0x8fbc8f,
121 darkslateblue:0x483d8b,
122 darkslategray:0x2f4f4f,
123 darkturquoise:0x00ced1,
126 deepskyblue:0x00bfff,
130 floralwhite:0xfffaf0,
131 forestgreen:0x228b22,
139 greenyellow:0xadff2f,
147 lavenderblush:0xfff0f5,
149 lemonchiffon:0xfffacd,
153 lightgoldenrodyellow:0xfafad2,
157 lightsalmon:0xffa07a,
158 lightseagreen:0x20b2aa,
159 lightskyblue:0x87cefa,
160 lightslategray:0x778899,
161 lightsteelblue:0xb0c4de,
162 lightyellow:0xffffe0,
168 mediumaquamarine:0x66cdaa,
170 mediumorchid:0xba55d3,
171 mediumpurple:0x9370d8,
172 mediumseagreen:0x3cb371,
173 mediumslateblue:0x7b68ee,
174 mediumspringgreen:0x00fa9a,
175 mediumturquoise:0x48d1cc,
176 mediumvioletred:0xc71585,
177 midnightblue:0x191970,
181 navajowhite:0xffdead,
189 palegoldenrod:0xeee8aa,
191 paleturquoise:0xafeeee,
192 palevioletred:0xd87093,
203 saddlebrown:0x8b4513,
214 springgreen:0x00ff7f,
226 yellowgreen:0x9acd32 };
230 public function RuleSet(mins:uint,maxs:uint,redrawCall:Function=null,iconLoadedCallback:Function=null):void {
233 redrawCallback = redrawCall;
234 iconCallback = iconLoadedCallback;
237 /** Create a StyleList for an Entity, by creating a blank StyleList, then running each StyleChooser over it.
238 @see net.systemeD.halcyon.styleparser.StyleList */
240 public function getStyles(obj:Entity, tags:Object, zoom:uint):StyleList {
241 var sl:StyleList=new StyleList();
242 for each (var sc:StyleChooser in choosers) {
243 sc.updateStyles(obj,tags,sl,zoom);
248 // ---------------------------------------------------------------------------------------------------------
249 // Loading stylesheet
251 /** Load and then parse a MapCSS stylesheet. Usually you will supply a filename, but you can also pass
252 a complete stylesheet in the string parameter. (Any string containing space characters will be
253 assumed to be a stylesheet rather than a filename.) */
255 public function loadFromCSS(str:String):void {
256 if (str.match(/[\s\n\r\t]/)!=null) { parseCSS(str); loaded=true; redrawCallback(); return; }
258 var cssLoader:NestedCSSLoader=new NestedCSSLoader();
259 cssLoader.addEventListener(Event.COMPLETE, doParseCSS);
263 private function doParseCSS(e:Event):void {
264 parseCSS(e.target.css);
267 private function parseCSS(str:String):void {
270 if (evals.length==0) { redrawCallback(); }
275 /// ------------------------------------------------------------------------------------------------
276 /** Load all images referenced in the RuleSet (for example, icons or bitmap fills). */
278 private function loadImages():void {
279 ImageBank.getInstance().addEventListener(ImageBank.IMAGES_LOADED,doIconCallback);
281 for each (var chooser:StyleChooser in choosers) {
282 for each (var style:Style in chooser.styles) {
283 if (style is PointStyle && PointStyle(style).icon_image ) { filename=PointStyle(style).icon_image; }
284 else if (style is ShapeStyle && ShapeStyle(style).fill_image ) { filename=ShapeStyle(style).fill_image; }
285 else if (style is ShieldStyle && ShieldStyle(style).shield_image) { filename=ShieldStyle(style).shield_image; }
288 if (filename!='square' && filename!='circle')
289 ImageBank.getInstance().loadImage(filename);
294 private function doIconCallback(e:Event):void {
298 // ------------------------------------------------------------------------------------------------
301 /** Parse a MapCSS stylesheet into a set of StyleChoosers. The parser is regular expression-based
302 and runs sequentially through the file from start to end. */
303 public function parse(css:String):void {
304 var previous:uint=0; // what was the previous CSS word?
305 var sc:StyleChooser=new StyleChooser(); // currently being assembled
306 choosers=new Array();
309 var o:Object=new Object();
310 while (css.length>0) {
313 if ((o=COMMENT.exec(css))) {
314 css=css.replace(COMMENT,'');
316 // Whitespace (probably only at beginning of file)
317 } else if ((o=WHITESPACE.exec(css))) {
318 css=css.replace(WHITESPACE,'');
320 // Class - .motorway, .builtup, :hover
321 } else if ((o=CLASS.exec(css))) {
322 if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
324 css=css.replace(CLASS,'');
325 sc.currentChain.addConditionToLast(new Condition('set',o[1]));
328 // Not class - !.motorway, !.builtup, !:hover
329 } else if ((o=NOT_CLASS.exec(css))) {
330 if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
332 css=css.replace(NOT_CLASS,'');
333 sc.currentChain.addConditionToLast(new Condition('unset',o[1]));
337 } else if ((o=ZOOM.exec(css))) {
338 if (previous!=oOBJECT && previous!=oCONDITION) { sc.currentChain.addRule(); }
340 css=css.replace(ZOOM,'');
341 var z:Array=parseZoom(o[1]);
342 sc.currentChain.addZoomToLast(z[0],z[1]);
343 sc.zoomSpecific=true;
346 // Grouping - just a comma
347 } else if ((o=GROUP.exec(css))) {
348 css=css.replace(GROUP,'');
352 // Condition - [highway=primary]
353 } else if ((o=CONDITION.exec(css))) {
354 if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
355 if (previous!=oOBJECT && previous!=oZOOM && previous!=oCONDITION) { sc.currentChain.addRule(); }
356 css=css.replace(CONDITION,'');
357 sc.currentChain.addConditionToLast(parseCondition(o[1]) as Condition);
360 // Object - way, node, relation
361 } else if ((o=OBJECT.exec(css))) {
362 if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
364 css=css.replace(OBJECT,'');
365 sc.currentChain.addRule(o[1]);
368 // Subpart - ::centreline
369 } else if ((o=SUBPART.exec(css))) {
370 if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
371 css=css.replace(SUBPART,'');
372 sc.currentChain.setSubpart(o[1]);
375 // Declaration - {...}
376 } else if ((o=DECLARATION.exec(css))) {
377 css=css.replace(DECLARATION,'');
378 sc.addStyles(parseDeclaration(o[1]));
379 previous=oDECLARATION;
382 } else if ((o=UNKNOWN.exec(css))) {
383 css=css.replace(UNKNOWN,'');
384 trace("unknown: "+o[1]);
385 // ** do some debugging with o[1]
388 trace("choked on "+css);
392 if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
395 private function saveChooser(sc:StyleChooser):void {
399 private function saveEval(expr:String):Eval {
401 var e:Eval=new Eval(expr);
402 e.addEventListener("swf_loaded",evalLoaded, false, 0, true);
407 private function evalLoaded(e:Event):void {
409 if (evalsToLoad==0) { redrawCallback(); }
412 // Parse declaration string into list of styles
414 private function parseDeclaration(s:String):Array {
416 var t:Object=new Object();
417 var o:Object=new Object();
418 var a:String, k:String, v:*;
421 var ss:ShapeStyle =new ShapeStyle() ;
422 var ps:PointStyle =new PointStyle() ;
423 var ts:TextStyle =new TextStyle() ;
424 var hs:ShieldStyle=new ShieldStyle();
425 var xs:InstructionStyle=new InstructionStyle();
427 for each (a in s.split(';')) {
428 if ((o=ASSIGNMENT_EVAL.exec(a))) { t[o[1].replace(DASH,'_')]=saveEval(o[2]); }
429 else if ((o=ASSIGNMENT.exec(a))) { t[o[1].replace(DASH,'_')]=o[2]; }
430 else if ((o=SET_TAG_EVAL.exec(a))) { xs.addSetTag(o[1],saveEval(o[2])); }
431 else if ((o=SET_TAG.exec(a))) { xs.addSetTag(o[1],o[2]); }
432 else if ((o=SET_TAG_TRUE.exec(a))) { xs.addSetTag(o[1],true); }
433 else if ((o=EXIT.exec(a))) { xs.setPropertyFromString('breaker',true); }
438 if (t['z_index']) { sub=Number(t['z_index']); delete t['z_index']; }
439 ss.sublayer=ps.sublayer=ts.sublayer=hs.sublayer=sub;
442 // Find "interactive" property - it's true unless explicitly set false.
443 var inter:Boolean=true;
444 if (t['interactive']) { inter=t['interactive'].match(FALSE) ? false : true; delete t['interactive']; }
445 ss.interactive=ps.interactive=ts.interactive=hs.interactive=xs.interactive=inter;
447 // Munge special values
448 if (t['font_weight'] ) { t['font_bold' ] = t['font_weight' ].match(BOLD ) ? true : false; delete t['font_weight']; }
449 if (t['font_style'] ) { t['font_italic'] = t['font_style' ].match(ITALIC) ? true : false; delete t['font_style']; }
450 if (t['text_decoration']) { t['font_underline'] = t['text_decoration'].match(UNDERLINE) ? true : false; delete t['text_decoration']; }
451 if (t['text_position'] ) { t['text_center'] = t['text_position' ].match(CENTER) ? true : false; delete t['text_position']; }
452 if (t['text_transform']) {
453 // ** needs other transformations, e.g. lower-case, sentence-case
454 if (t['text_transform'].match(CAPS)) { t['font_caps']=true; } else { t['font_caps']=false; }
455 delete t['text_transform'];
458 // ** Do compound settings (e.g. line: 5px dotted blue;)
460 // Assign each property to the appropriate style
463 // ** also do units, e.g. px/pt/m
464 if (a.match(COLOR)) { v = parseCSSColor(t[a]); }
468 if (ss.hasOwnProperty(a)) { ss.setPropertyFromString(a,v); }
469 else if (ps.hasOwnProperty(a)) { ps.setPropertyFromString(a,v); }
470 else if (ts.hasOwnProperty(a)) { ts.setPropertyFromString(a,v); }
471 else if (hs.hasOwnProperty(a)) { hs.setPropertyFromString(a,v); }
474 // Add each style to list
475 if (ss.edited) { styles.push(ss); }
476 if (ps.edited) { styles.push(ps); }
477 if (ts.edited) { styles.push(ts); }
478 if (hs.edited) { styles.push(hs); }
479 if (xs.edited) { styles.push(xs); }
483 private function parseZoom(s:String):Array {
484 var o:Object=new Object();
485 if ((o=ZOOM_MINMAX.exec(s))) { return [o[1],o[2]]; }
486 else if ((o=ZOOM_MIN.exec(s))) { return [o[1],maxscale]; }
487 else if ((o=ZOOM_MAX.exec(s))) { return [minscale,o[1]]; }
488 else if ((o=ZOOM_SINGLE.exec(s))) { return [o[1],o[1]]; }
492 private function parseCondition(s:String):Object {
493 var o:Object=new Object();
494 if ((o=CONDITION_TRUE.exec(s))) { return new Condition('true' ,o[1]); }
495 else if ((o=CONDITION_FALSE.exec(s))) { return new Condition('false',o[1]); }
496 else if ((o=CONDITION_SET.exec(s))) { return new Condition('set' ,o[1]); }
497 else if ((o=CONDITION_UNSET.exec(s))) { return new Condition('unset',o[1]); }
498 else if ((o=CONDITION_NE.exec(s))) { return new Condition('ne' ,o[1],o[2]); }
499 else if ((o=CONDITION_GT.exec(s))) { return new Condition('>' ,o[1],o[2]); }
500 else if ((o=CONDITION_GE.exec(s))) { return new Condition('>=' ,o[1],o[2]); }
501 else if ((o=CONDITION_LT.exec(s))) { return new Condition('<' ,o[1],o[2]); }
502 else if ((o=CONDITION_LE.exec(s))) { return new Condition('<=' ,o[1],o[2]); }
503 else if ((o=CONDITION_REGEX.exec(s))) { return new Condition('regex',o[1],o[2]); }
504 else if ((o=CONDITION_EQ.exec(s))) { return new Condition('eq' ,o[1],o[2]); }
508 /** Convert a colour string from CSS colour name ("blue"), short hex ("#abc") or long hex ("#a0b0c0"),
513 public static function parseCSSColor(colorStr:String):uint {
514 colorStr = colorStr.toLowerCase();
515 if (CSSCOLORS[colorStr]) {
516 return CSSCOLORS[colorStr];
518 var match:Object = HEX.exec(colorStr);
520 if ( match[1].length == 3) {
521 // repeat digits. #abc => 0xaabbcc
522 return Number("0x"+match[1].charAt(0)+match[1].charAt(0)+
523 match[1].charAt(1)+match[1].charAt(1)+
524 match[1].charAt(2)+match[1].charAt(2));
525 } else if ( match[1].length == 6) {
526 return Number("0x"+match[1]);
528 return Number("0x000000"); //as good as any