177 lines
5.2 KiB
Python
177 lines
5.2 KiB
Python
"""Tools for creating and manipulating SON, the Serialized Ocument Notation.
|
|
|
|
Regular dictionaries can be used instead of SON objects, but not when the order
|
|
of keys is important."""
|
|
|
|
import datetime
|
|
import re
|
|
import binascii
|
|
import base64
|
|
from UserDict import DictMixin
|
|
|
|
import ElementTree as ET
|
|
from objectid import ObjectId
|
|
from dbref import DBRef
|
|
from errors import UnsupportedTag
|
|
|
|
class SON(DictMixin):
|
|
"""SON data.
|
|
|
|
A subclass of dict that maintains ordering of keys and provides a few extra
|
|
niceties for dealing with SON.
|
|
"""
|
|
def __init__(self, data=None, **kwargs):
|
|
self.__keys = []
|
|
self.__data = {}
|
|
if data is not None:
|
|
if hasattr(data, 'items'):
|
|
items = data.iteritems()
|
|
else:
|
|
items = list(data)
|
|
for item in items:
|
|
if len(item) != 2:
|
|
raise ValueError("sequence elements must have length 2")
|
|
self.__keys.append(item[0])
|
|
self.__data[item[0]] = item[1]
|
|
if kwargs:
|
|
self.__merge_keys(kwargs.iterkeys())
|
|
self.update(kwargs)
|
|
|
|
def __merge_keys(self, keys):
|
|
self.__keys.extend(keys)
|
|
newkeys = {}
|
|
self.__keys = [newkeys.setdefault(x, x) for x in self.__keys if x not in newkeys]
|
|
|
|
def __repr__(self):
|
|
result = []
|
|
for key in self.__keys:
|
|
result.append("(%r, %r)" % (key, self.__data[key]))
|
|
return "SON([%s])" % ", ".join(result)
|
|
|
|
def update(self, data):
|
|
if data is not None:
|
|
if hasattr(data, "iterkeys"):
|
|
self.__merge_keys(data.iterkeys())
|
|
else:
|
|
self.__merge_keys(data.keys())
|
|
self.__data.update(data)
|
|
|
|
def __getitem__(self, key):
|
|
return self.__data[key]
|
|
|
|
def __setitem__(self, key, value):
|
|
if key not in self.__data:
|
|
self.__keys.append(key)
|
|
self.__data[key] = value
|
|
|
|
def __delitem__(self, key):
|
|
del self.__data[key]
|
|
self.__keys.remove(key)
|
|
|
|
def keys(self):
|
|
return list(self.__keys)
|
|
|
|
def copy(self):
|
|
other = SON()
|
|
other.__data = self.__data.copy()
|
|
other.__keys = self.__keys[:]
|
|
return other
|
|
|
|
@classmethod
|
|
def from_xml(cls, xml):
|
|
"""Create an instance of SON from an xml document.
|
|
"""
|
|
def pad(list, index):
|
|
while index >= len(list):
|
|
list.append(None)
|
|
|
|
def make_array(array):
|
|
doc = make_doc(array)
|
|
array = []
|
|
for (key, value) in doc.items():
|
|
index = int(key)
|
|
pad(array, index)
|
|
array[index] = value
|
|
return array
|
|
|
|
def make_string(string):
|
|
return string.text is not None and unicode(string.text) or u""
|
|
|
|
def make_binary(binary):
|
|
return binary.text is not None and base64.b64decode(binary.text) or ""
|
|
|
|
def make_boolean(bool):
|
|
return bool.text == "true"
|
|
|
|
def make_date(date):
|
|
return datetime.datetime.utcfromtimestamp(float(date.text) / 1000.0)
|
|
|
|
def make_ref(dbref):
|
|
return DBRef(make_elem(dbref[0]), make_elem(dbref[1]))
|
|
|
|
def make_oid(oid):
|
|
return ObjectId(binascii.unhexlify(oid.text))
|
|
|
|
def make_int(data):
|
|
return int(data.text)
|
|
|
|
def make_null(null):
|
|
return None
|
|
|
|
def make_number(number):
|
|
return float(number.text)
|
|
|
|
def make_regex(regex):
|
|
return re.compile(make_elem(regex[0]), make_elem(regex[1]))
|
|
|
|
def make_options(data):
|
|
options = 0
|
|
if not data.text:
|
|
return options
|
|
if "i" in data.text:
|
|
options |= re.IGNORECASE
|
|
if "l" in data.text:
|
|
options |= re.LOCALE
|
|
if "m" in data.text:
|
|
options |= re.MULTILINE
|
|
if "s" in data.text:
|
|
options |= re.DOTALL
|
|
if "u" in data.text:
|
|
options |= re.UNICODE
|
|
if "x" in data.text:
|
|
options |= re.VERBOSE
|
|
return options
|
|
|
|
def make_elem(elem):
|
|
try:
|
|
return {"array": make_array,
|
|
"doc": make_doc,
|
|
"string": make_string,
|
|
"binary": make_binary,
|
|
"boolean": make_boolean,
|
|
"code": make_string,
|
|
"date": make_date,
|
|
"ref": make_ref,
|
|
"ns": make_string,
|
|
"oid": make_oid,
|
|
"int": make_int,
|
|
"null": make_null,
|
|
"number": make_number,
|
|
"regex": make_regex,
|
|
"pattern": make_string,
|
|
"options": make_options,
|
|
}[elem.tag](elem)
|
|
except KeyError:
|
|
raise UnsupportedTag("cannot parse tag: %s" % elem.tag)
|
|
|
|
def make_doc(doc):
|
|
son = SON()
|
|
for elem in doc:
|
|
son[elem.attrib["name"]] = make_elem(elem)
|
|
return son
|
|
|
|
tree = ET.XML(xml)
|
|
doc = tree[1]
|
|
|
|
return make_doc(doc)
|