2
# -*- coding: UTF-8 -*-
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
"""XML externalization support."""
20
# "XML is like violence: if it doesn't solve your problem, you aren't
21
# using enough of it." -- various
23
# importing this module is fairly slow because it has to load several
27
from util.cElementTree import ElementTree, SubElement, Element
29
from util.elementtree.ElementTree import ElementTree, SubElement, Element
31
from bzrlib.inventory import ROOT_ID, Inventory, InventoryEntry
32
from bzrlib.revision import Revision, RevisionReference
33
from bzrlib.errors import BzrError
36
class Serializer(object):
37
"""Abstract object serialize/deserialize"""
38
def write_inventory(self, inv, f):
39
"""Write inventory to a file"""
40
elt = self._pack_inventory(inv)
41
self._write_element(elt, f)
43
def read_inventory(self, f):
44
return self._unpack_inventory(self._read_element(f))
46
def write_revision(self, rev, f):
47
self._write_element(self._pack_revision(rev), f)
49
def read_revision(self, f):
50
return self._unpack_revision(self._read_element(f))
52
def _write_element(self, elt, f):
53
ElementTree(elt).write(f, 'utf-8')
56
def _read_element(self, f):
57
return ElementTree().parse(f)
61
class _Serializer_v4(Serializer):
62
"""Version 0.0.4 serializer
64
You should use the serialzer_v4 singleton."""
68
def _pack_inventory(self, inv):
69
"""Convert to XML Element"""
70
e = Element('inventory')
72
if inv.root.file_id not in (None, ROOT_ID):
73
e.set('file_id', inv.root.file_id)
74
for path, ie in inv.iter_entries():
75
e.append(self._pack_entry(ie))
79
def _pack_entry(self, ie):
80
"""Convert InventoryEntry to XML element"""
82
e.set('name', ie.name)
83
e.set('file_id', ie.file_id)
84
e.set('kind', ie.kind)
86
if ie.text_size != None:
87
e.set('text_size', '%d' % ie.text_size)
89
for f in ['text_id', 'text_sha1']:
94
# to be conservative, we don't externalize the root pointers
95
# for now, leaving them as null in the xml form. in a future
96
# version it will be implied by nested elements.
97
if ie.parent_id != ROOT_ID:
98
assert isinstance(ie.parent_id, basestring)
99
e.set('parent_id', ie.parent_id)
106
def _unpack_inventory(self, elt):
107
"""Construct from XML Element
109
assert elt.tag == 'inventory'
110
root_id = elt.get('file_id') or ROOT_ID
111
inv = Inventory(root_id)
113
ie = self._unpack_entry(e)
114
if ie.parent_id == ROOT_ID:
115
ie.parent_id = root_id
120
def _unpack_entry(self, elt):
121
assert elt.tag == 'entry'
123
## original format inventories don't have a parent_id for
124
## nodes in the root directory, but it's cleaner to use one
126
parent_id = elt.get('parent_id')
127
if parent_id == None:
130
ie = InventoryEntry(elt.get('file_id'),
134
ie.text_id = elt.get('text_id')
135
ie.text_sha1 = elt.get('text_sha1')
137
## mutter("read inventoryentry: %r" % (elt.attrib))
139
v = elt.get('text_size')
140
ie.text_size = v and int(v)
145
def _pack_revision(self, rev):
146
"""Revision object -> xml tree"""
147
root = Element('revision',
148
committer = rev.committer,
149
timestamp = '%.9f' % rev.timestamp,
150
revision_id = rev.revision_id,
151
inventory_id = rev.inventory_id,
152
inventory_sha1 = rev.inventory_sha1,
155
root.set('timezone', str(rev.timezone))
158
msg = SubElement(root, 'message')
159
msg.text = rev.message
163
pelts = SubElement(root, 'parents')
164
pelts.tail = pelts.text = '\n'
165
for rr in rev.parents:
166
assert isinstance(rr, RevisionReference)
167
p = SubElement(pelts, 'revision_ref')
169
assert rr.revision_id
170
p.set('revision_id', rr.revision_id)
172
p.set('revision_sha1', rr.revision_sha1)
177
def _unpack_revision(self, elt):
178
"""XML Element -> Revision object"""
180
# <changeset> is deprecated...
181
if elt.tag not in ('revision', 'changeset'):
182
raise BzrError("unexpected tag in revision file: %r" % elt)
184
rev = Revision(committer = elt.get('committer'),
185
timestamp = float(elt.get('timestamp')),
186
revision_id = elt.get('revision_id'),
187
inventory_id = elt.get('inventory_id'),
188
inventory_sha1 = elt.get('inventory_sha1')
191
precursor = elt.get('precursor')
192
precursor_sha1 = elt.get('precursor_sha1')
194
pelts = elt.find('parents')
198
assert p.tag == 'revision_ref', \
199
"bad parent node tag %r" % p.tag
200
rev_ref = RevisionReference(p.get('revision_id'),
201
p.get('revision_sha1'))
202
rev.parents.append(rev_ref)
206
prec_parent = rev.parents[0].revision_id
207
assert prec_parent == precursor
209
# revisions written prior to 0.0.5 have a single precursor
210
# give as an attribute
211
rev_ref = RevisionReference(precursor, precursor_sha1)
212
rev.parents.append(rev_ref)
214
v = elt.get('timezone')
215
rev.timezone = v and int(v)
217
rev.message = elt.findtext('message') # text of <message>
222
class _Serializer_v5(Serializer):
223
"""Version 5 serializer
225
You should use the serialzer_v5 singleton."""
229
def _pack_inventory(self, inv):
230
"""Convert to XML Element"""
231
e = Element('inventory')
233
if inv.root.file_id not in (None, ROOT_ID):
234
e.set('file_id', inv.root.file_id)
235
for path, ie in inv.iter_entries():
236
e.append(self._pack_entry(ie))
240
def _pack_entry(self, ie):
241
"""Convert InventoryEntry to XML element"""
243
e.set('name', ie.name)
244
e.set('file_id', ie.file_id)
245
e.set('kind', ie.kind)
247
if ie.text_size != None:
248
e.set('text_size', '%d' % ie.text_size)
250
for f in ['text_version', 'text_sha1', 'entry_version']:
255
# to be conservative, we don't externalize the root pointers
256
# for now, leaving them as null in the xml form. in a future
257
# version it will be implied by nested elements.
258
if ie.parent_id != ROOT_ID:
259
assert isinstance(ie.parent_id, basestring)
260
e.set('parent_id', ie.parent_id)
267
def _pack_revision(self, rev):
268
"""Revision object -> xml tree"""
269
root = Element('revision',
270
committer = rev.committer,
271
timestamp = '%.9f' % rev.timestamp,
272
revision_id = rev.revision_id,
273
inventory_id = rev.inventory_id,
274
inventory_sha1 = rev.inventory_sha1,
277
root.set('timezone', str(rev.timezone))
280
msg = SubElement(root, 'message')
281
msg.text = rev.message
285
pelts = SubElement(root, 'parents')
286
pelts.tail = pelts.text = '\n'
287
for rr in rev.parents:
288
assert isinstance(rr, RevisionReference)
289
p = SubElement(pelts, 'revision_ref')
291
assert rr.revision_id
292
p.set('revision_id', rr.revision_id)
298
def _unpack_inventory(self, elt):
299
"""Construct from XML Element
301
assert elt.tag == 'inventory'
302
root_id = elt.get('file_id') or ROOT_ID
303
inv = Inventory(root_id)
305
ie = self._unpack_entry(e)
306
if ie.parent_id == ROOT_ID:
307
ie.parent_id = root_id
312
def _unpack_entry(self, elt):
314
assert kind == 'directory' or kind == 'file'
316
parent_id = elt.get('parent_id')
317
if parent_id == None:
320
ie = InventoryEntry(elt.get('file_id'),
324
ie.text_version = elt.get('text_version')
325
ie.entry_version = elt.get('entry_version')
326
ie.text_sha1 = elt.get('text_sha1')
327
v = elt.get('text_size')
328
ie.text_size = v and int(v)
333
def _unpack_revision(self, elt):
334
"""XML Element -> Revision object"""
335
assert elt.tag == 'revision'
337
rev = Revision(committer = elt.get('committer'),
338
timestamp = float(elt.get('timestamp')),
339
revision_id = elt.get('revision_id'),
340
inventory_id = elt.get('inventory_id'),
341
inventory_sha1 = elt.get('inventory_sha1')
344
for p in elt.find('parents'):
345
assert p.tag == 'revision_ref', \
346
"bad parent node tag %r" % p.tag
347
rev_ref = RevisionReference(p.get('revision_id'))
348
rev.parents.append(rev_ref)
350
v = elt.get('timezone')
351
rev.timezone = v and int(v)
353
rev.message = elt.findtext('message') # text of <message>
358
"""singleton instance"""
359
serializer_v4 = _Serializer_v4()
361
serializer_v5 = _Serializer_v5()