ANNOUNCE: xmlpickle.py 0.1

John Wiegley johnw at gnu.org
Wed Jul 26 19:59:00 EDT 2000


This is a very poor man's XML pickling scheme, but I was unable to
find anything else.

It also provides a way of doing disk mirroring of in-memory objects.
That is, the object is represented both in memory, as a Python object,
and on disk, as an XML data file.  Whichever one is changed first, the
other will be updated appropriately on the next access to the object.

To use this behavior, all that's necessary is:

    import xmlpickle

    class Derived(xmlpickle.Persistent):
        def __init__(self, name):
            xmlpickle.Persistent.__init__(self, name + ".xml")

All changes to Derived objects will now be mirrored to name.xml files,
which, if changed on disk, will alter their in-memory counterparts the
next time someone accesses them.

-----[ file: xmlpickle.py ]--------------------------------------------

import os
import os.path
import sys
import time
import types
import string

from xml.sax import saxexts
from xml.dom.core import TEXT_NODE
from xml.dom.builder import Builder
from xml.dom.sax_builder import SaxBuilder

class Renderable:
    def first_nontext_child(self, node, count = 0):
        for child in node.childNodes:
            if child.nodeType != TEXT_NODE:
                if count:
                    count = count - 1
                else:
                    return child

    def input_data(self, node):
        if node.nodeName == 'string':
            text = ""
            for child in node.childNodes:
                text = text + child.nodeValue
            return text
        elif node.nodeName == 'int':
            text = ""
            for child in node.childNodes:
                text = text + child.nodeValue
            return int(string.strip(text))
        elif node.nodeName == 'list':
            newlist = []
            for item in node.childNodes:
                if item.nodeName != 'listitem':
                    continue
                value = self.input_data(self.first_nontext_child(item))
                newlist.append(value)
            return newlist
        elif node.nodeName == 'tuple':
            newtuple = ()
            for item in node.childNodes:
                if item.nodeName != 'tupleitem':
                    continue
                value = self.input_data(self.first_nontext_child(item))
                newtuple = newtuple + (value,)
            return newtuple
        elif node.nodeName == 'hash':
            newhash = {}
            for item in node.childNodes:
                if item.nodeName != 'hashitem':
                    continue
                hi = self.first_nontext_child(item)
                hk = self.first_nontext_child(hi)
                key = self.input_data(hk)
                hi = self.first_nontext_child(item, 1)
                hv = self.first_nontext_child(hi)
                value = self.input_data(hv)
                newhash[key] = value
            return newhash
        elif node.nodeName == 'type':
            obj = eval(node.attributes['name'])()
            return self.input_data(obj, node)
        else:
            pass                        # ignore tag

    def input_object(self, obj, node):
        for child in node.childNodes:
            if child.nodeName != 'property':
                continue
            data = self.input_data(self.first_nontext_child(child))
            obj.__dict__[child.attributes['name'].value] = data
                    

    def read_from_xml(self, file):
        "Read in the XML description of the project."

        # Create a SAX parser and a SaxBuilder instance
        p = saxexts.make_parser()
        dh = SaxBuilder()
        p.setDocumentHandler(dh)

        # Parse the input, and close the parser
        data = open(file, "r")
        p.parseFile(data)
        p.close()
        data.close()

        # Retrieve the DOM tree
        doc = dh.document

        return self.input_object(self, doc.documentElement)

    def output_data(self, builder, value, depth):
        vtype = type(value)
        if vtype == types.InstanceType:
            # Recursively process subobjects
            self.do_convert_to_dom(builder, value, depth)
        elif vtype == types.StringType:
            builder.text("\n" + (" " * depth))
            builder.startElement('string')
            builder.text(value)
            builder.endElement('string')
        elif vtype == types.IntType:
            builder.text("\n" + (" " * depth))
            builder.startElement('int')
            builder.text(str(value))
            builder.endElement('int')
        elif vtype == types.ListType:
            builder.text("\n" + (" " * depth))
            builder.startElement('list')
            for member in value:
                builder.text("\n" + (" " * (depth + 2)))
                builder.startElement('listitem')
                self.output_data(builder, member, depth + 4)
                builder.text("\n" + (" " * (depth + 2)))
                builder.endElement('listitem')
            builder.text("\n" + (" " * depth))
            builder.endElement('list')
        elif vtype == types.TupleType:
            builder.text("\n" + (" " * depth))
            builder.startElement('tuple')
            for member in value:
                builder.text("\n" + (" " * (depth + 2)))
                builder.startElement('tupleitem')
                self.output_data(builder, member, depth + 4)
                builder.text("\n" + (" " * (depth + 2)))
                builder.endElement('tupleitem')
            builder.text("\n" + (" " * depth))
            builder.endElement('tuple')
        elif vtype == types.DictType:
            builder.text("\n" + (" " * depth))
            builder.startElement('hash')
            for key, value in value.items():
                builder.text("\n" + (" " * (depth + 2)))
                builder.startElement('hashitem')
                builder.text("\n" + (" " * (depth + 4)))
                builder.startElement('hashkey')
                self.output_data(builder, key, depth + 6)
                builder.text("\n" + (" " * (depth + 4)))
                builder.endElement('hashkey')
                builder.text("\n" + (" " * (depth + 4)))
                builder.startElement('hashvalue')
                self.output_data(builder, value, depth + 6)
                builder.text("\n" + (" " * (depth + 4)))
                builder.endElement('hashvalue')
                builder.text("\n" + (" " * (depth + 2)))
                builder.endElement('hashitem')
            builder.text("\n" + (" " * depth))
            builder.endElement('hash')

    def do_convert_to_dom(self, builder, obj, depth = 2):
        builder.startElement('type', {"name": obj.__class__.__name__})

        L = obj.__dict__.keys()
        L.sort()

        for attr in L:
            if attr[:1] == '__' or attr[:3] == "_v_":
                continue
            value = getattr(obj, attr)
            builder.text('\n' + (" " * depth))
            builder.startElement('property', {"name": attr})
            self.output_data(builder, value, depth + 2)
            builder.text('\n' + (" " * depth))
            builder.endElement('property')

        builder.text('\n')
        builder.endElement('type')

    def write_to_xml(self, file):
        builder = Builder()
        self.do_convert_to_dom(builder, self)
        data = open(file, "w")
        data.write(builder.document.toxml())
        data.close()

class Persistent(Renderable):
    def __init__(self, filename):
        self.__dict__['_v_filename'] = filename
        self._revert_object(force = 1)
    
    def _revert_object(self, force = 0):
        """Check the XML representation of this object.

        If the memory resident copy is out of date, read it from disk
        again."""
        if not self.__dict__.has_key('_v_filename'):
            return
        
        if not os.path.isfile(self.__dict__['_v_filename']):
            return

        mtime = os.stat(self.__dict__['_v_filename'])[8]

        if force or mtime > self.__dict__['_v_timestamp']:
            self.read_from_xml(self.__dict__['_v_filename'])
            self.__dict__['_v_timestamp'] = time.time()

    def _save_project(self):
        if not self.__dict__.has_key('_v_filename'):
            return

        self.write_to_xml(self._v_filename)
        self.__dict__['_v_timestamp'] = time.time()

    def __setattr__(self, prop, value):
        self.__dict__[prop] = value
        self._save_project()
        
    def __getattr__(self, prop):
        self._revert_object()
        return self.__dict__[prop]
        
    def __hasattr__(self, prop):
        self._revert_object()
        return self.__dict__.has_key(prop)



More information about the Python-list mailing list