Merge branch 'master' into history
[potlatch2.git] / net / systemeD / halcyon / styleparser / RuleSet.as
1 package net.systemeD.halcyon.styleparser {
2
3         import flash.events.*;
4         import flash.net.*;
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;
10
11     import net.systemeD.halcyon.connection.*;
12
13     /**
14     * A complete stylesheet, as loaded from a MapCSS file. It contains all selectors, declarations,
15     * and embedded images.
16     *
17     * <p>The RuleSet class has two principal methods: getStyles, which calculates the styles that apply
18     * to an entity (returned as a StyleList); and parse, which parses a MapCSS stylesheet into
19     * a complete RuleSet.</p>
20     */
21
22         public class RuleSet {
23
24                 public var loaded:Boolean=false;                        // is the RuleSet fully loaded and available for use?
25                 private var redrawCallback:Function=null;       // function to call when CSS loaded
26                 private var iconCallback:Function=null;         // function to call when all icons loaded
27                 private var iconsToLoad:uint=0;                         // number of icons left to load (fire iconCallback when ==0)
28                 private var evalsToLoad:uint=0;                         // number of evals left to load (fire redrawCallback when ==0)
29
30                 private var minscale:uint;
31                 private var maxscale:uint;
32                 public var choosers:Array;
33                 public var evals:Array;
34
35                 private static const WHITESPACE:RegExp  =/^ \s+ /sx;
36                 private static const COMMENT:RegExp             =/^ \/\* .+? \*\/ \s* /sx;      /* */
37                 private static const CLASS:RegExp               =/^ ([\.:]\w+) \s* /sx;
38                 private static const NOT_CLASS:RegExp   =/^ !([\.:]\w+) \s* /sx;
39                 private static const ZOOM:RegExp                =/^ \| \s* z([\d\-]+) \s* /isx;
40                 private static const GROUP:RegExp               =/^ , \s* /isx;
41                 private static const CONDITION:RegExp   =/^ \[(.+?)\] \s* /sx;
42                 private static const OBJECT:RegExp              =/^ (\w+) \s* /sx;
43                 private static const DECLARATION:RegExp =/^ \{(.+?)\} \s* /sx;
44                 private static const SUBPART:RegExp             =/^ ::(\w+) \s* /sx;
45                 private static const UNKNOWN:RegExp             =/^ (\S+) \s* /sx;
46
47                 private static const ZOOM_MINMAX:RegExp =/^ (\d+)\-(\d+) $/sx;
48                 private static const ZOOM_MIN:RegExp    =/^ (\d+)\-      $/sx;
49                 private static const ZOOM_MAX:RegExp    =/^      \-(\d+) $/sx;
50                 private static const ZOOM_SINGLE:RegExp =/^        (\d+) $/sx;
51
52                 private static const CONDITION_TRUE:RegExp      =/^ \s* ([:\w]+) \s* = \s* yes \s*  $/isx;
53                 private static const CONDITION_FALSE:RegExp     =/^ \s* ([:\w]+) \s* = \s* no  \s*  $/isx;
54                 private static const CONDITION_SET:RegExp       =/^ \s* ([:\w]+) \s* $/sx;
55                 private static const CONDITION_UNSET:RegExp     =/^ \s* !([:\w]+) \s* $/sx;
56                 private static const CONDITION_EQ:RegExp        =/^ \s* ([:\w]+) \s* =  \s* (.+) \s* $/sx;
57                 private static const CONDITION_NE:RegExp        =/^ \s* ([:\w]+) \s* != \s* (.+) \s* $/sx;
58                 private static const CONDITION_GT:RegExp        =/^ \s* ([:\w]+) \s* >  \s* (.+) \s* $/sx;
59                 private static const CONDITION_GE:RegExp        =/^ \s* ([:\w]+) \s* >= \s* (.+) \s* $/sx;
60                 private static const CONDITION_LT:RegExp        =/^ \s* ([:\w]+) \s* <  \s* (.+) \s* $/sx;
61                 private static const CONDITION_LE:RegExp        =/^ \s* ([:\w]+) \s* <= \s* (.+) \s* $/sx;
62                 private static const CONDITION_REGEX:RegExp     =/^ \s* ([:\w]+) \s* =~\/ \s* (.+) \/ \s* $/sx;
63
64                 private static const ASSIGNMENT_EVAL:RegExp             =/^ \s* (\S+) \s* \:      \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
65                 private static const ASSIGNMENT_TAGVALUE:RegExp =/^ \s* (\S+) \s* \:      \s* tag  \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
66                 private static const ASSIGNMENT:RegExp                  =/^ \s* (\S+) \s* \:      \s*          (.+?) \s*                   $/sx;
67                 private static const SET_TAG_EVAL:RegExp                =/^ \s* set \s+(\S+)\s* = \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
68                 private static const SET_TAG_TAGVALUE:RegExp    =/^ \s* set \s+(\S+)\s* = \s* tag  \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
69                 private static const SET_TAG:RegExp                             =/^ \s* set \s+(\S+)\s* = \s*          (.+?) \s*                   $/isx;
70                 private static const SET_TAG_TRUE:RegExp                =/^ \s* set \s+(\S+)\s* $/isx;
71                 private static const DELETE_TAG:RegExp                  =/^ \s* delete \s+(\S+)\s* $/isx;
72                 private static const EXIT:RegExp                                =/^ \s* exit \s* $/isx;
73
74                 private static const oZOOM:uint=2;
75                 private static const oGROUP:uint=3;
76                 private static const oCONDITION:uint=4;
77                 private static const oOBJECT:uint=5;
78                 private static const oDECLARATION:uint=6;
79                 private static const oSUBPART:uint=7;
80
81                 private static const DASH:RegExp=/\-/g;
82                 private static const COLOR:RegExp=/color$/;
83                 private static const BOLD:RegExp=/^bold$/i;
84                 private static const ITALIC:RegExp=/^italic|oblique$/i;
85                 private static const UNDERLINE:RegExp=/^underline$/i;
86                 private static const CAPS:RegExp=/^uppercase$/i;
87                 private static const CENTER:RegExp=/^center$/i;
88                 private static const FALSE:RegExp=/^(no|false|0)$/i;
89
90                 private static const HEX:RegExp=/^#([0-9a-f]+)$/i;
91                 private static const CSSCOLORS:Object = {
92                         aliceblue:0xf0f8ff,
93                         antiquewhite:0xfaebd7,
94                         aqua:0x00ffff,
95                         aquamarine:0x7fffd4,
96                         azure:0xf0ffff,
97                         beige:0xf5f5dc,
98                         bisque:0xffe4c4,
99                         black:0x000000,
100                         blanchedalmond:0xffebcd,
101                         blue:0x0000ff,
102                         blueviolet:0x8a2be2,
103                         brown:0xa52a2a,
104                         burlywood:0xdeb887,
105                         cadetblue:0x5f9ea0,
106                         chartreuse:0x7fff00,
107                         chocolate:0xd2691e,
108                         coral:0xff7f50,
109                         cornflowerblue:0x6495ed,
110                         cornsilk:0xfff8dc,
111                         crimson:0xdc143c,
112                         cyan:0x00ffff,
113                         darkblue:0x00008b,
114                         darkcyan:0x008b8b,
115                         darkgoldenrod:0xb8860b,
116                         darkgray:0xa9a9a9,
117                         darkgreen:0x006400,
118                         darkkhaki:0xbdb76b,
119                         darkmagenta:0x8b008b,
120                         darkolivegreen:0x556b2f,
121                         darkorange:0xff8c00,
122                         darkorchid:0x9932cc,
123                         darkred:0x8b0000,
124                         darksalmon:0xe9967a,
125                         darkseagreen:0x8fbc8f,
126                         darkslateblue:0x483d8b,
127                         darkslategray:0x2f4f4f,
128                         darkturquoise:0x00ced1,
129                         darkviolet:0x9400d3,
130                         deeppink:0xff1493,
131                         deepskyblue:0x00bfff,
132                         dimgray:0x696969,
133                         dodgerblue:0x1e90ff,
134                         firebrick:0xb22222,
135                         floralwhite:0xfffaf0,
136                         forestgreen:0x228b22,
137                         fuchsia:0xff00ff,
138                         gainsboro:0xdcdcdc,
139                         ghostwhite:0xf8f8ff,
140                         gold:0xffd700,
141                         goldenrod:0xdaa520,
142                         gray:0x808080,
143                         green:0x008000,
144                         greenyellow:0xadff2f,
145                         honeydew:0xf0fff0,
146                         hotpink:0xff69b4,
147                         indianred :0xcd5c5c,
148                         indigo :0x4b0082,
149                         ivory:0xfffff0,
150                         khaki:0xf0e68c,
151                         lavender:0xe6e6fa,
152                         lavenderblush:0xfff0f5,
153                         lawngreen:0x7cfc00,
154                         lemonchiffon:0xfffacd,
155                         lightblue:0xadd8e6,
156                         lightcoral:0xf08080,
157                         lightcyan:0xe0ffff,
158                         lightgoldenrodyellow:0xfafad2,
159                         lightgrey:0xd3d3d3,
160                         lightgreen:0x90ee90,
161                         lightpink:0xffb6c1,
162                         lightsalmon:0xffa07a,
163                         lightseagreen:0x20b2aa,
164                         lightskyblue:0x87cefa,
165                         lightslategray:0x778899,
166                         lightsteelblue:0xb0c4de,
167                         lightyellow:0xffffe0,
168                         lime:0x00ff00,
169                         limegreen:0x32cd32,
170                         linen:0xfaf0e6,
171                         magenta:0xff00ff,
172                         maroon:0x800000,
173                         mediumaquamarine:0x66cdaa,
174                         mediumblue:0x0000cd,
175                         mediumorchid:0xba55d3,
176                         mediumpurple:0x9370d8,
177                         mediumseagreen:0x3cb371,
178                         mediumslateblue:0x7b68ee,
179                         mediumspringgreen:0x00fa9a,
180                         mediumturquoise:0x48d1cc,
181                         mediumvioletred:0xc71585,
182                         midnightblue:0x191970,
183                         mintcream:0xf5fffa,
184                         mistyrose:0xffe4e1,
185                         moccasin:0xffe4b5,
186                         navajowhite:0xffdead,
187                         navy:0x000080,
188                         oldlace:0xfdf5e6,
189                         olive:0x808000,
190                         olivedrab:0x6b8e23,
191                         orange:0xffa500,
192                         orangered:0xff4500,
193                         orchid:0xda70d6,
194                         palegoldenrod:0xeee8aa,
195                         palegreen:0x98fb98,
196                         paleturquoise:0xafeeee,
197                         palevioletred:0xd87093,
198                         papayawhip:0xffefd5,
199                         peachpuff:0xffdab9,
200                         peru:0xcd853f,
201                         pink:0xffc0cb,
202                         plum:0xdda0dd,
203                         powderblue:0xb0e0e6,
204                         purple:0x800080,
205                         red:0xff0000,
206                         rosybrown:0xbc8f8f,
207                         royalblue:0x4169e1,
208                         saddlebrown:0x8b4513,
209                         salmon:0xfa8072,
210                         sandybrown:0xf4a460,
211                         seagreen:0x2e8b57,
212                         seashell:0xfff5ee,
213                         sienna:0xa0522d,
214                         silver:0xc0c0c0,
215                         skyblue:0x87ceeb,
216                         slateblue:0x6a5acd,
217                         slategray:0x708090,
218                         snow:0xfffafa,
219                         springgreen:0x00ff7f,
220                         steelblue:0x4682b4,
221                         tan:0xd2b48c,
222                         teal:0x008080,
223                         thistle:0xd8bfd8,
224                         tomato:0xff6347,
225                         turquoise:0x40e0d0,
226                         violet:0xee82ee,
227                         wheat:0xf5deb3,
228                         white:0xffffff,
229                         whitesmoke:0xf5f5f5,
230                         yellow:0xffff00,
231                         yellowgreen:0x9acd32 };
232
233                 /** Constructor */
234
235                 public function RuleSet(mins:uint,maxs:uint,redrawCall:Function=null,iconLoadedCallback:Function=null):void {
236                         minscale = mins;
237                         maxscale = maxs;
238                         redrawCallback = redrawCall;
239                         iconCallback = iconLoadedCallback;
240                 }
241
242                 /** Create a StyleList for an Entity, by creating a blank StyleList, then running each StyleChooser over it.
243                     Optionally, styleUntagged can be set to false, to abort (and return a blank StyleList) if the tag hash is empty.
244                         @see net.systemeD.halcyon.styleparser.StyleList */
245
246                 public function getStyles(obj:Entity, tags:Object, zoom:uint, styleUntagged:Boolean=true):StyleList {
247                         var sl:StyleList=new StyleList();
248                         var tagged:Boolean=styleUntagged;
249                         for (var k:String in tags) { tagged=true; break; }
250                         if (tagged) {
251                                 for each (var sc:StyleChooser in choosers) {
252                                         sc.updateStyles(obj,tags,sl,zoom);
253                                 }
254                         }
255                         return sl;
256                 }
257
258                 /** Run instruction styles only, for CSSTransform. */
259                 public function runInstructions(obj:Entity, tags:Object):Object {
260                         for each (var sc:StyleChooser in choosers) {
261                                 tags=sc.runInstructions(obj,tags);
262                         }
263                         return tags;
264                 }
265
266                 // ---------------------------------------------------------------------------------------------------------
267                 // Loading stylesheet
268
269         /** Load and then parse a MapCSS stylesheet. Usually you will supply a filename, but you can also pass 
270                         a complete stylesheet in the string parameter. (Any string containing space characters will be 
271                         assumed to be a stylesheet rather than a filename.) */
272
273                 public function loadFromCSS(str:String):void {
274                         if (str.match(/[\s\n\r\t]/)!=null) { parseCSS(str); loaded=true; redrawCallback(); return; }
275
276                         var cssLoader:NestedCSSLoader=new NestedCSSLoader();
277                         cssLoader.addEventListener(Event.COMPLETE, doParseCSS);
278                         cssLoader.load(str);
279                 }
280
281                 private function doParseCSS(e:Event):void {
282                         parseCSS(e.target.css);
283                 }
284
285                 private function parseCSS(str:String):void {
286                         parse(str);
287                         loaded=true;
288                         if (evals.length==0) { redrawCallback(); }
289                         loadImages();
290                 }
291
292
293                 /// ------------------------------------------------------------------------------------------------
294                 /** Load all images referenced in the RuleSet (for example, icons or bitmap fills). */
295                 
296                 private function loadImages():void {
297                         ImageBank.getInstance().addEventListener(ImageBank.IMAGES_LOADED,doIconCallback);
298                         var filename:String;
299                         for each (var chooser:StyleChooser in choosers) {
300                                 for each (var style:Style in chooser.styles) {
301                                         if      (style is PointStyle  && PointStyle(style).icon_image   ) { filename=PointStyle(style).icon_image; }
302                                         else if (style is ShapeStyle  && ShapeStyle(style).fill_image   ) { filename=ShapeStyle(style).fill_image; }
303                                         else if (style is ShieldStyle && ShieldStyle(style).shield_image) { filename=ShieldStyle(style).shield_image; }
304                                         else { continue; }
305
306                                         if (filename!='square' && filename!='circle')
307                                                 ImageBank.getInstance().loadImage(filename);
308                                 }
309                         }
310                 }
311                 
312                 private function doIconCallback(e:Event):void {
313                         iconCallback();
314                 }
315                 
316                 // ------------------------------------------------------------------------------------------------
317                 // Parse CSS
318
319                 /** Parse a MapCSS stylesheet into a set of StyleChoosers. The parser is regular expression-based 
320                         and runs sequentially through the file from start to end. */
321                 public function parse(css:String):void {
322                         var previous:uint=0;                                    // what was the previous CSS word?
323                         var sc:StyleChooser=new StyleChooser(); // currently being assembled
324                         choosers=new Array();
325                         evals=new Array();
326
327                         var o:Object=new Object();
328                         while (css.length>0) {
329
330                                 // CSS comment
331                                 if ((o=COMMENT.exec(css))) {
332                                         css=css.replace(COMMENT,'');
333
334                                 // Whitespace (probably only at beginning of file)
335                                 } else if ((o=WHITESPACE.exec(css))) {
336                                         css=css.replace(WHITESPACE,'');
337
338                                 // Class - .motorway, .builtup, :hover
339                                 } else if ((o=CLASS.exec(css))) {
340                                         if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
341
342                                         css=css.replace(CLASS,'');
343                                         sc.currentChain.addConditionToLast(new Condition('set',o[1]));
344                                         previous=oCONDITION;
345
346                                 // Not class - !.motorway, !.builtup, !:hover
347                                 } else if ((o=NOT_CLASS.exec(css))) {
348                                         if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
349
350                                         css=css.replace(NOT_CLASS,'');
351                                         sc.currentChain.addConditionToLast(new Condition('unset',o[1]));
352                                         previous=oCONDITION;
353
354                                 // Zoom
355                                 } else if ((o=ZOOM.exec(css))) {
356                                         if (previous!=oOBJECT && previous!=oCONDITION) { sc.currentChain.addRule(); }
357
358                                         css=css.replace(ZOOM,'');
359                                         var z:Array=parseZoom(o[1]);
360                                         sc.currentChain.addZoomToLast(z[0],z[1]);
361                                         sc.zoomSpecific=true;
362                                         previous=oZOOM;
363
364                                 // Grouping - just a comma
365                                 } else if ((o=GROUP.exec(css))) {
366                                         css=css.replace(GROUP,'');
367                                         sc.newRuleChain();
368                                         previous=oGROUP;
369
370                                 // Condition - [highway=primary]
371                                 } else if ((o=CONDITION.exec(css))) {
372                                         if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
373                                         if (previous!=oOBJECT && previous!=oZOOM && previous!=oCONDITION) { sc.currentChain.addRule(); }
374                                         css=css.replace(CONDITION,'');
375                                         sc.currentChain.addConditionToLast(parseCondition(o[1]) as Condition);
376                                         previous=oCONDITION;
377
378                                 // Object - way, node, relation
379                                 } else if ((o=OBJECT.exec(css))) {
380                                         if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
381
382                                         css=css.replace(OBJECT,'');
383                                         sc.currentChain.addRule(o[1]);
384                                         previous=oOBJECT;
385
386                                 // Subpart - ::centreline
387                                 } else if ((o=SUBPART.exec(css))) {
388                                         if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
389                                         css=css.replace(SUBPART,'');
390                                         sc.currentChain.setSubpart(o[1]);
391                                         previous=oSUBPART;
392
393                                 // Declaration - {...}
394                                 } else if ((o=DECLARATION.exec(css))) {
395                                         css=css.replace(DECLARATION,'');
396                                         sc.addStyles(parseDeclaration(o[1]));
397                                         previous=oDECLARATION;
398                                 
399                                 // Unknown pattern
400                                 } else if ((o=UNKNOWN.exec(css))) {
401                                         css=css.replace(UNKNOWN,'');
402                                         trace("unknown: "+o[1]);
403                                         // ** do some debugging with o[1]
404
405                                 } else {
406                                         trace("choked on "+css);
407                                         return;
408                                 }
409                         }
410                         if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
411                 }
412                 
413                 private function saveChooser(sc:StyleChooser):void {
414                         choosers.push(sc);
415                 };
416                 
417                 private function saveEval(expr:String):Eval {
418                         evalsToLoad++;
419                         var e:Eval=new Eval(expr);
420                         e.addEventListener("swf_loaded",evalLoaded, false, 0, true);
421                         evals.push(e);
422                         return e;
423                 }
424                 
425                 private function evalLoaded(e:Event):void {
426                         evalsToLoad--;
427                         if (evalsToLoad==0) { redrawCallback(); }
428                 }
429
430                 // Parse declaration string into list of styles
431
432                 private function parseDeclaration(s:String):Array {
433                         var styles:Array=[];
434                         var t:Object=new Object();
435                         var o:Object=new Object();
436                         var a:String, k:String, v:*;
437
438                         // Create styles\10
439                         var ss:ShapeStyle =new ShapeStyle() ;
440                         var ps:PointStyle =new PointStyle() ; 
441                         var ts:TextStyle  =new TextStyle()  ; 
442                         var hs:ShieldStyle=new ShieldStyle(); 
443                         var xs:InstructionStyle=new InstructionStyle(); 
444
445                         for each (a in s.split(';')) {
446                                 if      ((o=ASSIGNMENT_EVAL.exec(a)))           { t[o[1].replace(DASH,'_')]=saveEval(o[2]); }
447                                 else if ((o=ASSIGNMENT_TAGVALUE.exec(a)))       { t[o[1].replace(DASH,'_')]=new TagValue(o[2]); }
448                                 else if ((o=ASSIGNMENT.exec(a)))                        { t[o[1].replace(DASH,'_')]=o[2]; }
449                                 else if ((o=SET_TAG_EVAL.exec(a)))                      { xs.addSetTag(o[1],saveEval(o[2])); }
450                                 else if ((o=SET_TAG_TAGVALUE.exec(a)))          { xs.addSetTag(o[1],new TagValue(o[2])); }
451                                 else if ((o=SET_TAG.exec(a)))                           { xs.addSetTag(o[1],o[2]); }
452                                 else if ((o=SET_TAG_TRUE.exec(a)))                      { xs.addSetTag(o[1],true); }
453                                 else if ((o=DELETE_TAG.exec(a)))                        { xs.addSetTag(o[1],''); }
454                                 else if ((o=EXIT.exec(a))) { xs.setPropertyFromString('breaker',true); }
455                         }
456
457                         // Find sublayer
458                         var sub:uint=5;
459                         if (t['z_index']) { sub=Number(t['z_index']); delete t['z_index']; }
460                         ss.sublayer=ps.sublayer=ts.sublayer=hs.sublayer=sub;
461                         xs.sublayer=10;
462                         
463                         // Find "interactive" property - it's true unless explicitly set false.
464                         var inter:Boolean=true;
465                         if (t['interactive']) { inter=t['interactive'].match(FALSE) ? false : true; delete t['interactive']; }
466                         ss.interactive=ps.interactive=ts.interactive=hs.interactive=xs.interactive=inter;
467
468                         // Munge special values
469                         if (t['font_weight']    ) { t['font_bold'  ]    = t['font_weight'    ].match(BOLD  )    ? true : false; delete t['font_weight']; }
470                         if (t['font_style']     ) { t['font_italic']    = t['font_style'     ].match(ITALIC)    ? true : false; delete t['font_style']; }
471                         if (t['text_decoration']) { t['font_underline'] = t['text_decoration'].match(UNDERLINE) ? true : false; delete t['text_decoration']; }
472                         if (t['text_position']  ) { t['text_center']    = t['text_position'  ].match(CENTER)    ? true : false; delete t['text_position']; }
473                         if (t['text_transform']) {
474                                 // ** needs other transformations, e.g. lower-case, sentence-case
475                                 if (t['text_transform'].match(CAPS)) { t['font_caps']=true; } else { t['font_caps']=false; }
476                                 delete t['text_transform'];
477                         }
478
479                         // ** Do compound settings (e.g. line: 5px dotted blue;)
480
481                         // Assign each property to the appropriate style
482                         for (a in t) {
483                                 // Parse properties
484                                 // ** also do units, e.g. px/pt/m
485                                 if (a.match(COLOR)) { v = parseCSSColor(t[a]); }
486                                                else { v = t[a]; }
487                                 
488                                 // Set in styles
489                                 if      (ss.hasOwnProperty(a)) { ss.setPropertyFromString(a,v); }
490                                 else if (ps.hasOwnProperty(a)) { ps.setPropertyFromString(a,v); }
491                                 else if (ts.hasOwnProperty(a)) { ts.setPropertyFromString(a,v); }
492                                 else if (hs.hasOwnProperty(a)) { hs.setPropertyFromString(a,v); }
493                         }
494
495                         // Add each style to list
496                         if (ss.edited) { styles.push(ss); }
497                         if (ps.edited) { styles.push(ps); }
498                         if (ts.edited) { styles.push(ts); }
499                         if (hs.edited) { styles.push(hs); }
500                         if (xs.edited) { styles.push(xs); }
501                         return styles;
502                 }
503                 
504                 private function parseZoom(s:String):Array {
505                         var o:Object=new Object();
506                         if ((o=ZOOM_MINMAX.exec(s))) { return [o[1],o[2]]; }
507                         else if ((o=ZOOM_MIN.exec(s))) { return [o[1],maxscale]; }
508                         else if ((o=ZOOM_MAX.exec(s))) { return [minscale,o[1]]; }
509                         else if ((o=ZOOM_SINGLE.exec(s))) { return [o[1],o[1]]; }
510                         return null;
511                 }
512
513                 private function parseCondition(s:String):Object {
514                         var o:Object=new Object();
515                         if      ((o=CONDITION_TRUE.exec(s)))  { return new Condition('true'     ,o[1]); }
516                         else if ((o=CONDITION_FALSE.exec(s))) { return new Condition('false',o[1]); }
517                         else if ((o=CONDITION_SET.exec(s)))   { return new Condition('set'      ,o[1]); }
518                         else if ((o=CONDITION_UNSET.exec(s))) { return new Condition('unset',o[1]); }
519                         else if ((o=CONDITION_NE.exec(s)))    { return new Condition('ne'       ,o[1],o[2]); }
520                         else if ((o=CONDITION_GT.exec(s)))    { return new Condition('>'        ,o[1],o[2]); }
521                         else if ((o=CONDITION_GE.exec(s)))    { return new Condition('>='       ,o[1],o[2]); }
522                         else if ((o=CONDITION_LT.exec(s)))    { return new Condition('<'        ,o[1],o[2]); }
523                         else if ((o=CONDITION_LE.exec(s)))    { return new Condition('<='       ,o[1],o[2]); }
524                         else if ((o=CONDITION_REGEX.exec(s))) { return new Condition('regex',o[1],o[2]); }
525                         else if ((o=CONDITION_EQ.exec(s)))    { return new Condition('eq'       ,o[1],o[2]); }
526                         return null;
527                 }
528
529         /** Convert a colour string from CSS colour name ("blue"), short hex ("#abc") or long hex ("#a0b0c0"), 
530                         to an integer. 
531                         
532                 @default 0 */
533
534         public static function parseCSSColor(colorStr:String):uint {
535             colorStr = colorStr.toLowerCase();
536             if (CSSCOLORS[colorStr] != undefined) {
537                 return CSSCOLORS[colorStr];
538             } else {
539                 var match:Object = HEX.exec(colorStr);
540                 if ( match ) { 
541                   if ( match[1].length == 3) {
542                     // repeat digits. #abc => 0xaabbcc
543                     return Number("0x"+match[1].charAt(0)+match[1].charAt(0)+
544                                        match[1].charAt(1)+match[1].charAt(1)+
545                                        match[1].charAt(2)+match[1].charAt(2));
546                   } else if ( match[1].length == 6) {
547                     return Number("0x"+match[1]);
548                   } else {
549                     return Number("0x000000"); //as good as any
550                   }
551                 }
552             }
553             return 0;
554         }
555         }
556 }