XML to ActionScript #2

Actionscript:
  1. var script:XML=<code>
  2.  
  3.    <make reference="blur" class="flash.filters.BlurFilter">
  4.      <set blurX="10" blurY="10" />
  5.    </make>
  6.    
  7.    <make reference="filts" class="Array">
  8.      <call method="push">
  9.        <arg reference="blur" />
  10.      </call>
  11.    </make>
  12.    
  13.    <make reference="mat" class="flash.geom.Matrix">
  14.      <call method="rotate" args="1" />
  15.      <set tx="100" ty="100" />
  16.    </make>
  17.    
  18.    <make reference="box" class="flash.display.Sprite">
  19.        <setRef filters="filts" transform.matrix="mat"/>
  20.        <call method="graphics.beginFill" args="0x000000" />
  21.        <call method="graphics.drawRect" args="-50,-50,100,100" />
  22.    </make>
  23.    
  24.    <call method="addChild">
  25.      <arg reference="box"/>
  26.    </call>
  27.    
  28.    <make reference="tf" class="flash.text.TextFormat">
  29.     <set font="_sans" size="12" color="0xFFFFFF" />
  30.    </make>
  31.  
  32.    <make reference="txt" class="flash.text.TextField">
  33.      <setRef defaultTextFormat="tf" />
  34.      <set autoSize="center" text="XML to AS3" />
  35.    </make>
  36.  
  37.    <make reference="circle" class="flash.display.Sprite">
  38.        <set x="300" y="300" />
  39.        <call method="graphics.beginFill" args="0xFF0000" />
  40.        <call method="graphics.drawCircle" args="0,0,100" />
  41.        <call method="addChild">
  42.          <arg reference="txt" />
  43.        </call>
  44.    </make>
  45.    
  46.    <call method="addChild">
  47.      <arg reference="circle"/>
  48.    </call>
  49.  
  50. </code>
  51.  
  52. // parse and run
  53. runCode(this, script);
  54.  
  55. function runCode(target:*, code:XML):void{
  56.     var children:XMLList = code.children();
  57.     for (var i:int = 0; i<children.length(); i++){
  58.         var child:XML = children[i];
  59.         var type:String = child.name();
  60.         if (type == "call"){
  61.             runMethod(target, child);
  62.         }else if (type == "set"){
  63.             setProp(target, child);
  64.         }else if (type == "setRef"){
  65.             setRefProp(target, child);
  66.         }else if (type == "make"){
  67.             makeInstance(child);
  68.         }
  69.     }
  70. }
  71.  
  72. function makeInstance(code:XML):void{
  73.     var ClassRef:Class = getDefinitionByName(code.@["class"]) as Class;
  74.     // nothing can be passed to the class constructor
  75.     // its not possible with function.apply
  76.     var instance:* = this[code.@reference] = new ClassRef();
  77.     runCode(instance, code);
  78. }
  79.  
  80. // set a property to a reference
  81. function setRefProp(target:*, code:XML):void{
  82.     var attributes:XMLList = code.attributes();
  83.     for (var i:int = 0; i<attributes.length(); i++){
  84.         var prop:String = attributes[i].name();
  85.         var o:Object = dotSyntax(target, prop, 1)
  86.         if (prop.indexOf(".") != -1){
  87.           o.obj[o.prop] = dotSyntax(this, attributes[i]);
  88.         }else{
  89.           o.obj[o.prop] = this[attributes[i]];
  90.         }
  91.     }
  92. }
  93.  
  94. // set a property to a value such as a Number, Boolean, String etc...
  95. function setProp(target:*, code:XML):void{
  96.     var attributes:XMLList = code.attributes();
  97.     for (var i:int = 0; i<attributes.length(); i++){
  98.         var prop:String = attributes[i].name();
  99.         var o:Object = dotSyntax(target, prop, 1)
  100.         o.obj[o.prop] = valueType(attributes[i]);
  101.     }
  102. }
  103.  
  104. function runMethod(target:*, code:XML):void{
  105.     var i:int = 0;
  106.     // get a reference to the function
  107.     var method:Function = dotSyntax(target, code.@method);
  108.     // call the function if there are no arguments
  109.     var attributeArgsLength:int = code.@args.toXMLString().length;
  110.     var childArgsLength:int = code.arg.length()
  111.     if (attributeArgsLength == 0 && childArgsLength == 0){
  112.         method();
  113.     }else{
  114.      
  115.      var args:Array;
  116.      if (attributeArgsLength> 0){
  117.          args = code.@args.toString().split(",");
  118.      } else {
  119.         args = [];
  120.      }
  121.    
  122.      for (i = 0; i<childArgsLength; i++){
  123.           var val:String = code.arg[i].@value;
  124.           if (val){
  125.             args.push(val);
  126.           }
  127.       }
  128.       for (i = 0; i<args.length; i++){
  129.           if (args[i] != ""){
  130.             args[i] = valueType(args[i]);
  131.           }
  132.       }
  133.       for (i = 0; i<code.arg.length(); i++){
  134.          var rs:String = code.arg[i].@reference;
  135.          if (rs){
  136.              args.push(dotSyntax(this, rs));  
  137.          }
  138.       }
  139.      
  140.       // run the function
  141.       method.apply(null,args);
  142.     }
  143. }
  144.  
  145. // parse dot syntax and return the last property or method
  146. function dotSyntax(target:*, str:String, offset:int = 0):*{
  147.     var path:Array = str.split(".");
  148.     var curr:* = target;
  149.     for (var i:int = 0; i<path.length - offset; i++){
  150.         curr = curr[path[i]]
  151.     }
  152.     if (offset != 0){
  153.         return {obj:curr, prop:path[i]}
  154.     }
  155.     return curr;
  156. }
  157.  
  158. function valueType(val:*):* {
  159.     if (isNaN(Number(val))) {
  160.            // remove leading and trailing white
  161.            // remove "" around strings
  162.            val = val.replace(/^s+|s+$/g,"").replace(/^"|"$/g,"");
  163.            //  see if it's a boolean
  164.            if (val == "true"){
  165.                val = true;
  166.            }else if (val == "false"){
  167.                val = false
  168.            }
  169.     } else {
  170.         val = Number(val);
  171.     }
  172.     return val;
  173. }

WARNING: There area few small bugs in this snippet. If you'd like to use this, check out the AsXML mini-library

This is the next version of yesterdays snippet. As you can see it isn't really a snippet anymore... This code parses a specifically formatted xml file to allow the following features to be achieved from XML at runtime:

1) call methods of the main timeline
2) read and write properties on the main timeline
3) instantiate classes on the main timeline
4) call methods on these classes
5) read and write properties on these classes

By implement those five feature a great deal becomes possible. The above XML creates this somewhat crappy looking thing:

While that doesn't look like much, it's actually doing quite a bit... It has a BlurFilter, an Array (for the filters property), two Sprites, a TextFormat and a TextField. addChild() is called on the main timeline and on one of the sprites (to nest the TextField in the circle).

Uses For This

You could use this to create levels for a simple game.

You could generate this XML based on user input to create e-cards and mini-apps.

If you have a medium sized app you could use it to create a sort of advanced config file that helps to ease your pain as the client decides they need 10 subtly different versions of the app. Every time the client decides they need a subtly different version you'd just need to create a different config file. THIS is probably the thing that I'll be using it for - could be a huge time saver...

Uses With Libraries

This could be used with TweenLite and/or Papervision. You could just add a bunch of import statements to the timeline or to a dynamic document class. Then you'd be able to do some basic authoring from the XML. I think I'll post an example of this either tonight or tomorrow.

I did originally start writing this thinking about QuickBox2D - but I already have an editor for QuickBox2D that just needs some UI (currently its key controlled). So I don't really need it. The QuickBox2D editor generates an ActionScript file - which makes sense because usually you need to go in and manually do a bunch of logic that couldn't really be done easily with an editor. That said, this will work with Box2D or QuickBox2D as an XML format.

The Downside

The downside is that this is dynamic and is therefore untyped. All class instances created by reading the XML will be dynamically typed. For something where your only adding 10 classes this way, its no big deal - but for a game editor it could be a problem - at the very least there are interesting advanced techniques implemented in this snippet that could be repurposed for lots of different things.

More Examples

Tonight or tomorrow I'll upload a few examples - it would be fun to do a TweenLite/Papervision example... would also be good to show what I mean by an advanced config for a medium sized app.

This entry was posted in XML, dynamic, external data, instantiation and tagged , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

2 Trackbacks

  1. [...] XML to ActionScript #2 [...]

  2. By 7 interessante sommerartikler · omFlash(); on July 27, 2009 at 2:45 am

    [...] XML to ActionScript Et bud på hvordan en xml-to-as engine kan skrues sammen, og selv hvis man ikke har brug for lige netop det, så er koden et kig værd. [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*