LeichtgewichtLeichtgewicht

2011/04/18 – 19:51

Mastering AS3 namespaces

I know few programmers who can really say they understand namespaces in ActionScript. Aside a few rudimentary blog posts like the one at GSkinner or the wiki entry of Maashaack, google provides just very basic informations like this.

Mastering AS3 namespaces

  1. Basics
  2. QName
  3. use namespace
  4. XML
  5. e4x
  6. Limitations
  7. Flex

1. Basics

Namespaces can be used for two things. You can create custom visibilities (like public, private) of functions/variables. Namespaces can also be used in combination with xml. Lets start easy: namespaces are like variables, they can be defined in pretty much any scope with the namespace keyword.

package {
  namespace myNs = "uri"; // separate file
}
package {
  class MyClass {
    private namespace privateNs = "private";
    public function MyClass() {
       namespace localNs = "local";
    }
  }
}

These namespaces can be used as a new visibility for variables or functions.

package {
   namespaces myNs;
}
package {
  class MyClass {
    myNs var myVar: String = "foo";
    myNs function myFunction(): String {}
  }
}

These new properties of the myNs can be addressed with the :: keyword.

var instance: MyClass = new MyClass();
instance.myNs::myVar;
instance.myNs::myFunction();

The namespaces in the example above are defined as constant. You can also use the var or const to define them like regular variables.

var myNs: Namespace = new Namespace("prefix", "uri"); 

For the visibility of properties, all that matters is the uri. You can define two namespaces with a different name in two different packages. As long as they contain the same uri, they will refer to the same property.

namespace aNs = "myUri";
namespace bNs = "myUri";

class MyClass {
  aNs var foo: String;
}

var instance: MyClass = new MyClass();
instance.aNs::foo; // works as expected
instance.bNs::foo; // works too

var cNs: Namespace = new Namespace( "myUri" );
instance.cNs::foo; // works as well

Keep in mind that accessing the variables using a namespace that is defined via the namespace keyword is almost 2 times faster than using Namespace instances.

namespace keyWordNs = "uri";
var localNs: Namespace = new Namespace( null, "uri" );

instance.keyWordNs::myVar; // FAST
instance.localNs::myVar; // slow

Referencing to built-in namespaces public,private and internal is not possible. A namespace variable without uri however can work to access the regular properties with the :: syntax.

  class MyClass {
       public var a: String;
  }
  var publicNs: Namespace = new Namespace();
  var instance: MyClass = new MyClass();
  instance.publicNs::a = "hello";

But this does not work with any namespace. You have to refer to the namespace as variable in a function scope, it will not work if its defined in the class scope.

  class MyClass {
    public var ns: Namespace = new Namespace();

    public var test: String;

    public function foo(): void {
       this.ns::["test"] = "ho"; // this might not work
       var myNs: Namespace = ns;
       this.myNs::["test"] = "ho"; // this will.
    }
  }

Unless necessary however you should refrain from that, since its about 50% slower than the regular approach.

If you define a namespace without a uri it will get one automatically assigned.

  package my.pkg {
    namespace myns;
  }
  class MyClass {
    public namespace myns;

    public function test(): Namespace {
       namespace localns;
       return localns;
    }
  }
  my.pkg.myns.uri; // "my.package:myns"
  (new MyClass).myns.uri; // "MyClass/myns"
  (new MyClass).test().uri; // "MyClass/test/localns"

If you have a untyped instance you can not use your common syntax with squared braces to address them.

   instance["uri::myVar"] = "test"; // doesnt work ...
   instance.myNs::["myVar"] = "test"; // works - yey!

Curiously this is almost as fast as if you would access it with regular square braces.

  class MyClass {
    public var test: String;
    public function foo(): void {
       var myNs: Namespace = new Namespace();
       this.myNs::["test"] = "ho";
       this["test"] = "ho"; // as fast as line above
    }
  }

2. QName

The ECMA guys thought ahead! There is a another way to access properties with a namespace: QName. While its more to write QName allows to address those variables a lot clearer

  namespace ns;
  class MyClass {
    public var bar1: String;
    ns var bar2: String;
    public function foo(): void {
       var qn: QName;
       qn = new QName( ns, "bar2" );
       this[qn] = "ha";

       this["bar1"] = "ho"; // like before
       qn = new QName( null, "bar1" );
       this[qn] = "ho"; // twice as fast
    }
  }

Beside that nice little trick QName also works with .hasOwnProperty.

namespace ns;
class MyClass {
  ns test: String;
}

var inst: MyClass = new MyClass();
inst.hasOwnProperty( new QName( ns, "test" ) );
// inst.hasOwnProperty( "ns::test" );
// Using a namespace like this wont work

As with the ns::["prop"] syntax, QName doesn’t enable namespace access/write properties on a dynamic object (it will throw an error as you try).

namespace ns;
var inst: Object = {};
inst[ new QName( ns, "test" ) ] = "ho";
// ReferenceError: Error #1056: Cannot create property ns::test on Object.
var dict: Dictionary = new Dictionary();
dict[ new QName( ns, "test" ) ] = "ho";
// ReferenceError: Error #1056: Cannot create property ns::true
// on flash.utils.Dictionary.

A small note for working with QName: If two QNames have the same properties they are equal using the == operator.

var qn1: QName = new QName( null, "test" );
var qn2: QName = new QName( null, "test" );
qn1 == qn2: // true - good to know

Another small note: Even though QName instances are equals, you can not check with Array.indexOf() for them within a list. So you need to make sure that within a list you have to take the exactly(===) same qname.

3. use namespace

The use namespace keyword makes your life easier by allowing to skip a few statements.

 public class MyClassA {
  namespace ns;

  ns var a: String;
  ns const c: int = 3;

  public function withoutUseNs(): String {
    var result: String = "";
    ns::a = "foo";
    for( var i: int = 0; i < ns::c; i++ ) {
      result += (ns::a += "o") + (ns::c*i);
    }
    return result;
  }
  public function withUseNs(): String {
    use namespace ns;
    var result: String = "";
    a = "foo";
    for( var i: int = 0; i < c; i++ ) {
      result += (a += "o") + (c*i);
    }
    return result;
  }
}

There are two interesting things about use namespace. You can place them (like variable definitions) wherever you like in your scope.

  public function withUseNs(): String {
    var result: String = "";
    a = "foo";
    for( var i: int = 0; i < c; i++ ) {
      result += (a += "o") + (c*i);
    }
    use namespace ns; // works just like before
    return result;
  }

And the other thing is that you can define more than one "use namespace" within a function scope.

 public class MyClassA {
  namespace ns1;
  namespace ns2;

  ns1 const a: Number = 1;
  ns2 const b: Number = 3;  

  public function foo(): Number {
    use namespace ns1;
    use namespace ns2;
    return a + b;
  }
}

4. XML

Up till now we just used namespaces in straight ActionScript 3, but as mentioned in the beginning it can be used in combination with XML and e4x.

When working with xml files you can define namespaces for prefixes. I am sure you saw this before:

This just means that every node within this definition with the mx prefix refers to the namespace with that uri. You access the namespace via code using the namespace() function.

var ns: Namespace = .namespace("pre");
ns.prefix; // "pre"
ns.uri; // "uri"

Sideinfo: You could use that namespace to access code like explained in the sections before.

The default namespace can be accessed same way:

var xml: XML = ;
var ns: Namespace = xml.namespace();
ns.prefix; // ""
ns.uri; // "uri"
xml.children()[0].namespace() == ns;
// All children without a defined ns. get the default ns.

Nodes defined using a namespace also contain all separate namespace information.

var nodeAsVar: XML = 
.children()[0];
nodeAsVar.name(); // uri::node
nodeAsVar.localName();// node
var nodeNs: Namespace = nodeAsVar.namespace();
nodeNs.prefix; // pre
nodeNs.uri; // uri

XML.setNamespace allows to define the default namespace programatically.

var xml: XML = ;
xml.setNamespace( "uri" );
xml.toXMLString();

Now - one should assume that all the childnodes changed too but sadly that is not the case (why do the folks at adobe offer that method?). You need to copy/reparse the xml to get the proper namespace in all childnodes too.

var xml: XML = ;
xml.setNamespace( "uri" );
xml.children()[0].namespace(); // "" ... ?! the nodes are not updated
xml = new XML( xml.toXMLString() ); // copy/reparse the xml
xml.children()[0].namespace(); // "uri" ... right on! 

To define some custom namespaces you can use XML.addNamespace. Now we finally get to use the prefix property of Namespace, because without the prefix property this method will do nothing.

var xml: XML = ;
xml.addNamespace( new Namespace( null, "uri" ) ); // will do nothing
xml.addNamespace( new Namespace( "prefix", "uri" ) );
// will change it to 

To redefine a namespace on a prefix you have to remove the namespace. But that does have its implications.

var xml: XML = 
;
xml.removeNamespace( xml.namespace( "prefix" ) ); 
xml.addNamespace( new Namespace( "prefix2", "uri2" ) );

If you set a namespace to a xml that had a namespace defined before.

var xml: XML = ;
xml.setNamespace( "uri" );

  

In case you didn't notice it: The "aaa" is was added by Flash automatically. I didn't have to do anything for that magic trick. To find out what Flash really does, lets see what happens if we set the namespace a few more times.

var xml: XML = ;
xml.setNamespace( "uri" );
xml.setNamespace( "urib" );
xml.setNamespace( "uric" );

 

Okay, now with the "aaa" namespace defined before.

var xml: XML = ;
xml.setNamespace( "urib" );

Now, if we just had a method to remove the namespace we could eventually create a little fix for this. XML.removeNamespace seems to be just right. And actually does the job quite well, if use it for namespaces with a prefix.

var xml: XML = ;
var ns: Namespace = xml.namespaceDeclarations()[0];
// by the way: you can access all namespace declarations with that method
xml.removeNamespace( ns ); // and gone! 

Without a prefix it does pretty much nothing. I've tried a lot of ways to remove that default namespace.

var xml: XML = ;
xml.removeNamespace( n.namespace() ); // not that way
xml.removeNamespace( null ); // not this way.
xml.setNamespace( new Namespace( ) ); // doesn't work
xml.@xmlns = ""; // okay, thats it!
// (more about that last one soon.) 

If you ask stack overflow, people will tell you to parse the xml string by hand (which is rather slow, especially if you have a big XML file).

// modified code from Eric Belair - see stack overflow
package at.leichtgewicht {
  public function removeDefaultNamespace(xml:XML):String {
    /* Replace the default namespace from the String representation
        of the result XML with an empty string. */
    return xml.toXMLString().replace(xmlnsPattern, "");
  }
}
/* Define the regex pattern to remove the default namespace
    from the String representation of the XML result. */
const xmlnsPattern: RegExp = / xmlns=[^"]*"[^"]*"/gi; 

You can also just download that here if you need it.

5. e4x

For those new to xml in flash: the xml.@xmlns syntax is part of e4x, a sub-syntax for ActionScript that offers edit and access abilities for xml code.

With e4x its easy to request all your nodes within a element.

var xml: XML = ;
var nodes: XMLList = xml.node; // contains a list with both nodes

If you add a namespace to your xml node, the same request won't work anymore. You have to add a namespace in the expression.

var xml: XML = ;
var ns: Namespace = xml.namespace();
var nodes: XMLList = xml.node; // contains a empty list
var nodesNs: XMLList = xml.ns::node; // contains a list with both nodes

That is confusing because most people don't realize that their accessor suddenly is wrong. For that reason it is a good practice for xml to use namespaces.

With xml.@attr its not just easy to read the content of properties, it is also possible to set them. So, my thought (some mentions earlier) were to change the default namespace using attributes. The result is very funny.

var xml: XML = ;
xml.@xmlns = "newuri";
<xml xmlns="newuri" xmlns="olduri"> <node/> </xml> 

Unexpected result. In xml it is not really common to define a property twice. Seems like Flash stores namespaces and attributes differently, good to know!

6. Limitations

Even though it looks obvious, you can not pass a string variable to a namespace definition.

var str: String = "uri";
namespace myNs = str;
// It would be nice if adobe had implemented that as a native
// way to "object pool" namespaces. 

You can not use namespaces in interfaces.

interface MyInterface {
  myns function aFunction() {}
  // Error. This may be one of the biggest flaws of namespaces.
} 

You can not define a constructor with a different namespace.

class MyClass {
  public function MyClass() {}
  myns function MyClass() {}
  // Error while compiling, oh! that feature would be cool....
} 

It is not possible to define variables in a namespace on dynamic objects.

 var obj: * = {};
obj.myNs::test = "foo"; // Nope
obj.myNs::["test"] = "foo"; // Not even like that. 

It is possible to define static, public/private/internal, local and a namespaces variable with the same name. However it is not possible to use use namespace if you have a public and a namespaced variable with the same name.

class MyClass {
  namespace ns;
  public var a: String;
  ns var a: String;
  public function foo(): void {
    use namespace ns; // Error: Ambiguous reference to a.
  }
}

Making for( .. in.. ); loops work over properties with a namespace is not possible using .propertyIsEnumerable and .
setPropertyIsEnumerable
.

  namespace ns;
  public class MyClass {
    function MyClass() {
      this.setPropertyIsEnumerable( new QName( ns, "foo"), true );
    // ReferenceError: Error #1056: Cannot create property ns::foo on MyClass.
    }
    ns var foo: String;
  }

7. Flex

If you don't know how flex does it you might get the impression that flex does it really quirky, where truly it just does it buggy (referring to Flex 4 here).

If you make a variable with namespace [Bindable], the owning instance will send a event that contains the namespace in the .property parameter.

namespace ns = "uri";
class MyClassA extends EventDispatcher {
  [Bindable]
  ns var foo: String;
}
var inst: MyClassA = new MyClassA();
inst.addEventListener( PropertyChangeEvent.PROPERTY_CHANGE,
  function( e: PropertyChangeEvent ): void {
    e.property; // "ns::foo";
  } );
inst.ns::foo = "me"; 

If you paid close attention you recognized a fatal flaw: The property notified by the event should be "uri::foo". Now that is a nasty little bugger.

This bug results that you need to use namespaces that are named same like the uri they refer to.

namespace ns = "uri";
namespace ns2 = "ns2";
class MyClass {
  [Bindable]
  ns var foo: String; // won't work properly at all

  [Bindable]
  ns2 var bar: String; // will work in the case you access them properly
}

What does "properly" mean? You have to also address it with a namespaces that is named "ns2".

<mx:Script><![CDATA[
  namespace ns2 = "ns2";

  [Bindable]
  public var a: MyClass = new MyClass();
]]></mx:Script>
<mx:TextField text="{a.ns2::bar}" /> 

Anything else but this might eventually compile but will not work.

What about BindingUtil? Well, as stated above: obj["ns::prop"] does not work. Unfortunately ChangeWatcher (a implementation detail of BindingUtil) is working by just attempting binding in this wrong way. That means: binding will not work using BindingUtil, sorry pals.

I had a real fun time to make that one work out of the box with nanosome binding.

import nanosome.notify.bind.bind;
bind( a, "uri::foo", a, "ns2::bar" ); 

Since nanosome supports deep binding (like "foo.bar.anotherProp") and namespace encourage to use dots for separation "www.domain.com/fun": you will soon run into a definition problem. For that very reason I added the path method. Just define the properties each by each and it will work with all namespaces you like.

import nanosome.notify.bind.bind;
import nanosome.notify.bind.impl.path;
bind( a, path( "aChild", "www.domain.com/fun::foo" ), a, "ns2::bar" );

Enjoy!

Tags: , ,

Comments

  1. gueNo Gravatar

    :-D
    Guess your time at <former-company-here> hindered you to really march into foreign territories.

    Thanks for you extensive summary.

  2. TamasNo Gravatar

    Hi Martin!

    Thanks for the nice post. It helped me clarify things. For the default xml namespace issue, I have a solution which worked for my at the end. It’s not an elegant one, but at least no need to re-parse the xml source string. Hope it works for you too.

    private function replaceDefaultNameSpace (src:XML, ns:Namespace):void
    {
    	var tempNs:Namespace = new Namespace ("temp");
    	var node:XML;
    
    	src.addNamespace(tempNs);
    	src.setNamespace(tempNs);
    
    	for each (node in src.descendants()) node.setNamespace(tempNs);
    
    	src.addNamespace(ns);
    	src.setNamespace(ns);
    
    	for each (node in src.descendants()) node.setNamespace(ns);
    
    	src.removeNamespace(tempNs);
    
    	trace (src.toXMLString());
    }
  3. TamasNo Gravatar

    And call the function like this:

    replaceDefaultNameSpace(src, new Namespace());

    or

    replaceDefaultNameSpace(src, new Namespace("", "http://uri.com"));
  4. Martin HeideggerNo Gravatar

    @Tamas Looks like a nice alternative. I wonder how well it performs, did you do some tests?

  5. TamasNo Gravatar

    I did not measure it’s performance, but I believe replacing the namespace reference twice for each node should be faster than running a regexp based replacement and the xml parsing procedure. I have an update which avoids replacing the non-default namespaces and also has a shortcut when the default namespace uri is an empty string.

    private function replaceDefaultNameSpace (src:XML, ns:Namespace):void
    {
    	var tempNs:Namespace;
    	var defNs:Namespace = src.namespace();
    	var node:XML;
    
    	if (!defNs.uri) {
    
    		src.setNamespace(ns);
    
    	} else {
    
    		tempNs = new Namespace ("temp");
    
    		src.addNamespace(tempNs);
    		src.setNamespace(tempNs);
    
    		for each (node in src..defNs::*) node.setNamespace(tempNs);
    
    		src.addNamespace(ns);
    		src.setNamespace(ns);
    
    		for each (node in src..tempNs::*) node.setNamespace(ns);
    
    		src.removeNamespace(tempNs);
    	}
    
    	//trace (src.toXMLString());
    
    }
  6. Manfed KarrerNo Gravatar

    Thanks a lot for this detailed information! Specially the fact that QName supports hasOwnProperty was really valuable news.
    Namespaces are one of the nice, not well known treasures of ActionScript…

  7. KrilnonNo Gravatar

    One thing that you didn’t mention is that you can use namespaces to avoid ever having to use the “import” statement in code that uses names from other namespaces. There’s not much of a practical reason to do this, but it’s interesting. More info: http://www.kirupa.com/forum/showthread.php?340676-AS3-Doubt-Import-command&p=2524593&viewfull=1#post2524593

Leave a Reply

Site informations

Martin Heidegger – Web Developerskype:mastakanedaxing:martin.heideggertwitter:leichtgewicht