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

StackOverflow https://stackoverflow.com/questions/8567372

Question

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?

Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top