1 package net.systemeD.halcyon.styleparser {
5 import net.systemeD.halcyon.ExtendedLoader;
6 import net.systemeD.halcyon.ExtendedURLLoader;
7 import net.systemeD.halcyon.connection.Entity;
9 import net.systemeD.halcyon.connection.*;
10 import net.systemeD.halcyon.Globals;
11 // import bustin.dev.Inspector;
13 public class RuleSet {
15 public var loaded:Boolean=false; // has it loaded yet?
16 public var images:Object=new Object(); // loaded images
17 public var imageWidths:Object=new Object(); // width of each bitmap image
18 private var redrawCallback:Function=null; // function to call when CSS loaded
19 private var iconCallback:Function=null; // function to call when all icons loaded
20 private var iconsToLoad:uint=0; // number of icons left to load (fire iconCallback when ==0)
21 private var evalsToLoad:uint=0; // number of evals left to load (fire redrawCallback when ==0)
23 private var minscale:uint;
24 private var maxscale:uint;
25 public var choosers:Array;
26 public var evals:Array;
28 private static const WHITESPACE:RegExp =/^ \s+ /sx;
29 private static const COMMENT:RegExp =/^ \/\* .+? \*\/ \s* /sx; /* */
30 private static const CLASS:RegExp =/^ ([\.:]\w+) \s* /sx;
31 private static const NOT_CLASS:RegExp =/^ !([\.:]\w+) \s* /sx;
32 private static const ZOOM:RegExp =/^ \| \s* z([\d\-]+) \s* /isx;
33 private static const GROUP:RegExp =/^ , \s* /isx;
34 private static const CONDITION:RegExp =/^ \[(.+?)\] \s* /sx;
35 private static const OBJECT:RegExp =/^ (\w+) \s* /sx;
36 private static const DECLARATION:RegExp =/^ \{(.+?)\} \s* /sx;
37 private static const UNKNOWN:RegExp =/^ (\S+) \s* /sx;
39 private static const ZOOM_MINMAX:RegExp =/^ (\d+)\-(\d+) $/sx;
40 private static const ZOOM_MIN:RegExp =/^ (\d+)\- $/sx;
41 private static const ZOOM_MAX:RegExp =/^ \-(\d+) $/sx;
42 private static const ZOOM_SINGLE:RegExp =/^ (\d+) $/sx;
44 private static const CONDITION_TRUE:RegExp =/^ \s* ([:\w]+) \s* = \s* yes \s* $/isx;
45 private static const CONDITION_FALSE:RegExp =/^ \s* ([:\w]+) \s* = \s* no \s* $/isx;
46 private static const CONDITION_SET:RegExp =/^ \s* ([:\w]+) \s* $/sx;
47 private static const CONDITION_UNSET:RegExp =/^ \s* !([:\w]+) \s* $/sx;
48 private static const CONDITION_EQ:RegExp =/^ \s* ([:\w]+) \s* = \s* (.+) \s* $/sx;
49 private static const CONDITION_NE:RegExp =/^ \s* ([:\w]+) \s* != \s* (.+) \s* $/sx;
50 private static const CONDITION_GT:RegExp =/^ \s* ([:\w]+) \s* > \s* (.+) \s* $/sx;
51 private static const CONDITION_GE:RegExp =/^ \s* ([:\w]+) \s* >= \s* (.+) \s* $/sx;
52 private static const CONDITION_LT:RegExp =/^ \s* ([:\w]+) \s* < \s* (.+) \s* $/sx;
53 private static const CONDITION_LE:RegExp =/^ \s* ([:\w]+) \s* <= \s* (.+) \s* $/sx;
54 private static const CONDITION_REGEX:RegExp =/^ \s* ([:\w]+) \s* =~\/ \s* (.+) \/ \s* $/sx;
56 private static const ASSIGNMENT_EVAL:RegExp =/^ \s* (\S+) \s* \: \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
57 private static const ASSIGNMENT:RegExp =/^ \s* (\S+) \s* \: \s* (.+?) \s* $/sx;
58 private static const SET_TAG_EVAL:RegExp =/^ \s* set \s+(\S+)\s* = \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
59 private static const SET_TAG:RegExp =/^ \s* set \s+(\S+)\s* = \s* (.+?) \s* $/isx;
60 private static const SET_TAG_TRUE:RegExp =/^ \s* set \s+(\S+)\s* $/isx;
61 private static const EXIT:RegExp =/^ \s* exit \s* $/isx;
63 private static const oZOOM:uint=2;
64 private static const oGROUP:uint=3;
65 private static const oCONDITION:uint=4;
66 private static const oOBJECT:uint=5;
67 private static const oDECLARATION:uint=6;
69 private static const DASH:RegExp=/\-/g;
70 private static const COLOR:RegExp=/color$/;
71 private static const BOLD:RegExp=/^bold$/i;
72 private static const ITALIC:RegExp=/^italic|oblique$/i;
73 private static const UNDERLINE:RegExp=/^underline$/i;
74 private static const CAPS:RegExp=/^uppercase$/i;
75 private static const CENTER:RegExp=/^center$/i;
77 private static const HEX:RegExp=/^#([0-9a-f]+)$/i;
78 private static const CSSCOLORS:Object = {
80 antiquewhite:0xfaebd7,
87 blanchedalmond:0xffebcd,
96 cornflowerblue:0x6495ed,
102 darkgoldenrod:0xb8860b,
106 darkmagenta:0x8b008b,
107 darkolivegreen:0x556b2f,
112 darkseagreen:0x8fbc8f,
113 darkslateblue:0x483d8b,
114 darkslategray:0x2f4f4f,
115 darkturquoise:0x00ced1,
118 deepskyblue:0x00bfff,
122 floralwhite:0xfffaf0,
123 forestgreen:0x228b22,
131 greenyellow:0xadff2f,
139 lavenderblush:0xfff0f5,
141 lemonchiffon:0xfffacd,
145 lightgoldenrodyellow:0xfafad2,
149 lightsalmon:0xffa07a,
150 lightseagreen:0x20b2aa,
151 lightskyblue:0x87cefa,
152 lightslategray:0x778899,
153 lightsteelblue:0xb0c4de,
154 lightyellow:0xffffe0,
160 mediumaquamarine:0x66cdaa,
162 mediumorchid:0xba55d3,
163 mediumpurple:0x9370d8,
164 mediumseagreen:0x3cb371,
165 mediumslateblue:0x7b68ee,
166 mediumspringgreen:0x00fa9a,
167 mediumturquoise:0x48d1cc,
168 mediumvioletred:0xc71585,
169 midnightblue:0x191970,
173 navajowhite:0xffdead,
181 palegoldenrod:0xeee8aa,
183 paleturquoise:0xafeeee,
184 palevioletred:0xd87093,
195 saddlebrown:0x8b4513,
206 springgreen:0x00ff7f,
218 yellowgreen:0x9acd32 };
220 public function RuleSet(mins:uint,maxs:uint,redrawCall:Function=null,iconLoadedCallback:Function=null):void {
223 redrawCallback = redrawCall;
224 iconCallback = iconLoadedCallback;
227 // Get styles for an object
229 public function getStyles(obj:Entity,tags:Object):StyleList {
230 var sl:StyleList=new StyleList();
231 for each (var sc:StyleChooser in choosers) {
232 sc.updateStyles(obj,tags,sl,imageWidths);
237 // ---------------------------------------------------------------------------------------------------------
238 // Loading stylesheet
240 public function loadFromCSS(str:String):void {
241 if (str.match(/[\s\n\r\t]/)!=null) { parseCSS(str); loaded=true; redrawCallback(); return; }
243 var request:URLRequest=new URLRequest(str);
244 var loader:URLLoader=new URLLoader();
246 request.method=URLRequestMethod.GET;
247 loader.dataFormat = URLLoaderDataFormat.TEXT;
248 loader.addEventListener(Event.COMPLETE, doParseCSS);
249 loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
250 loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
251 loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
252 loader.load(request);
255 private function doParseCSS(e:Event):void {
256 parseCSS(e.target.data);
259 private function parseCSS(str:String):void {
262 if (evals.length==0) { redrawCallback(); }
267 // ------------------------------------------------------------------------------------------------
268 // Load all referenced images
269 // ** will duplicate if referenced twice, shouldn't
271 public function loadImages():void {
273 for each (var chooser:StyleChooser in choosers) {
274 for each (var style:Style in chooser.styles) {
275 if (style is PointStyle && PointStyle(style).icon_image ) { filename=PointStyle(style).icon_image; }
276 else if (style is ShapeStyle && ShapeStyle(style).fill_image ) { filename=ShapeStyle(style).fill_image; }
277 else if (style is ShieldStyle && ShieldStyle(style).shield_image) { filename=ShieldStyle(style).shield_image; }
279 if (filename=='square' || filename=='circle') { continue; }
282 var request:URLRequest=new URLRequest(filename);
283 var loader:ExtendedURLLoader=new ExtendedURLLoader();
284 loader.dataFormat=URLLoaderDataFormat.BINARY;
285 loader.info['filename']=filename;
286 loader.addEventListener(Event.COMPLETE, loadedImage, false, 0, true);
287 loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler, false, 0, true);
288 loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler, false, 0, true);
289 loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler, false, 0, true);
290 loader.load(request);
297 private function loadedImage(event:Event):void {
298 var fn:String=event.target.info['filename'];
299 images[fn]=event.target.data;
301 var loader:ExtendedLoader = new ExtendedLoader();
302 loader.info['filename']=fn;
303 loader.contentLoaderInfo.addEventListener(Event.COMPLETE, measureWidth);
304 loader.loadBytes(images[fn]);
307 private function measureWidth(event:Event):void {
308 var fn:String=event.target.loader.info['filename'];
309 imageWidths[fn]=event.target.width;
310 // ** do we need to explicitly remove the loader object now?
313 if (iconsToLoad==0 && iconCallback!=null) { iconCallback(); }
316 private function httpStatusHandler( event:HTTPStatusEvent ):void { }
317 private function securityErrorHandler( event:SecurityErrorEvent ):void { Globals.vars.root.addDebug("securityerrorevent"); }
318 private function ioErrorHandler( event:IOErrorEvent ):void { Globals.vars.root.addDebug("ioerrorevent"); }
320 // ------------------------------------------------------------------------------------------------
323 public function parse(css:String):void {
324 var previous:uint=0; // what was the previous CSS word?
325 var sc:StyleChooser=new StyleChooser(); // currently being assembled
326 choosers=new Array();
329 var o:Object=new Object();
330 while (css.length>0) {
333 if ((o=COMMENT.exec(css))) {
334 css=css.replace(COMMENT,'');
336 // Whitespace (probably only at beginning of file)
337 } else if ((o=WHITESPACE.exec(css))) {
338 css=css.replace(WHITESPACE,'');
340 // Class - .motorway, .builtup, :hover
341 } else if ((o=CLASS.exec(css))) {
342 if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
344 css=css.replace(CLASS,'');
345 sc.addCondition(new Condition('set',o[1]));
348 // Not class - !.motorway, !.builtup, !:hover
349 } else if ((o=NOT_CLASS.exec(css))) {
350 if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
352 css=css.replace(NOT_CLASS,'');
353 sc.addCondition(new Condition('unset',o[1]));
357 } else if ((o=ZOOM.exec(css))) {
358 if (previous!=oOBJECT && previous!=oCONDITION) { sc.newObject(); }
360 css=css.replace(ZOOM,'');
361 var z:Array=parseZoom(o[1]);
362 sc.addZoom(z[0],z[1]);
365 // Grouping - just a comma
366 } else if ((o=GROUP.exec(css))) {
367 css=css.replace(GROUP,'');
371 // Condition - [highway=primary]
372 } else if ((o=CONDITION.exec(css))) {
373 if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
374 if (previous!=oOBJECT && previous!=oZOOM && previous!=oCONDITION) { sc.newObject(); }
375 css=css.replace(CONDITION,'');
376 sc.addCondition(parseCondition(o[1]) as Condition);
379 // Object - way, node, relation
380 } else if ((o=OBJECT.exec(css))) {
381 if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
383 css=css.replace(OBJECT,'');
387 // Declaration - {...}
388 } else if ((o=DECLARATION.exec(css))) {
389 css=css.replace(DECLARATION,'');
390 sc.addStyles(parseDeclaration(o[1]));
391 previous=oDECLARATION;
394 } else if ((o=UNKNOWN.exec(css))) {
395 css=css.replace(UNKNOWN,'');
396 Globals.vars.root.addDebug("unknown: "+o[1]);
397 // ** do some debugging with o[1]
400 Globals.vars.root.addDebug("choked on "+css);
404 if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
407 private function saveChooser(sc:StyleChooser):void {
411 private function saveEval(expr:String):Eval {
413 var e:Eval=new Eval(expr);
414 e.addEventListener("swf_loaded",evalLoaded);
419 private function evalLoaded(e:Event):void {
421 if (evalsToLoad==0) { redrawCallback(); }
424 // Parse declaration string into list of styles
426 private function parseDeclaration(s:String):Array {
428 var t:Object=new Object();
429 var o:Object=new Object();
430 var a:String, k:String;
433 var ss:ShapeStyle =new ShapeStyle() ;
434 var ps:PointStyle =new PointStyle() ;
435 var ts:TextStyle =new TextStyle() ;
436 var hs:ShieldStyle=new ShieldStyle();
437 var xs:InstructionStyle=new InstructionStyle();
439 for each (a in s.split(';')) {
440 if ((o=ASSIGNMENT_EVAL.exec(a))) { t[o[1].replace(DASH,'_')]=saveEval(o[2]); }
441 else if ((o=ASSIGNMENT.exec(a))) { t[o[1].replace(DASH,'_')]=o[2]; }
442 else if ((o=SET_TAG_EVAL.exec(a))) { xs.addSetTag(o[1],saveEval(o[2])); }
443 else if ((o=SET_TAG.exec(a))) { xs.addSetTag(o[1],o[2]); }
444 else if ((o=SET_TAG_TRUE.exec(a))) { xs.addSetTag(o[1],true); }
445 else if ((o=EXIT.exec(a))) { xs.setPropertyFromString('breaker',true); }
450 if (t['z_index']) { sub=Number(t['z_index']); delete t['z_index']; }
451 ss.sublayer=ps.sublayer=ts.sublayer=hs.sublayer=sub;
454 // Munge special values
455 if (t['font_weight'] ) { t['font_bold' ] = t['font_weight' ].match(BOLD ) ? true : false; delete t['font_weight']; }
456 if (t['font_style'] ) { t['font_italic'] = t['font_style' ].match(ITALIC) ? true : false; delete t['font_style']; }
457 if (t['text_decoration']) { t['font_underline'] = t['text_decoration'].match(UNDERLINE) ? true : false; delete t['text_decoration']; }
458 if (t['text_position'] ) { t['text_center'] = t['text_position' ].match(CENTER) ? true : false; delete t['text_position']; }
459 if (t['text_transform']) {
460 // ** needs other transformations, e.g. lower-case, sentence-case
461 if (t['text_transform'].match(CAPS)) { t['font_caps']=true; } else { t['font_caps']=false; }
462 delete t['text_transform'];
465 // ** Do compound settings (e.g. line: 5px dotted blue;)
467 // Assign each property to the appropriate style
470 // ** also do units, e.g. px/pt
471 if (a.match(COLOR)) {
472 t[a] = parseCSSColor(t[a]);
476 if (ss.hasOwnProperty(a)) { ss.setPropertyFromString(a,t[a]); }
477 else if (ps.hasOwnProperty(a)) { ps.setPropertyFromString(a,t[a]); }
478 else if (ts.hasOwnProperty(a)) { ts.setPropertyFromString(a,t[a]); }
479 else if (hs.hasOwnProperty(a)) { hs.setPropertyFromString(a,t[a]); }
482 // Add each style to list
483 if (ss.edited) { styles.push(ss); }
484 if (ps.edited) { styles.push(ps); }
485 if (ts.edited) { styles.push(ts); }
486 if (hs.edited) { styles.push(hs); }
487 if (xs.edited) { styles.push(xs); }
491 private function parseZoom(s:String):Array {
492 var o:Object=new Object();
493 if ((o=ZOOM_MINMAX.exec(s))) { return [o[1],o[2]]; }
494 else if ((o=ZOOM_MIN.exec(s))) { return [o[1],maxscale]; }
495 else if ((o=ZOOM_MAX.exec(s))) { return [minscale,o[1]]; }
496 else if ((o=ZOOM_SINGLE.exec(s))) { return [o[1],o[1]]; }
500 private function parseCondition(s:String):Object {
501 var o:Object=new Object();
502 if ((o=CONDITION_TRUE.exec(s))) { return new Condition('true' ,o[1]); }
503 else if ((o=CONDITION_FALSE.exec(s))) { return new Condition('false',o[1]); }
504 else if ((o=CONDITION_SET.exec(s))) { return new Condition('set' ,o[1]); }
505 else if ((o=CONDITION_UNSET.exec(s))) { return new Condition('unset',o[1]); }
506 else if ((o=CONDITION_NE.exec(s))) { return new Condition('ne' ,o[1],o[2]); }
507 else if ((o=CONDITION_GT.exec(s))) { return new Condition('>' ,o[1],o[2]); }
508 else if ((o=CONDITION_GE.exec(s))) { return new Condition('>=' ,o[1],o[2]); }
509 else if ((o=CONDITION_LT.exec(s))) { return new Condition('<' ,o[1],o[2]); }
510 else if ((o=CONDITION_LE.exec(s))) { return new Condition('<=' ,o[1],o[2]); }
511 else if ((o=CONDITION_REGEX.exec(s))) { return new Condition('regex',o[1],o[2]); }
512 else if ((o=CONDITION_EQ.exec(s))) { return new Condition('eq' ,o[1],o[2]); }
516 public static function parseCSSColor(colorStr:String):uint {
517 colorStr = colorStr.toLowerCase();
518 if (CSSCOLORS[colorStr])
519 return CSSCOLORS[colorStr];
521 var match:Object = HEX.exec(colorStr);
523 return Number("0x"+match[1]);