'''
Created on 2011-10-11
@author: jacekf

Responsible for converting return values into cleanly serializable dict/tuples/lists
for JSON/XML/YAML output
'''

import collections
import logging
import json
from twisted.python import log

primitives = (int, float, bool, str,str)

def convertForSerialization(obj):
    """Converts anything (clas,tuples,list) to the safe serializable equivalent"""
    try:
        if type(obj) in primitives:
        # no conversion
            return obj 
        elif isinstance(obj, dict):
            return traverseDict(obj)
        elif isClassInstance(obj):
            return convertClassToDict(obj)
        elif isinstance(obj,collections.Iterable) and not isinstance(obj,str):
            # iterable
            values = []
            for val in obj:
                values.append(convertForSerialization(val))
            return values
        else:
            # return as-is
            return obj
    except AttributeError as ex:
        log.msg(ex,logLevel=logging.WARN)
        return obj

def convertClassToDict(clazz):
    """Converts a class to a dictionary"""
    properties = {}
    for prop,val in clazz.__dict__.items():
        #omit private fields
        if not prop.startswith("_"):
            properties[prop] = val

    return traverseDict(properties)

def traverseDict(dictObject):
    """Traverses a dict recursively to convertForSerialization any nested classes"""
    newDict = {}

    for prop,val in dictObject.items():
        newDict[prop] = convertForSerialization(val)
    
    return newDict


def convertToJson(obj):
    """Converts to JSON, including Python classes that are not JSON serializable by default"""
    try:
        return json.dumps(obj)
    except Exception as ex:
        raise RuntimeError(str(ex))
    
def generateXml(obj):
    """Generates basic XML from an object that has already been converted for serialization"""
    if isinstance(obj, dict):
        return getXML_dict(obj, "item")
    elif isinstance(obj,collections.Iterable):
        return "<list>%s</list>" % getXML(obj, "item")
    else:
        raise RuntimeError("Unable to convert to XML: %s" % obj)    
    
def isClassInstance(obj):
    """Checks if a given obj is a class instance"""
    return getattr(obj, "__class__",None) != None and not isinstance(obj,dict) and not isinstance(obj,tuple) and not isinstance(obj,list) and not isinstance(obj,str)

## {{{ http://code.activestate.com/recipes/440595/ (r2)
def getXML(obj, objname=None):
    """getXML(obj, objname=None)
    returns an object as XML where Python object names are the tags.
    
    >>> u={'UserID':10,'Name':'Mark','Group':['Admin','Webmaster']}
    >>> getXML(u,'User')
    '<User><UserID>10</UserID><Name>Mark</Name><Group>Admin</Group><Group>Webmaster</Group></User>'
    """
    if obj == None:
        return ""
    if not objname:
        objname = "item"
    adapt={
        dict: getXML_dict,
        list: getXML_list,
        tuple: getXML_list,
        }
    if obj.__class__ in adapt:
        return adapt[obj.__class__](obj, objname)
    else:
        return "<%(n)s>%(o)s</%(n)s>"%{'n':objname,'o':str(obj)}

def getXML_dict(indict, objname=None):
    h = "<%s>"%objname
    for k, v in list(indict.items()):
        h += getXML(v, k)
    h += "</%s>"%objname
    return h

def getXML_list(inlist, objname=None):
    h = ""
    for i in inlist:
        h += getXML(i, objname)
    return h
## end of http://code.activestate.com/recipes/440595/ }}}