Пример #1
0
    def mutate(self,obj):

        if isinstance(obj,UnicodeType):
            # unicode strings are required to be placed in the body
            # (by our encoding scheme)
            self.in_body = 1
        else:
            # XXX really should check getInBody(), but we'd have
            # to do isinstance() for each type ... maybe do later
            self.in_body = 0

        if isInstanceLike(obj) or hasPickleFuncs(obj):
            # obj has data items (list,dict,tuple) *AND* attributes.
            # mutate to an oldstyle object, turning the data items into
            # a special attribute (eg. __items__, __entries__).
            #
            # also, if obj defines the special pickling functions, we treat
            # it as an instance so we don't have to duplicate all the
            # protocol logic here.
            return XMLP_Mutated(newinst_to_oldinst(obj))
        else:
            # obj has only data items (list,dict,tuple,etc.)
            # convert to the raw datatype and remember the
            # module.class of obj for unpickling.
            (o,t) = newdata_to_olddata(obj)
            return XMLP_Mutated(o,t)
Пример #2
0
    def mutate(self, obj):

        if isinstance(obj, UnicodeType):
            # unicode strings are required to be placed in the body
            # (by our encoding scheme)
            self.in_body = 1
        else:
            # XXX really should check getInBody(), but we'd have
            # to do isinstance() for each type ... maybe do later
            self.in_body = 0

        if isInstanceLike(obj) or hasPickleFuncs(obj):
            # obj has data items (list,dict,tuple) *AND* attributes.
            # mutate to an oldstyle object, turning the data items into
            # a special attribute (eg. __items__, __entries__).
            #
            # also, if obj defines the special pickling functions, we treat
            # it as an instance so we don't have to duplicate all the
            # protocol logic here.
            return XMLP_Mutated(newinst_to_oldinst(obj))
        else:
            # obj has only data items (list,dict,tuple,etc.)
            # convert to the raw datatype and remember the
            # module.class of obj for unpickling.
            (o, t) = newdata_to_olddata(obj)
            return XMLP_Mutated(o, t)
Пример #3
0
 def __init__(self, py_obj=None):
     if py_obj is not None:
         #if type(py_obj) is InstanceType:
         if isInstanceLike(py_obj):
             self.to_pickle = py_obj
         else:
             raise XMLPicklingError, \
                   "XML_Pickler must be initialized with Instance (or None)"
Пример #4
0
 def __init__(self, py_obj=None):
     if py_obj is not None:
         #if type(py_obj) is InstanceType:
         if isInstanceLike(py_obj):
             self.to_pickle = py_obj
         else:
             raise XMLPicklingError, \
                   "XML_Pickler must be initialized with Instance (or None)"
Пример #5
0
def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy):
    tag_body = []

    (mtag, thing, in_body, mextra) = try_mutate(orig_thing, None,
                                                getInBody(type(orig_thing)),
                                                None)

    if type(thing) is NoneType:
        start_tag += "%s />\n" % (_family_type('none', 'None', None, None))
        close_tag = ''
    # looks like bool cannot be used as a base class, so if thing
    # is a bool it will always be BooleanType, and either True or False
    elif py_version >= '2.3' and type(thing) is BooleanType:
        if thing is True:
            typestr = 'True'
        else:  # must be False
            typestr = 'False'

        if in_body:
            start_tag +='%s>%s' % \
                      (_family_type('uniq',typestr,mtag,mextra),
                       '')
            close_tag = close_tag.lstrip()
        else:
            start_tag +='%s value="%s" />\n' % \
                      (_family_type('uniq',typestr,mtag,mextra),
                       '')
            close_tag = ''
    # ClassType will get caught by isInstanceLike(), which is not
    # what we want (also check for new-style class objects)
    elif isinstance(thing, ClassType) or isNewStyleClass(thing):
        module = thing.__module__
        if module:
            extra = 'module="%s" class="%s"' % (module, thing.__name__)
        else:
            extra = 'class="%s"' % _klass(thing.__name__)
        start_tag +='%s %s/>\n' % \
                     (_family_type('lang','class',mtag,mextra),extra)

        close_tag = ''
    # have to check for instance-like next since ints, etc., can be
    # instance-like in Python 2.2+. if it's really an object, we don't
    # want to fall through to the regular int,float,etc. code, since
    # that would skip the special handling in pickle_instance().
    elif isInstanceLike(thing):
        module = _module(thing)
        if module:
            extra = 'module="%s" class="%s"' % (module, _klass(thing))
        else:
            extra = 'class="%s"' % _klass(thing)
        start_tag, do_copy = \
                   _tag_compound(start_tag,_family_type('obj','PyObject',mtag,mextra),
                                 orig_thing, deepcopy, extra)
        # need to remember we've seen container before pickling subitems
        visited[id(orig_thing)] = orig_thing
        if do_copy:
            pickle_instance(thing, tag_body, level + 1, deepcopy)
        else:
            close_tag = ''
    elif isinstance_any(thing, (IntType, LongType, FloatType, ComplexType)):
        #thing_str = repr(thing)
        thing_str = ntoa(thing)

        if in_body:
            # we don't call safe_content() here since numerics won't
            # contain special XML chars.
            # the unpickler can either call unsafe_content() or not,
            # it won't matter
            start_tag +='%s>%s' % \
                      (_family_type('atom','numeric',mtag,mextra),
                       thing_str)
            close_tag = close_tag.lstrip()
        else:
            start_tag +='%s value="%s" />\n' % \
                      (_family_type('atom','numeric',mtag,mextra),thing_str)
            close_tag = ''
    elif isinstance_any(thing, (StringType, UnicodeType)):
        if in_body:
            start_tag +='%s>%s' % \
                      (_family_type('atom','string',mtag,mextra),
                       safe_content(thing))
            close_tag = close_tag.lstrip()
        else:
            start_tag +='%s value="%s" />\n' % \
                      (_family_type('atom','string',mtag,mextra),
                       safe_string(thing))
            close_tag = ''
    # General notes:
    #   1. When we make references, set type to referenced object
    #      type -- we don't need type when unpickling, but it may be useful
    #      to someone reading the XML file
    #   2. For containers, we have to stick the container into visited{}
    #      before pickling subitems, in case it contains self-references
    #      (we CANNOT just move the visited{} update to the top of this
    #      function, since that would screw up every _family_type() call)
    elif type(thing) is TupleType:
        start_tag, do_copy = \
                   _tag_compound(start_tag,_family_type('seq','tuple',mtag,mextra),
                                 orig_thing,deepcopy)
        if do_copy:
            for item in thing:
                tag_body.append(_item_tag(item, level + 1, deepcopy))
        else:
            close_tag = ''
    elif type(thing) is ListType:
        start_tag, do_copy = \
                   _tag_compound(start_tag,_family_type('seq','list',mtag,mextra),
                                 orig_thing,deepcopy)
        # need to remember we've seen container before pickling subitems
        visited[id(orig_thing)] = orig_thing
        if do_copy:
            for item in thing:
                tag_body.append(_item_tag(item, level + 1, deepcopy))
        else:
            close_tag = ''
    elif type(thing) in [DictType]:
        start_tag, do_copy = \
                   _tag_compound(start_tag,_family_type('map','dict',mtag,mextra),
                                 orig_thing,deepcopy)
        # need to remember we've seen container before pickling subitems
        visited[id(orig_thing)] = orig_thing
        if do_copy:
            for key, val in thing.items():
                tag_body.append(_entry_tag(key, val, level + 1, deepcopy))
        else:
            close_tag = ''
    elif type(thing) in [FunctionType, BuiltinFunctionType]:
        info = get_function_info(thing)
        # use module/class tags -- not perfect semantically, but better
        # that creating new attr names
        start_tag +='%s module="%s" class="%s"/>\n' % \
                     (_family_type('lang','function',mtag,mextra),
                      info[0],info[1])
        close_tag = ''
    else:
        # try using pickled value as the XML value tag.
        # rationale:  it won't be (easily) editable, but at least
        # you'll get valid XML even if your classes happen to
        # contain a few "foreign" types, and you don't feel like
        # writing a helper object (see gnosis.xml.pickle.ext for
        # how to do that)
        try:
            # we can't lookup the helper by type, since rawpickle can pickle
            # any pickleable class, so lookup by tag (unmutator) instead
            # (mutator & unmutator are always the same object)

            # always put rawpickles in the element body
            mutator = get_unmutator('rawpickle', None)
            thing = safe_content(mutator.mutate(thing).obj)
            start_tag += '%s>%s' % (_family_type('atom', None, 'rawpickle',
                                                 None), thing)
            close_tag = close_tag.lstrip()
        except:
            raise XMLPicklingError, "non-handled type %s" % type(thing)

    # need to keep a ref to the object for two reasons -
    #  1. we can ref it later instead of copying it into the XML stream
    #  2. need to keep temporary objects around so their ids don't get reused

    # if DEEPCOPY, we can skip this -- reusing ids is not an issue if we
    # never look at them
    if not deepcopy:
        visited[id(orig_thing)] = orig_thing

    return start_tag + ''.join(tag_body) + close_tag
Пример #6
0
def _pickle_toplevel_obj(xml_list, py_obj, deepcopy):
    "handle the top object -- add XML header, etc."

    # Store the ref id to the pickling object (if not deepcopying)
    global visited
    visited = {}
    if not deepcopy:
        id_ = id(py_obj)
        visited[id_] = py_obj

    # note -- setting family="obj" lets us know that a mutator was used on
    # the object. Otherwise, it's tricky to unpickle both <PyObject ...>
    # and <.. type="PyObject" ..> with the same code. Having family="obj" makes
    # it clear that we should slurp in a 'typeless' object and unmutate it.

    # note 2 -- need to add type= to <PyObject> when using mutators.
    # this is b/c a mutated object can still have a class= and
    # module= that we need to read before unmutating (i.e. the mutator
    # mutated into a PyObject)

    famtype = ''  # unless we have to, don't add family= and type=

    #if type(py_obj) is not InstanceType:
    if not isInstanceLike(py_obj):
        # use our wrapper-mutator to pickle builtin types.
        # get by name since mutator classtype is None
        mutator = get_unmutator('builtin_wrapper', None)
        # wrapper must not have an id (otherwise the wrapper AND the
        # wrapper-obj would appear to have the same id)
        if not deepcopy:
            del visited[id_]
        id_ = None
        py_obj = mutator.mutate(py_obj).obj
        famtype += 'family="obj" type="%s" ' % mutator.tag
        # don't show module for wrapped types
        module = None
        # refs get funny here, but since this is a special case, and we're
        # only pickling a single object, turning deepcopy off is easiest
        #deepcopy = 1
    else:
        if can_mutate(py_obj):
            (mtype, py_obj, in_body, extra) = mutate(py_obj)
            # sanity check until/if we eventually support these
            # at the toplevel
            if in_body or extra:
                raise XMLPicklingError, \
                      "Sorry, mutators can't set in_body and/or extra at the toplevel."
            famtype += 'family="obj" type="%s" ' % mtype

        module = _module(py_obj)

    klass_tag = _klass(py_obj)

    # Generate the XML string
    if module:
        extra = '%smodule="%s" class="%s"' % (famtype, module, klass_tag)
    else:
        extra = '%s class="%s"' % (famtype, klass_tag)

    xml_list.append('<?xml version="1.0"?>\n' +
                    '<!DOCTYPE PyObject SYSTEM "PyObjects.dtd">\n')

    if deepcopy:
        xml_list.append('<PyObject %s>\n' % (extra))
    elif id_ is not None:
        xml_list.append('<PyObject %s id="%s">\n' % (extra, id_))
    else:
        xml_list.append('<PyObject %s>\n' % (extra))

    pickle_instance(py_obj, xml_list, level=0, deepcopy=deepcopy)
    xml_list.append('</PyObject>\n')

    # returns None if xml_list is a fileobj, but caller should
    # know that (or not care)
    return xml_list.getvalue()
Пример #7
0
def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy):
    tag_body = []

    (mtag,thing,in_body,mextra) = try_mutate(orig_thing,None,
                                             getInBody(type(orig_thing)), None)

    if type(thing) is NoneType:
        start_tag = start_tag + "%s />\n" % (_family_type('none','None',None,None))
        close_tag = ''
    # bool cannot be used as a base class (see sanity check above) so if thing
    # is a bool it will always be BooleanType, and either True or False
    elif Have_BoolClass and type(thing) is BooleanType:
        if thing is True:
            typestr = 'True'
        else: # must be False
            typestr = 'False'

        if in_body:
            start_tag = start_tag + '%s>%s' % \
                      (_family_type('uniq',typestr,mtag,mextra),
                       '')
            close_tag = close_tag.lstrip()
        else:
            start_tag = start_tag + '%s value="%s" />\n' % \
                      (_family_type('uniq',typestr,mtag,mextra),
                       '')
            close_tag = ''
    # ClassType will get caught by isInstanceLike(), which is not
    # what we want. There are two cases here - the first catches
    # old-style classes, the second catches new-style classes.
    elif isinstance(thing,ClassType) or isNewStyleClass(thing):
        module = thing.__module__
        if module:
            extra = 'module="%s" class="%s"' % (module, thing.__name__)
        else:
            extra = 'class="%s"' % _klass(thing.__name__)
        start_tag = start_tag + '%s %s/>\n' % \
                     (_family_type('lang','class',mtag,mextra),extra)

        close_tag = ''
    # have to check for instance-like next since ints, etc., can be
    # instance-like in Python 2.2+. if it's really an object, we don't
    # want to fall through to the regular int,float,etc. code, since
    # that would skip the special handling in pickle_instance().
    elif isInstanceLike(thing):
        module = _module(thing)
        if module:
            extra = 'module="%s" class="%s"' % (module, _klass(thing))
        else:
            extra = 'class="%s"' % _klass(thing)
        start_tag, do_copy = \
                   _tag_compound(start_tag,_family_type('obj','PyObject',mtag,mextra),
                                 orig_thing, deepcopy, extra)
        # need to remember we've seen container before pickling subitems
        visited[id(orig_thing)] = orig_thing
        if do_copy:
            pickle_instance(thing, tag_body, level+1, deepcopy)
        else:
            close_tag = ''
    elif isinstance_any(thing, (IntType, LongType, FloatType, ComplexType)):
        #thing_str = repr(thing)
        thing_str = ntoa(thing)

        if in_body:
            # we don't call safe_content() here since numerics won't
            # contain special XML chars.
            # the unpickler can either call unsafe_content() or not,
            # it won't matter
            start_tag = start_tag + '%s>%s' % \
                      (_family_type('atom','numeric',mtag,mextra),
                       thing_str)
            close_tag = close_tag.lstrip()
        else:
            start_tag = start_tag + '%s value="%s" />\n' % \
                      (_family_type('atom','numeric',mtag,mextra),thing_str)
            close_tag = ''
    elif isinstance_any(thing, (StringType,UnicodeType)):
        #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
        # special check for now - this will be fixed in the next major
        # gnosis release, so I don't care that the code is inline & gross
        # for now
        #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX       
        if isinstance(thing,UnicodeType):
            # can't pickle unicode containing the special "escape" sequence
            # we use for putting strings in the XML body (they'll be unpickled
            # as strings, not unicode, if we do!)
            if thing[0:2] == u'\xbb\xbb' and thing[-2:] == u'\xab\xab':
                raise Exception("Unpickleable Unicode value. To be fixed in next major Gnosis release.")
        
            # see if it contains any XML-illegal values
            if not is_legal_xml(thing):
                raise Exception("Unpickleable Unicode value. To be fixed in next major Gnosis release.")

        if isinstance(thing,StringType) and getInBody(StringType):
            # technically, this will crash safe_content(), but I prefer to
            # have the test here for clarity
            try:
                # safe_content assumes it can always convert the string
                # to unicode, which isn't true (eg. pickle a UTF-8 value)
                u = unicode(thing)
            except:
                raise Exception("Unpickleable string value (%s). To be fixed in next major Gnosis release." % repr(thing))

        #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
        # End of temporary hack code
        #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
            
        if in_body:
            start_tag = start_tag + '%s>%s' % \
                      (_family_type('atom','string',mtag,mextra),
                       safe_content(thing))
            close_tag = close_tag.lstrip()
        else:
            start_tag = start_tag + '%s value="%s" />\n' % \
                      (_family_type('atom','string',mtag,mextra),
                       safe_string(thing))
            close_tag = ''
    # General notes:
    #	1. When we make references, set type to referenced object
    #	   type -- we don't need type when unpickling, but it may be useful
    #	   to someone reading the XML file
    #	2. For containers, we have to stick the container into visited{}
    #	   before pickling subitems, in case it contains self-references
    #	   (we CANNOT just move the visited{} update to the top of this
    #	   function, since that would screw up every _family_type() call)
    elif type(thing) is TupleType:
        start_tag, do_copy = \
                   _tag_compound(start_tag,_family_type('seq','tuple',mtag,mextra),
                                 orig_thing,deepcopy)
        if do_copy:
            for item in thing:
                tag_body.append(_item_tag(item, level+1, deepcopy))
        else:
            close_tag = ''
    elif type(thing) is ListType:
        start_tag, do_copy = \
                   _tag_compound(start_tag,_family_type('seq','list',mtag,mextra),
                                 orig_thing,deepcopy)
        # need to remember we've seen container before pickling subitems
        visited[id(orig_thing)] = orig_thing
        if do_copy:
            for item in thing:
                tag_body.append(_item_tag(item, level+1, deepcopy))
        else:
            close_tag = ''
    elif type(thing) in [DictType]:
        start_tag, do_copy = \
                   _tag_compound(start_tag,_family_type('map','dict',mtag,mextra),
                                 orig_thing,deepcopy)
        # need to remember we've seen container before pickling subitems
        visited[id(orig_thing)] = orig_thing
        if do_copy:
            for key, val in thing.items():
                tag_body.append(_entry_tag(key, val, level+1, deepcopy))
        else:
            close_tag = ''
    elif type(thing) in [FunctionType,BuiltinFunctionType]:
        info = get_function_info(thing)
        # use module/class tags -- not perfect semantically, but better
        # that creating new attr names
        start_tag = start_tag + '%s module="%s" class="%s"/>\n' % \
                     (_family_type('lang','function',mtag,mextra),
                      info[0],info[1])
        close_tag = ''
    else:
        # try using pickled value as the XML value tag.
        # rationale:  it won't be (easily) editable, but at least
        # you'll get valid XML even if your classes happen to
        # contain a few "foreign" types, and you don't feel like
        # writing a helper object (see gnosis.xml.pickle.ext for
        # how to do that)
        try:
            # we can't lookup the helper by type, since rawpickle can pickle
            # any pickleable class, so lookup by tag (unmutator) instead
            # (mutator & unmutator are always the same object)

            # always put rawpickles in the element body
            mutator = get_unmutator('rawpickle',None)
            thing = safe_content(mutator.mutate(thing).obj)
            start_tag = start_tag + '%s>%s' % (_family_type('atom',None,'rawpickle',None),
                                   thing)
            close_tag = close_tag.lstrip()
        except:
            raise XMLPicklingError, "non-handled type %s" % type(thing)

    # need to keep a ref to the object for two reasons -
    #  1. we can ref it later instead of copying it into the XML stream
    #  2. need to keep temporary objects around so their ids don't get reused

    # if DEEPCOPY, we can skip this -- reusing ids is not an issue if we
    # never look at them
    if not deepcopy:
        visited[id(orig_thing)] = orig_thing

    return start_tag + ''.join(tag_body) + close_tag
Пример #8
0
def _pickle_toplevel_obj(xml_list, py_obj, deepcopy):
    "handle the top object -- add XML header, etc."

    # Store the ref id to the pickling object (if not deepcopying)
    global visited
    visited = {}
    if not deepcopy:
        id_ = id(py_obj)
        visited[id_] = py_obj

    # note -- setting family="obj" lets us know that a mutator was used on
    # the object. Otherwise, it's tricky to unpickle both <PyObject ...>
    # and <.. type="PyObject" ..> with the same code. Having family="obj" makes
    # it clear that we should slurp in a 'typeless' object and unmutate it.

    # note 2 -- need to add type= to <PyObject> when using mutators.
    # this is b/c a mutated object can still have a class= and
    # module= that we need to read before unmutating (i.e. the mutator
    # mutated into a PyObject)

    famtype = '' # unless we have to, don't add family= and type=

    #if type(py_obj) is not InstanceType:
    if not isInstanceLike(py_obj):
        # use our wrapper-mutator to pickle builtin types.
        # get by name since mutator classtype is None
        mutator = get_unmutator('builtin_wrapper',None)
        # wrapper must not have an id (otherwise the wrapper AND the
        # wrapper-obj would appear to have the same id)
        if not deepcopy:
            del visited[id_]
        id_ = None
        py_obj = mutator.mutate(py_obj).obj
        famtype = famtype + 'family="obj" type="%s" ' % mutator.tag
        # don't show module for wrapped types
        module = None
        # refs get funny here, but since this is a special case, and we're
        # only pickling a single object, turning deepcopy off is easiest
        #deepcopy = 1
    else:
        if can_mutate(py_obj):
            (mtype,py_obj,in_body,extra) = mutate(py_obj)
            # sanity check until/if we eventually support these
            # at the toplevel
            if in_body or extra:
                raise XMLPicklingError, \
                      "Sorry, mutators can't set in_body and/or extra at the toplevel."
            famtype = famtype + 'family="obj" type="%s" ' % mtype

        module = _module(py_obj)

    klass_tag = _klass(py_obj)

    # Generate the XML string
    if module: extra = '%smodule="%s" class="%s"' % (famtype,module,klass_tag)
    else:	   extra = '%s class="%s"' % (famtype,klass_tag)

    xml_list.append('<?xml version="1.0"?>\n'+
                    '<!DOCTYPE PyObject SYSTEM "PyObjects.dtd">\n')

    if deepcopy:
        xml_list.append('<PyObject %s>\n' % (extra))
    elif id_ is not None:
        xml_list.append('<PyObject %s id="%s">\n' % (extra, id_))
    else:
        xml_list.append('<PyObject %s>\n' % (extra))

    pickle_instance(py_obj, xml_list, level=0, deepcopy=deepcopy)
    xml_list.append('</PyObject>\n')

    # returns None if xml_list is a fileobj, but caller should
    # know that (or not care)
    return xml_list.getvalue()
Пример #9
0
def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy):
    tag_body = []

    (mtag, thing, in_body, mextra) = try_mutate(orig_thing, None,
                                                getInBody(type(orig_thing)),
                                                None)

    if type(thing) is type(None):
        start_tag = start_tag + "%s />\n" % (_family_type(
            'none', 'None', None, None))
        close_tag = ''
    # bool cannot be used as a base class (see sanity check above) so if thing
    # is a bool it will always be BooleanType, and either True or False
    elif Have_BoolClass and type(thing) is bool:
        if thing is True:
            typestr = 'True'
        else:  # must be False
            typestr = 'False'

        if in_body:
            start_tag = start_tag + '%s>%s' % \
                      (_family_type('uniq',typestr,mtag,mextra),
                       '')
            close_tag = close_tag.lstrip()
        else:
            start_tag = start_tag + '%s value="%s" />\n' % \
                      (_family_type('uniq',typestr,mtag,mextra),
                       '')
            close_tag = ''
    # ClassType will get caught by isInstanceLike(), which is not
    # what we want. There are two cases here - the first catches
    # old-style classes, the second catches new-style classes.
    elif isinstance(thing, type) or isNewStyleClass(thing):
        module = thing.__module__
        if module:
            extra = 'module="%s" class="%s"' % (module, thing.__name__)
        else:
            extra = 'class="%s"' % _klass(thing.__name__)
        start_tag = start_tag + '%s %s/>\n' % \
                     (_family_type('lang','class',mtag,mextra),extra)

        close_tag = ''
    # have to check for instance-like next since ints, etc., can be
    # instance-like in Python 2.2+. if it's really an object, we don't
    # want to fall through to the regular int,float,etc. code, since
    # that would skip the special handling in pickle_instance().
    elif isInstanceLike(thing):
        module = _module(thing)
        if module:
            extra = 'module="%s" class="%s"' % (module, _klass(thing))
        else:
            extra = 'class="%s"' % _klass(thing)
        start_tag, do_copy = \
                   _tag_compound(start_tag,_family_type('obj','PyObject',mtag,mextra),
                                 orig_thing, deepcopy, extra)
        # need to remember we've seen container before pickling subitems
        visited[id(orig_thing)] = orig_thing
        if do_copy:
            pickle_instance(thing, tag_body, level + 1, deepcopy)
        else:
            close_tag = ''
    elif isinstance_any(thing, (int, int, float, complex)):
        #thing_str = repr(thing)
        thing_str = ntoa(thing)

        if in_body:
            # we don't call safe_content() here since numerics won't
            # contain special XML chars.
            # the unpickler can either call unsafe_content() or not,
            # it won't matter
            start_tag = start_tag + '%s>%s' % \
                      (_family_type('atom','numeric',mtag,mextra),
                       thing_str)
            close_tag = close_tag.lstrip()
        else:
            start_tag = start_tag + '%s value="%s" />\n' % \
                      (_family_type('atom','numeric',mtag,mextra),thing_str)
            close_tag = ''
    elif isinstance_any(thing, (bytes, str)):
        #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
        # special check for now - this will be fixed in the next major
        # gnosis release, so I don't care that the code is inline & gross
        # for now
        #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
        if isinstance(thing, str):
            # can't pickle unicode containing the special "escape" sequence
            # we use for putting strings in the XML body (they'll be unpickled
            # as strings, not unicode, if we do!)
            if thing[0:2] == '\xbb\xbb' and thing[-2:] == '\xab\xab':
                raise Exception(
                    "Unpickleable Unicode value. To be fixed in next major Gnosis release."
                )

            # see if it contains any XML-illegal values
            if not is_legal_xml(thing):
                raise Exception(
                    "Unpickleable Unicode value. To be fixed in next major Gnosis release."
                )

        if isinstance(thing, bytes) and getInBody(bytes):
            # technically, this will crash safe_content(), but I prefer to
            # have the test here for clarity
            try:
                # safe_content assumes it can always convert the string
                # to unicode, which isn't true (eg. pickle a UTF-8 value)
                u = str(thing)
            except:
                raise Exception(
                    "Unpickleable string value (%s). To be fixed in next major Gnosis release."
                    % repr(thing))

        #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
        # End of temporary hack code
        #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

        if in_body:
            start_tag = start_tag + '%s>%s' % \
                      (_family_type('atom','string',mtag,mextra),
                       safe_content(thing))
            close_tag = close_tag.lstrip()
        else:
            start_tag = start_tag + '%s value="%s" />\n' % \
                      (_family_type('atom','string',mtag,mextra),
                       safe_string(thing))
            close_tag = ''
    # General notes:
    #	1. When we make references, set type to referenced object
    #	   type -- we don't need type when unpickling, but it may be useful
    #	   to someone reading the XML file
    #	2. For containers, we have to stick the container into visited{}
    #	   before pickling subitems, in case it contains self-references
    #	   (we CANNOT just move the visited{} update to the top of this
    #	   function, since that would screw up every _family_type() call)
    elif type(thing) is tuple:
        start_tag, do_copy = \
                   _tag_compound(start_tag,_family_type('seq','tuple',mtag,mextra),
                                 orig_thing,deepcopy)
        if do_copy:
            for item in thing:
                tag_body.append(_item_tag(item, level + 1, deepcopy))
        else:
            close_tag = ''
    elif type(thing) is list:
        start_tag, do_copy = \
                   _tag_compound(start_tag,_family_type('seq','list',mtag,mextra),
                                 orig_thing,deepcopy)
        # need to remember we've seen container before pickling subitems
        visited[id(orig_thing)] = orig_thing
        if do_copy:
            for item in thing:
                tag_body.append(_item_tag(item, level + 1, deepcopy))
        else:
            close_tag = ''
    elif type(thing) in [dict]:
        start_tag, do_copy = \
                   _tag_compound(start_tag,_family_type('map','dict',mtag,mextra),
                                 orig_thing,deepcopy)
        # need to remember we've seen container before pickling subitems
        visited[id(orig_thing)] = orig_thing
        if do_copy:
            for key, val in list(thing.items()):
                tag_body.append(_entry_tag(key, val, level + 1, deepcopy))
        else:
            close_tag = ''
    elif type(thing) in [FunctionType, BuiltinFunctionType]:
        info = get_function_info(thing)
        # use module/class tags -- not perfect semantically, but better
        # that creating new attr names
        start_tag = start_tag + '%s module="%s" class="%s"/>\n' % \
                     (_family_type('lang','function',mtag,mextra),
                      info[0],info[1])
        close_tag = ''
    else:
        # try using pickled value as the XML value tag.
        # rationale:  it won't be (easily) editable, but at least
        # you'll get valid XML even if your classes happen to
        # contain a few "foreign" types, and you don't feel like
        # writing a helper object (see gnosis.xml.pickle.ext for
        # how to do that)
        try:
            # we can't lookup the helper by type, since rawpickle can pickle
            # any pickleable class, so lookup by tag (unmutator) instead
            # (mutator & unmutator are always the same object)

            # always put rawpickles in the element body
            mutator = get_unmutator('rawpickle', None)
            thing = safe_content(mutator.mutate(thing).obj)
            start_tag = start_tag + '%s>%s' % (_family_type(
                'atom', None, 'rawpickle', None), thing)
            close_tag = close_tag.lstrip()
        except:
            raise XMLPicklingError("non-handled type %s" % type(thing))

    # need to keep a ref to the object for two reasons -
    #  1. we can ref it later instead of copying it into the XML stream
    #  2. need to keep temporary objects around so their ids don't get reused

    # if DEEPCOPY, we can skip this -- reusing ids is not an issue if we
    # never look at them
    if not deepcopy:
        visited[id(orig_thing)] = orig_thing

    return start_tag + ''.join(tag_body) + close_tag