Search code examples
pythonactionscript-3dynamic-class-creation

using dynamic python class definition and amfast dynamic class mapping and code generation to generate ActionScript class


I have an xml snippet that contains an object hierarchy:

doc = """\
<RootObj val1="ValueOne" stat1="Stat1" stat2="Stat2">
  <internalarray type="array">
    <InternalObject val1="12" val2="12" />
    <InternalObject val1="13" val2="13" />
    <InternalObject val1="14" val2="14" />
    <InternalObject val1="15" val2="15" />
  </internalarray>
</RootObj>"""

I use the ElementTree xml representation to parse the xml:

from xml.etree import ElementTree as ET
...
xml_doc = ET.XML(doc)

I recursively loop through the xml_doc elements, building the class definitions as I go using "namedtuple":

from collections import namedtuple
...
def buildClass(name, node):
  symbol_table = {}
  args = []
  varnames = ""
  for subnode in node:
    args.append(buildClass(subnode.tag, subnode))
    if (subnode.tag not in symbol_table):
      symbol_table[subnode.tag] = 1
      varnames += subnode.tag + " "

  print 'Building class for:', name
  for (key, value) in node.items():
    args.append(value)
    varnames += key + " "
  varnames = varnames.strip()
  if (not name[0] == name[0].upper()):
    #this is an array, do not create a class, just return an array
    #pop the last element, "array"
    args.pop()
    return args
  globals()[name] = namedtuple(name, varnames)
  obj = globals()[name](*args)
  return obj

Which is called like this:

  rootObj = build_class(xml_doc.tag, xml_doc)

Using dump, a function found elsewhere on StackOverflow:

def dump(obj):
  '''return a printable representation of an object for debugging'''
  newobj=obj
  if '__dict__' in dir(obj):
    newobj=obj.__dict__
    if ' object at ' in str(obj) and not newobj.has_key('__type__'):
      newobj['__type__']=str(obj)
    for attr in newobj:
      newobj[attr]=dump(newobj[attr])
  return newobj

You can call:

print dump(rootObj)

And see (I formatted the spacing manually):

RootObj(
  internalarray=[
    InternalObject(val2='12', val1='12'), 
    InternalObject(val2='13', val1='13'), 
    InternalObject(val2='14', val1='14'), 
    InternalObject(val2='15', val1='15')
  ], 
  val1='ValueOne', stat2='Stat2', stat1='Stat1')

So we know the code is actually generating a class. Now, if you use the amfast DynamicClassMapper and code generator:

import amfast
from amfast import class_def
from amfast.class_def.code_generator import CodeGenerator   
...
class_mapper = class_def.ClassDefMapper()
mapped_class = class_def.DynamicClassDef(RootObj, 'RootObj', ())
#OR
#mapped_class = class_def.DynamicClassDef(globals()[xml_doc.tag],xml_doc.tag, ())
#I tried both and received the same output
coder = CodeGenerator()
coder.generateFilesFromMapper(class_mapper, use_accessors=False,
  packaged=True, constructor=True, bindable=True, extends='Object')

You get a file, RootObj.as:

package
{
  [Bindable]
  [RemoteClass(alias='RootObj')]
  public dynamic class RootObj extends Object
  {
    public function RootObj():void
    {
      super();
    }
  }
}

Which is obviously missing all the attributes and whatnot. Is there a way to utilize this coding methodology to output an ActionScript file that actually contains the correct class definition?


Solution

  • Ok, so apparently, this is something nobody knows how to do, so I came up with a workaround.

    I modified the buildClass() function like so:

    def buildClass(name, node):
      global _classes
      symbol_table = {}
      args = []
      varnames = ""
      varnameswithtypes = ""
      for subnode in node:
        args.append(buildClass(subnode.tag, subnode))
        if (subnode.tag not in symbol_table):
          symbol_table[subnode.tag] = 1
          varnames += subnode.tag + " "
          if (not subnode.tag[0] == subnode.tag[0].upper()):
            varnameswithtypes += subnode.tag + ":array "
          else:
            varnameswithtypes += subnode.tag + ":object "
    
      print 'Building class for:', name
      for (key, value) in node.items():
        args.append(value)
        varnames += key + " "
        if (key == "variable_name"):
          varnameswithtypes+= key + ":" + value + " "
        elif (is_numeric(value)):
          varnameswithtypes+= key + ":numeric" + " "
        else:
          varnameswithtypes+= key + ":text" + " "
      varnames = varnames.strip()
      varnameswithtypes = varnameswithtypes.strip()
    
      if (_classes.has_key(name)):
        if (len(_classes[name]) < len(varnameswithtypes)):
          _classes[name] = varnameswithtypes
      else:
        _classes[name] = varnameswithtypes
    
      if (not name[0] == name[0].upper()):
        #this is an array, do not create a class, just return an array
        return args
      #print varnames, args
      globals()[name] = namedtuple(name, varnames)
      obj = globals()[name](*args)
      #print dump(obj)
      return obj
    

    Then added:

    _classdefs = {}
    def getClassDef(name):
      global _classdefs, _classes
      _classdefs[name] = "class " + name + "(object):\n   def __init__(self):\n"    
      classvars = _classes[name].split(" ")
      for x in classvars:
        vals = x.split(":")
        if (vals[1] == "array"):
          c = _classes[vals[0]].split(":")[0]
          if (not _classdefs.has_key(c)):
            getClassDef(c)
          _classdefs[name] += "    self." + vals[0] + " = []\n"
        elif (vals[1] == "text"):
          _classdefs[name] += "    self." + vals[0] + " = \"\"\n"
        elif (vals[1] == "numeric"):
          _classdefs[name] += "    self." + vals[0] + " = 0\n"
        elif (vals[1] == "object"):
          if (not _classdefs.has_key(vals[0])):
            getClassDef(vals[0])
          subclassvars = _classes[vals[0]].split(" ")
          for z in subclassvars:
            if (z.split(":")[0] == "variable_name"):
              _classdefs[name] += "    self." + z.split(":")[1] + " = " + vals[0] + "()\n"
    

    Which you call like this:

    getClassDef("RootObj")
    for x in _classdefs.keys():
      print _classdefs[x]
    

    Using xml:

    <RootObj val1="ValueOne" stat1="Stat1" stat2="Stat2">
      <internalarray>
        <InternalObject val1="12" val2="12" />
        <InternalObject val1="13" val2="13" />
        <InternalObject val1="14" val2="14" />
        <InternalObject val1="15" val2="15" />
      </internalarray>
      <InternalObject2 val1="12" val2="13" variable_name="intObj2" />
    </RootObj>
    

    The code will output:

    class RootObj(object):
       def __init__(self):
        self.internalarray = []
        self.intObj2 = InternalObject2()
        self.val1 = ""
        self.stat2 = ""
        self.stat1 = ""
    
    class InternalObject2(object):
       def __init__(self):
        self.val2 = 0
        self.val1 = 0
    
    class InternalObject(object):
       def __init__(self):
        self.val2 = 0
        self.val1 = 0
    

    Which you can then save to a .py file and import to use with the amfast ActionScript code generation as normal. Go me.