22
22
# importing this module is fairly slow because it has to load several
25
from bzrlib import registry
26
from bzrlib.trace import mutter, warning
26
from util.cElementTree import ElementTree, SubElement, Element
30
# it's in this package in python2.5
31
from xml.etree.cElementTree import (ElementTree, SubElement, Element,
32
XMLTreeBuilder, fromstring, tostring)
33
import xml.etree as elementtree
35
from cElementTree import (ElementTree, SubElement, Element,
36
XMLTreeBuilder, fromstring, tostring)
37
import elementtree.ElementTree
38
ParseError = SyntaxError
27
39
except ImportError:
28
from util.elementtree.ElementTree import ElementTree, SubElement, Element
40
mutter('WARNING: using slower ElementTree; consider installing cElementTree'
41
" and make sure it's on your PYTHONPATH")
42
# this copy is shipped with bzr
43
from util.elementtree.ElementTree import (ElementTree, SubElement,
44
Element, XMLTreeBuilder,
46
import util.elementtree as elementtree
47
from xml.parsers.expat import ExpatError as ParseError
30
from bzrlib.inventory import ROOT_ID, Inventory, InventoryEntry
31
from bzrlib.revision import Revision, RevisionReference
32
from bzrlib.errors import BzrError
49
from bzrlib import errors
35
52
class Serializer(object):
36
53
"""Abstract object serialize/deserialize"""
37
55
def write_inventory(self, inv, f):
38
56
"""Write inventory to a file"""
39
elt = self._pack_inventory(inv)
40
self._write_element(elt, f)
42
def read_inventory(self, f):
43
return self._unpack_inventory(self._read_element(f))
57
raise NotImplementedError(self.write_inventory)
59
def write_inventory_to_string(self, inv):
60
raise NotImplementedError(self.write_inventory_to_string)
62
def read_inventory_from_string(self, xml_string, revision_id=None):
63
"""Read xml_string into an inventory object.
65
:param xml_string: The xml to read.
66
:param revision_id: If not-None, the expected revision id of the
67
inventory. Some serialisers use this to set the results' root
68
revision. This should be supplied for deserialising all
69
from-repository inventories so that xml5 inventories that were
70
serialised without a revision identifier can be given the right
71
revision id (but not for working tree inventories where users can
72
edit the data without triggering checksum errors or anything).
75
return self._unpack_inventory(fromstring(xml_string), revision_id)
77
raise errors.UnexpectedInventoryFormat(e)
79
def read_inventory(self, f, revision_id=None):
81
return self._unpack_inventory(self._read_element(f),
84
raise errors.UnexpectedInventoryFormat(e)
45
86
def write_revision(self, rev, f):
46
87
self._write_element(self._pack_revision(rev), f)
89
def write_revision_to_string(self, rev):
90
return tostring(self._pack_revision(rev)) + '\n'
48
92
def read_revision(self, f):
49
93
return self._unpack_revision(self._read_element(f))
95
def read_revision_from_string(self, xml_string):
96
return self._unpack_revision(fromstring(xml_string))
51
98
def _write_element(self, elt, f):
52
99
ElementTree(elt).write(f, 'utf-8')
56
103
return ElementTree().parse(f)
60
class _Serializer_v4(Serializer):
61
"""Version 0.0.4 serializer
63
You should use the serialzer_v4 singleton."""
67
def _pack_inventory(self, inv):
68
"""Convert to XML Element"""
69
e = Element('inventory')
71
if inv.root.file_id not in (None, ROOT_ID):
72
e.set('file_id', inv.root.file_id)
73
for path, ie in inv.iter_entries():
74
e.append(self._pack_entry(ie))
78
def _pack_entry(self, ie):
79
"""Convert InventoryEntry to XML element"""
81
e.set('name', ie.name)
82
e.set('file_id', ie.file_id)
83
e.set('kind', ie.kind)
85
if ie.text_size != None:
86
e.set('text_size', '%d' % ie.text_size)
88
for f in ['text_id', 'text_sha1']:
93
# to be conservative, we don't externalize the root pointers
94
# for now, leaving them as null in the xml form. in a future
95
# version it will be implied by nested elements.
96
if ie.parent_id != ROOT_ID:
97
assert isinstance(ie.parent_id, basestring)
98
e.set('parent_id', ie.parent_id)
105
def _unpack_inventory(self, elt):
106
"""Construct from XML Element
108
assert elt.tag == 'inventory'
109
root_id = elt.get('file_id') or ROOT_ID
110
inv = Inventory(root_id)
112
ie = self._unpack_entry(e)
113
if ie.parent_id == ROOT_ID:
114
ie.parent_id = root_id
119
def _unpack_entry(self, elt):
120
assert elt.tag == 'entry'
122
## original format inventories don't have a parent_id for
123
## nodes in the root directory, but it's cleaner to use one
125
parent_id = elt.get('parent_id')
126
if parent_id == None:
129
ie = InventoryEntry(elt.get('file_id'),
133
ie.text_id = elt.get('text_id')
134
ie.text_sha1 = elt.get('text_sha1')
136
## mutter("read inventoryentry: %r" % (elt.attrib))
138
v = elt.get('text_size')
139
ie.text_size = v and int(v)
144
def _pack_revision(self, rev):
145
"""Revision object -> xml tree"""
146
root = Element('revision',
147
committer = rev.committer,
148
timestamp = '%.9f' % rev.timestamp,
149
revision_id = rev.revision_id,
150
inventory_id = rev.inventory_id,
151
inventory_sha1 = rev.inventory_sha1,
154
root.set('timezone', str(rev.timezone))
157
msg = SubElement(root, 'message')
158
msg.text = rev.message
162
pelts = SubElement(root, 'parents')
163
pelts.tail = pelts.text = '\n'
164
for rr in rev.parents:
165
assert isinstance(rr, RevisionReference)
166
p = SubElement(pelts, 'revision_ref')
168
assert rr.revision_id
169
p.set('revision_id', rr.revision_id)
171
p.set('revision_sha1', rr.revision_sha1)
176
def _unpack_revision(self, elt):
177
"""XML Element -> Revision object"""
179
# <changeset> is deprecated...
180
if elt.tag not in ('revision', 'changeset'):
181
raise BzrError("unexpected tag in revision file: %r" % elt)
183
rev = Revision(committer = elt.get('committer'),
184
timestamp = float(elt.get('timestamp')),
185
revision_id = elt.get('revision_id'),
186
inventory_id = elt.get('inventory_id'),
187
inventory_sha1 = elt.get('inventory_sha1')
190
precursor = elt.get('precursor')
191
precursor_sha1 = elt.get('precursor_sha1')
193
pelts = elt.find('parents')
197
assert p.tag == 'revision_ref', \
198
"bad parent node tag %r" % p.tag
199
rev_ref = RevisionReference(p.get('revision_id'),
200
p.get('revision_sha1'))
201
rev.parents.append(rev_ref)
205
prec_parent = rev.parents[0].revision_id
206
assert prec_parent == precursor
208
# revisions written prior to 0.0.5 have a single precursor
209
# give as an attribute
210
rev_ref = RevisionReference(precursor, precursor_sha1)
211
rev.parents.append(rev_ref)
213
v = elt.get('timezone')
214
rev.timezone = v and int(v)
216
rev.message = elt.findtext('message') # text of <message>
221
class _Serializer_v5(Serializer):
222
"""Version 5 serializer
224
Packs objects into XML and vice versa.
226
You should use the serialzer_v5 singleton."""
230
def _pack_inventory(self, inv):
231
"""Convert to XML Element"""
232
e = Element('inventory')
234
if inv.root.file_id not in (None, ROOT_ID):
235
e.set('file_id', inv.root.file_id)
236
for path, ie in inv.iter_entries():
237
e.append(self._pack_entry(ie))
241
def _pack_entry(self, ie):
242
"""Convert InventoryEntry to XML element"""
243
assert ie.kind == 'directory' or ie.kind == 'file'
245
e.set('name', ie.name)
246
e.set('file_id', ie.file_id)
248
if ie.text_size != None:
249
e.set('text_size', '%d' % ie.text_size)
251
for f in ['text_version', 'text_sha1', 'entry_version']:
256
# to be conservative, we don't externalize the root pointers
257
# for now, leaving them as null in the xml form. in a future
258
# version it will be implied by nested elements.
259
if ie.parent_id != ROOT_ID:
260
assert isinstance(ie.parent_id, basestring)
261
e.set('parent_id', ie.parent_id)
268
def _pack_revision(self, rev):
269
"""Revision object -> xml tree"""
270
root = Element('revision',
271
committer = rev.committer,
272
timestamp = '%.9f' % rev.timestamp,
273
revision_id = rev.revision_id,
274
inventory_id = rev.inventory_id,
275
inventory_sha1 = rev.inventory_sha1,
278
root.set('timezone', str(rev.timezone))
281
msg = SubElement(root, 'message')
282
msg.text = rev.message
286
pelts = SubElement(root, 'parents')
287
pelts.tail = pelts.text = '\n'
288
for rr in rev.parents:
289
assert isinstance(rr, RevisionReference)
290
p = SubElement(pelts, 'revision_ref')
292
assert rr.revision_id
293
p.set('revision_id', rr.revision_id)
299
def _unpack_inventory(self, elt):
300
"""Construct from XML Element
302
assert elt.tag == 'inventory'
303
root_id = elt.get('file_id') or ROOT_ID
304
inv = Inventory(root_id)
306
ie = self._unpack_entry(e)
307
if ie.parent_id == ROOT_ID:
308
ie.parent_id = root_id
313
def _unpack_entry(self, elt):
315
assert kind == 'directory' or kind == 'file'
317
parent_id = elt.get('parent_id')
318
if parent_id == None:
321
ie = InventoryEntry(elt.get('file_id'),
325
ie.text_version = elt.get('text_version')
326
ie.entry_version = elt.get('entry_version')
327
ie.text_sha1 = elt.get('text_sha1')
328
v = elt.get('text_size')
329
ie.text_size = v and int(v)
334
def _unpack_revision(self, elt):
335
"""XML Element -> Revision object"""
336
assert elt.tag == 'revision'
338
rev = Revision(committer = elt.get('committer'),
339
timestamp = float(elt.get('timestamp')),
340
revision_id = elt.get('revision_id'),
341
inventory_id = elt.get('inventory_id'),
342
inventory_sha1 = elt.get('inventory_sha1')
345
for p in elt.find('parents'):
346
assert p.tag == 'revision_ref', \
347
"bad parent node tag %r" % p.tag
348
rev_ref = RevisionReference(p.get('revision_id'))
349
rev.parents.append(rev_ref)
351
v = elt.get('timezone')
352
rev.timezone = v and int(v)
354
rev.message = elt.findtext('message') # text of <message>
359
"""singleton instance"""
360
serializer_v4 = _Serializer_v4()
362
serializer_v5 = _Serializer_v5()
106
# performance tuning for elementree's serialiser. This should be
107
# sent upstream - RBC 20060523.
108
# the functions here are patched into elementtree at runtime.
110
escape_re = re.compile("[&'\"<>]")
113
"'":"'", # FIXME: overkill
118
def _escape_replace(match, map=escape_map):
119
return map[match.group()]
121
def _escape_attrib(text, encoding=None, replace=None):
122
# escape attribute value
126
text = elementtree.ElementTree._encode(text, encoding)
128
return elementtree.ElementTree._encode_entity(text)
130
return escape_re.sub(_escape_replace, text)
132
text = replace(text, "&", "&")
133
text = replace(text, "'", "'") # FIXME: overkill
134
text = replace(text, "\"", """)
135
text = replace(text, "<", "<")
136
text = replace(text, ">", ">")
138
except (TypeError, AttributeError):
139
elementtree.ElementTree._raise_serialization_error(text)
141
elementtree.ElementTree._escape_attrib = _escape_attrib
143
escape_cdata_re = re.compile("[&<>]")
149
def _escape_cdata_replace(match, map=escape_cdata_map):
150
return map[match.group()]
152
def _escape_cdata(text, encoding=None, replace=None):
153
# escape character data
157
text = elementtree.ElementTree._encode(text, encoding)
159
return elementtree.ElementTree._encode_entity(text)
161
return escape_cdata_re.sub(_escape_cdata_replace, text)
163
text = replace(text, "&", "&")
164
text = replace(text, "<", "<")
165
text = replace(text, ">", ">")
167
except (TypeError, AttributeError):
168
elementtree.ElementTree._raise_serialization_error(text)
170
elementtree.ElementTree._escape_cdata = _escape_cdata
173
class SerializerRegistry(registry.Registry):
174
"""Registry for serializer objects"""
177
format_registry = SerializerRegistry()
178
format_registry.register_lazy('4', 'bzrlib.xml4', 'serializer_v4')
179
format_registry.register_lazy('5', 'bzrlib.xml5', 'serializer_v5')
180
format_registry.register_lazy('6', 'bzrlib.xml6', 'serializer_v6')
181
format_registry.register_lazy('7', 'bzrlib.xml7', 'serializer_v7')
182
format_registry.register_lazy('8', 'bzrlib.xml8', 'serializer_v8')