Add support for CSS-based tag transformations
authorRichard Fairhurst <richard@systemeD.net>
Mon, 6 Feb 2012 13:54:43 +0000 (13:54 +0000)
committerRichard Fairhurst <richard@systemeD.net>
Mon, 6 Feb 2012 13:54:43 +0000 (13:54 +0000)
net/systemeD/halcyon/connection/Connection.as
net/systemeD/halcyon/connection/Entity.as
net/systemeD/halcyon/styleparser/CSSTransform.as [new file with mode: 0644]
net/systemeD/halcyon/styleparser/InstructionStyle.as
net/systemeD/halcyon/styleparser/RuleSet.as
net/systemeD/halcyon/styleparser/Style.as
net/systemeD/halcyon/styleparser/StyleChooser.as
net/systemeD/halcyon/styleparser/TagValue.as [new file with mode: 0644]
net/systemeD/potlatch2/VectorSourceDialog.mxml

index e3a1c58..5c67ba5 100644 (file)
@@ -9,22 +9,25 @@ package net.systemeD.halcyon.connection {
     import net.systemeD.halcyon.connection.actions.*;
     import net.systemeD.halcyon.connection.bboxes.*;
     import net.systemeD.halcyon.Globals;
+    import net.systemeD.halcyon.styleparser.CSSTransform;
 
        public class Connection extends EventDispatcher {
 
                public var name:String;
                public var statusFetcher:StatusFetcher;
                public var inlineStatus:Boolean = false;
+               public var cssTransform:CSSTransform;
         protected var apiBaseURL:String;
         protected var policyURL:String;
         protected var params:Object;
 
-               public function Connection(cname:String,api:String,policy:String,initparams:Object=null) {
+               public function Connection(cname:String,api:String,policy:String,initparams:Object=null,transform:CSSTransform=null) {
                        initparams = (initparams!=null ? initparams:{});
                        name=cname;
                        apiBaseURL=api;
                        policyURL=policy;
                        params=initparams;
+                       cssTransform=transform;
                }
 
         public function getParam(name:String, defaultValue:String):String {
index 16306cf..e05462e 100644 (file)
@@ -31,6 +31,7 @@ package net.systemeD.halcyon.connection {
             this._version = version;
             this._uid = uid;
             this._timestamp = timestamp;
+            if (connection.cssTransform) tags=connection.cssTransform.run(this,tags);
             this.tags = tags;
                        this._loaded = loaded;
             modified = id < 0;
diff --git a/net/systemeD/halcyon/styleparser/CSSTransform.as b/net/systemeD/halcyon/styleparser/CSSTransform.as
new file mode 100644 (file)
index 0000000..27a7747
--- /dev/null
@@ -0,0 +1,35 @@
+package net.systemeD.halcyon.styleparser {
+
+       import flash.net.*;
+    import flash.events.*;
+       import net.systemeD.halcyon.connection.Entity;
+
+    public class CSSTransform {
+
+               private static const GLOBAL_INSTANCE:CSSTransform = new CSSTransform();
+               public static function getInstance():CSSTransform { return GLOBAL_INSTANCE; }
+
+               [Bindable] public var url:String='';
+               private var ruleset:RuleSet;
+               
+               public function loadFromUrl(filename:String):void {
+                       url=filename;
+                       ruleset=new RuleSet(0,30,cssReady);
+                       ruleset.loadFromCSS(url);
+               }
+
+               public function clear():void {
+                       ruleset=null;
+                       url='';
+               }
+
+               private function cssReady():void {
+               }
+               
+               public function run(entity:Entity,tags:Object):Object {
+                       if (ruleset) return ruleset.runInstructions(entity,tags);
+                       return tags;
+               }
+
+       }
+}
index 1dec03e..82d5c79 100644 (file)
@@ -2,15 +2,28 @@ package net.systemeD.halcyon.styleparser {
 
        public class InstructionStyle extends Style {
 
-               public var set_tags:Object;
+               public var set_tags:Object={};
                public var breaker:Boolean=false;
+               public var set_tags_order:Array=[];
 
                public function addSetTag(k:String,v:*):void {
+                       if (v is Eval) { evals[k]=v; }
+                       else if (v is TagValue) { tagvalues[k]=v; }
+                       
                        edited=true;
-                       if (!set_tags) { set_tags=new Object(); }
+                       set_tags_order.push(k);
                        set_tags[k]=v;
                }
 
+               public function assignSetTags(tags:Object):void {
+                       for (var i:uint=0; i<set_tags_order.length; i++) {
+                               var k:String=set_tags_order[i];
+                               var v:*=set_tags[k];
+                               if (v is TagValue) { v=v.getValue(tags); }
+                               if (v=='') { delete tags[k]; }
+                               else { tags[k]=v; }
+                       }
+               }
        }
 
 }
index 9c16687..9c0e37e 100644 (file)
@@ -59,12 +59,15 @@ package net.systemeD.halcyon.styleparser {
                private static const CONDITION_LE:RegExp        =/^ \s* ([:\w]+) \s* <= \s* (.+) \s* $/sx;
                private static const CONDITION_REGEX:RegExp     =/^ \s* ([:\w]+) \s* =~\/ \s* (.+) \/ \s* $/sx;
 
-               private static const ASSIGNMENT_EVAL:RegExp     =/^ \s* (\S+) \s* \:      \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
-               private static const ASSIGNMENT:RegExp          =/^ \s* (\S+) \s* \:      \s*          (.+?) \s*                   $/sx;
-               private static const SET_TAG_EVAL:RegExp        =/^ \s* set \s+(\S+)\s* = \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
-               private static const SET_TAG:RegExp                     =/^ \s* set \s+(\S+)\s* = \s*          (.+?) \s*                   $/isx;
-               private static const SET_TAG_TRUE:RegExp        =/^ \s* set \s+(\S+)\s* $/isx;
-               private static const EXIT:RegExp                        =/^ \s* exit \s* $/isx;
+               private static const ASSIGNMENT_EVAL:RegExp             =/^ \s* (\S+) \s* \:      \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
+               private static const ASSIGNMENT_TAGVALUE:RegExp =/^ \s* (\S+) \s* \:      \s* tag  \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
+               private static const ASSIGNMENT:RegExp                  =/^ \s* (\S+) \s* \:      \s*          (.+?) \s*                   $/sx;
+               private static const SET_TAG_EVAL:RegExp                =/^ \s* set \s+(\S+)\s* = \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
+               private static const SET_TAG_TAGVALUE:RegExp    =/^ \s* set \s+(\S+)\s* = \s* tag  \s* \( \s* ' (.+?) ' \s* \) \s* $/isx;
+               private static const SET_TAG:RegExp                             =/^ \s* set \s+(\S+)\s* = \s*          (.+?) \s*                   $/isx;
+               private static const SET_TAG_TRUE:RegExp                =/^ \s* set \s+(\S+)\s* $/isx;
+               private static const DELETE_TAG:RegExp                  =/^ \s* delete \s+(\S+)\s* $/isx;
+               private static const EXIT:RegExp                                =/^ \s* exit \s* $/isx;
 
                private static const oZOOM:uint=2;
                private static const oGROUP:uint=3;
@@ -250,6 +253,14 @@ package net.systemeD.halcyon.styleparser {
                        return sl;
                }
 
+               /** Run instruction styles only, for CSSTransform. */
+               public function runInstructions(obj:Entity, tags:Object):Object {
+                       for each (var sc:StyleChooser in choosers) {
+                               tags=sc.runInstructions(obj,tags);
+                       }
+                       return tags;
+               }
+
                // ---------------------------------------------------------------------------------------------------------
                // Loading stylesheet
 
@@ -430,11 +441,14 @@ package net.systemeD.halcyon.styleparser {
                        var xs:InstructionStyle=new InstructionStyle(); 
 
                        for each (a in s.split(';')) {
-                               if ((o=ASSIGNMENT_EVAL.exec(a)))   { t[o[1].replace(DASH,'_')]=saveEval(o[2]); }
-                               else if ((o=ASSIGNMENT.exec(a)))   { t[o[1].replace(DASH,'_')]=o[2]; }
-                               else if ((o=SET_TAG_EVAL.exec(a))) { xs.addSetTag(o[1],saveEval(o[2])); }
-                               else if ((o=SET_TAG.exec(a)))      { xs.addSetTag(o[1],o[2]); }
-                               else if ((o=SET_TAG_TRUE.exec(a))) { xs.addSetTag(o[1],true); }
+                               if      ((o=ASSIGNMENT_EVAL.exec(a)))           { t[o[1].replace(DASH,'_')]=saveEval(o[2]); }
+                               else if ((o=ASSIGNMENT_TAGVALUE.exec(a)))       { t[o[1].replace(DASH,'_')]=new TagValue(o[2]); }
+                               else if ((o=ASSIGNMENT.exec(a)))                        { t[o[1].replace(DASH,'_')]=o[2]; }
+                               else if ((o=SET_TAG_EVAL.exec(a)))                      { xs.addSetTag(o[1],saveEval(o[2])); }
+                               else if ((o=SET_TAG_TAGVALUE.exec(a)))          { xs.addSetTag(o[1],new TagValue(o[2])); }
+                               else if ((o=SET_TAG.exec(a)))                           { xs.addSetTag(o[1],o[2]); }
+                               else if ((o=SET_TAG_TRUE.exec(a)))                      { xs.addSetTag(o[1],true); }
+                               else if ((o=DELETE_TAG.exec(a)))                        { xs.addSetTag(o[1],''); }
                                else if ((o=EXIT.exec(a))) { xs.setPropertyFromString('breaker',true); }
                        }
 
index 7ee5b00..32285a8 100644 (file)
@@ -34,6 +34,9 @@ package net.systemeD.halcyon.styleparser {
                /** Compiled SWFs for each eval. We keep it here, not in the property itself, so that we can retain typing for each property. */
                public var evals:Object={};
                
+               /** TagValue assignments, e.g. { width: tag('lanes'); } */
+               public var tagvalues:Object={};
+               
                /** Make an exact copy of an object.
                        Used when merging cascading styles. (FIXME: this needs some benchmarking - it may be quicker to simply iterate over .properties, 
                        copying each one. */
@@ -70,7 +73,7 @@ package net.systemeD.halcyon.styleparser {
                        return false;
                }
                
-               /** Are there any eval functions defined? */
+               /** Are there any eval functions defined? (This isn't used.) */
                public function hasEvals():Boolean {
                        for (var k:String in evals) { return true; }
                        return false;
@@ -79,16 +82,14 @@ package net.systemeD.halcyon.styleparser {
                /** Run all evals for this Style over the supplied tags.
                        If, for example, the stylesheet contains width=eval('_width+2'), then this will set Style.width to 7. */
                public function runEvals(tags:Object):void {
-                       for (var k:String in evals) {
-                               // ** Do we need to do typing here?
-                               this[k]=evals[k].exec(tags);
-                       }
+                       for (var k:String in evals) this[k]=evals[k].exec(tags);
                }
-
+               
                /** Set a property, casting as correct type. */
                public function setPropertyFromString(k:String,v:*):Boolean {
                        if (!this.hasOwnProperty(k)) { return false; }
                        if (v is Eval) { evals[k]=v; v=1; }
+                       else if (v is TagValue) { tagvalues[k]=v; v=1; }
 
                        // Arrays don't return a proper typeof, so check manually
                        // Note that undefined class variables always have typeof=object,
@@ -117,6 +118,7 @@ package net.systemeD.halcyon.styleparser {
             for each (var k:String in this.properties) {
                                if (this.hasOwnProperty(k)) { str+=k+"="+this[k]+"; "; }
                        }
+                       for each (k in tagvalues) str+=k+";"; 
                        return str;
         }
        }
index fdefa23..9d9d2f9 100644 (file)
@@ -52,7 +52,8 @@ package net.systemeD.halcyon.styleparser {
                                        sl.addSubpart(c.subpart);
 
                                        // Update StyleList
-                                       for each (var r:Style in styles) {
+                                       for (var i:uint=0; i<styles.length; i++) {
+                                               var r:Style=styles[i];
                                                var a:Object;
                                                if (r is ShapeStyle) {
                                                        a=sl.shapeStyles;
@@ -73,9 +74,7 @@ package net.systemeD.halcyon.styleparser {
                                                        if (w>sl.maxwidth) { sl.maxwidth=w; }
                                                } else if (r is InstructionStyle) {
                                                        if (InstructionStyle(r).breaker) { return; }
-                                                       if (InstructionStyle(r).set_tags) {
-                                                               for (var k:String in InstructionStyle(r).set_tags) { tags[k]=InstructionStyle(r).set_tags[k]; }
-                                                       }
+                                                       InstructionStyle(r).assignSetTags(tags);
                                                        continue;
                                                }
                                                if (r.drawn) { tags[':drawn']='yes'; }
@@ -96,6 +95,24 @@ package net.systemeD.halcyon.styleparser {
                        }
                }
                
+               /** Cut-down version of updateStyles that runs InstructionStyles only - for CSSTransform usage. */
+
+               public function runInstructions(obj:Entity, tags:Object):Object {
+                       for each (var c:RuleChain in ruleChains) {
+                               if (c.test(-1,obj,tags,10)) {
+                                       for (var i:uint=0; i<styles.length; i++) {
+                                               var r:Style=styles[i];
+                                               if (r is InstructionStyle) {
+                                                       if (InstructionStyle(r).breaker) { return tags; }
+                                                       InstructionStyle(r).assignSetTags(tags);
+                                               }
+                                               r.runEvals(tags);
+                                       }
+                               }
+                       }
+                       return tags;
+               }
+               
                
                // ---------------------------------------------------------------------------------------------
                // Methods to add properties (used by parsers such as MapCSS)
diff --git a/net/systemeD/halcyon/styleparser/TagValue.as b/net/systemeD/halcyon/styleparser/TagValue.as
new file mode 100644 (file)
index 0000000..06cc583
--- /dev/null
@@ -0,0 +1,33 @@
+package net.systemeD.halcyon.styleparser {
+
+       /*
+               === TagValue ===
+
+               This is a custom declaration value that means 'use the value of this tag'.
+               In other words,
+                       { set ref=tag('dftnumber'); }
+               parses to
+                       TagValue('dftnumber')
+               and returns the value of the dftnumber tag.
+               
+               There isn't really any logic contained within this class, it's just here 
+               so that we can store it as a custom property within Styles (like Eval).
+
+       */
+
+       public class TagValue {
+               private var key:String;
+
+               public function TagValue(k:String) {
+                       key=k;
+               }
+
+               public function getValue(tags:Object):String {
+                       return tags[key];
+               }
+               
+               public function toString():String {
+                       return "TagValue("+key+")";
+               }
+       }
+}
index e2358ba..5f49e10 100644 (file)
@@ -4,7 +4,7 @@
         xmlns:mx="library://ns.adobe.com/flex/mx"
         layout="vertical" showCloseButton="true"
         horizontalAlign="center" title="Load vector file"
-        width="500" height="350" verticalGap="0">
+        width="500" height="450" verticalGap="0">
 
        <mx:DataGrid editable="true" width="100%" height="100%" id="dataGrid" 
                dataProvider="{vectorLayers}" itemEditEnd="dataEdited(event)">
@@ -50,7 +50,9 @@
        </mx:HBox>
 
        <mx:VBox width="100%" paddingTop="10">
-               <mx:Label htmlText="&lt;b&gt;Add new vector layer&lt;/b&gt;" />
+               <mx:HRule width="100%" />
+               <!-- ** FIXME: this looks horrid - make into a nicely laid out form -->
+               <mx:Label text="Add new vector layer" fontSize="12" fontWeight="bold" />
                <mx:HBox>
                        <mx:RadioButtonGroup id="filetype" />
                        <mx:RadioButton width="100%" groupName="filetype" value="gpx" id="gpx" label="GPX" selected="true" />
                                        <fx:Object label="NAD83" data="EPSG:4269" />
                                </mx:ArrayCollection>
                        </mx:ComboBox>
-                       <mx:CheckBox width="100%" label="Simplify paths" selected="true" id="simplify" />
+                       <mx:CheckBox width="100%" label="Simplify paths" id="simplify" />
                </mx:HBox>
                <mx:HBox>
                        <mx:Text text="URL:"/>
                        <mx:TextInput width="100%" id="src" text="" change="selectByExtension()" />
                        <mx:Button label="Load" click="loadFiles(src.text, filetype.selectedValue.toString(), simplify.selected, projection.selectedItem.data);" enabled="{src.text == '' ? false : true}"/>
                </mx:HBox>
+               <mx:HBox>
+                       <mx:Text text="Tag transform:"/>
+                       <mx:TextInput width="100%" id="transformsrc" text="{CSSTransform.getInstance().url}"/>
+                       <mx:Button label="Load" click="loadTransform(transformsrc.text);" enabled="{transformsrc.text == '' ? false : true}"/>
+                       <mx:Button label="Clear" click="clearTransform();" enabled="{transformsrc.text == '' ? false : true}"/>
+               </mx:HBox>
        </mx:VBox>
 
   <fx:Script><![CDATA[
        import net.systemeD.halcyon.MapPaint;
        import net.systemeD.halcyon.Globals;
        import net.systemeD.halcyon.connection.Connection;
-       import net.systemeD.potlatch2.utils.Importer;
-       import net.systemeD.potlatch2.utils.GpxImporter;
-       import net.systemeD.potlatch2.utils.KmlImporter;
-       import net.systemeD.potlatch2.utils.ShpImporter;
-       import net.systemeD.potlatch2.utils.OsmImporter;
+       import net.systemeD.halcyon.styleparser.CSSTransform;
+       import net.systemeD.potlatch2.utils.*;
        import net.systemeD.potlatch2.collections.Stylesheets;
     import mx.managers.PopUpManager;
     import mx.events.DataGridEvent;
                var stylesheet:String="stylesheets/potlatch.css";
                if (type=='gpx') { stylesheet="stylesheets/gpx.css"; }
 
-               var connection:Connection = new Connection(name, url, null, null);
+               var connection:Connection = new Connection(name, url, null, null, CSSTransform.getInstance());
 
                var filesLoaded:Function = function(success:Boolean,message:String=null):void {
                        if (success) {
                                                            [url+".shp",url+".shx",url+".dbf"], filesLoaded, simplify, projection);
                }
        }
+       
+       private function loadTransform(url:String):void {
+               CSSTransform.getInstance().loadFromUrl(url);
+       }
+       private function clearTransform():void {
+               CSSTransform.getInstance().clear();
+       }
 
               ]]>
   </fx:Script>