~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/xml.py

  • Committer: Martin Pool
  • Date: 2005-09-06 02:26:28 UTC
  • Revision ID: mbp@sourcefrog.net-20050906022628-66d65f0feb4a9e80
- implement version 5 xml storage, and tests

  This stores files identified by the version that introduced the 
  text, and the version that introduced the name.  Entry kinds are
  given by the xml tag not an explicit kind field.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
# "XML is like violence: if it doesn't solve your problem, you aren't
21
21
# using enough of it." -- various
22
22
 
23
 
# importing this module is fairly slow because it has to load several ElementTree bits
 
23
# importing this module is fairly slow because it has to load several
 
24
# ElementTree bits
 
25
 
24
26
try:
25
27
    from util.cElementTree import ElementTree, SubElement, Element
26
28
except ImportError:
27
29
    from util.elementtree.ElementTree import ElementTree, SubElement, Element
28
30
 
29
 
 
30
 
def pack_xml(o, f):
31
 
    """Write object o to file f as XML.
32
 
 
33
 
    o must provide a to_element method.
34
 
    """
35
 
    ElementTree(o.to_element()).write(f, 'utf-8')
36
 
    f.write('\n')
37
 
 
38
 
 
39
 
def unpack_xml(cls, f):
40
 
    return cls.from_element(ElementTree().parse(f))
 
31
from bzrlib.inventory import ROOT_ID, Inventory, InventoryEntry
 
32
from bzrlib.revision import Revision, RevisionReference        
 
33
from bzrlib.errors import BzrError
 
34
 
 
35
 
 
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)
 
42
 
 
43
    def read_inventory(self, f):
 
44
        return self._unpack_inventory(self._read_element(f))
 
45
 
 
46
    def write_revision(self, rev, f):
 
47
        self._write_element(self._pack_revision(rev), f)
 
48
 
 
49
    def read_revision(self, f):
 
50
        return self._unpack_revision(self._read_element(f))
 
51
 
 
52
    def _write_element(self, elt, f):
 
53
        ElementTree(elt).write(f, 'utf-8')
 
54
        f.write('\n')
 
55
 
 
56
    def _read_element(self, f):
 
57
        return ElementTree().parse(f)
 
58
 
 
59
 
 
60
 
 
61
class _Serializer_v4(Serializer):
 
62
    """Version 0.0.4 serializer
 
63
 
 
64
    You should use the serialzer_v4 singleton."""
 
65
    
 
66
    __slots__ = []
 
67
    
 
68
    def _pack_inventory(self, inv):
 
69
        """Convert to XML Element"""
 
70
        e = Element('inventory')
 
71
        e.text = '\n'
 
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))
 
76
        return e
 
77
 
 
78
 
 
79
    def _pack_entry(self, ie):
 
80
        """Convert InventoryEntry to XML element"""
 
81
        e = Element('entry')
 
82
        e.set('name', ie.name)
 
83
        e.set('file_id', ie.file_id)
 
84
        e.set('kind', ie.kind)
 
85
 
 
86
        if ie.text_size != None:
 
87
            e.set('text_size', '%d' % ie.text_size)
 
88
 
 
89
        for f in ['text_id', 'text_sha1']:
 
90
            v = getattr(ie, f)
 
91
            if v != None:
 
92
                e.set(f, v)
 
93
 
 
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)
 
100
 
 
101
        e.tail = '\n'
 
102
 
 
103
        return e
 
104
 
 
105
 
 
106
    def _unpack_inventory(self, elt):
 
107
        """Construct from XML Element
 
108
        """
 
109
        assert elt.tag == 'inventory'
 
110
        root_id = elt.get('file_id') or ROOT_ID
 
111
        inv = Inventory(root_id)
 
112
        for e in elt:
 
113
            ie = self._unpack_entry(e)
 
114
            if ie.parent_id == ROOT_ID:
 
115
                ie.parent_id = root_id
 
116
            inv.add(ie)
 
117
        return inv
 
118
 
 
119
 
 
120
    def _unpack_entry(self, elt):
 
121
        assert elt.tag == 'entry'
 
122
 
 
123
        ## original format inventories don't have a parent_id for
 
124
        ## nodes in the root directory, but it's cleaner to use one
 
125
        ## internally.
 
126
        parent_id = elt.get('parent_id')
 
127
        if parent_id == None:
 
128
            parent_id = ROOT_ID
 
129
 
 
130
        ie = InventoryEntry(elt.get('file_id'),
 
131
                              elt.get('name'),
 
132
                              elt.get('kind'),
 
133
                              parent_id)
 
134
        ie.text_id = elt.get('text_id')
 
135
        ie.text_sha1 = elt.get('text_sha1')
 
136
 
 
137
        ## mutter("read inventoryentry: %r" % (elt.attrib))
 
138
 
 
139
        v = elt.get('text_size')
 
140
        ie.text_size = v and int(v)
 
141
 
 
142
        return ie
 
143
 
 
144
 
 
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,
 
153
                       )
 
154
        if rev.timezone:
 
155
            root.set('timezone', str(rev.timezone))
 
156
        root.text = '\n'
 
157
 
 
158
        msg = SubElement(root, 'message')
 
159
        msg.text = rev.message
 
160
        msg.tail = '\n'
 
161
 
 
162
        if rev.parents:
 
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')
 
168
                p.tail = '\n'
 
169
                assert rr.revision_id
 
170
                p.set('revision_id', rr.revision_id)
 
171
                if rr.revision_sha1:
 
172
                    p.set('revision_sha1', rr.revision_sha1)
 
173
 
 
174
        return root
 
175
 
 
176
    
 
177
    def _unpack_revision(self, elt):
 
178
        """XML Element -> Revision object"""
 
179
        
 
180
        # <changeset> is deprecated...
 
181
        if elt.tag not in ('revision', 'changeset'):
 
182
            raise BzrError("unexpected tag in revision file: %r" % elt)
 
183
 
 
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')
 
189
                       )
 
190
 
 
191
        precursor = elt.get('precursor')
 
192
        precursor_sha1 = elt.get('precursor_sha1')
 
193
 
 
194
        pelts = elt.find('parents')
 
195
 
 
196
        if pelts:
 
197
            for p in pelts:
 
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)
 
203
 
 
204
            if precursor:
 
205
                # must be consistent
 
206
                prec_parent = rev.parents[0].revision_id
 
207
                assert prec_parent == precursor
 
208
        elif 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)
 
213
 
 
214
        v = elt.get('timezone')
 
215
        rev.timezone = v and int(v)
 
216
 
 
217
        rev.message = elt.findtext('message') # text of <message>
 
218
        return rev
 
219
 
 
220
 
 
221
 
 
222
class _Serializer_v5(Serializer):
 
223
    """Version 5 serializer
 
224
 
 
225
    You should use the serialzer_v5 singleton."""
 
226
    
 
227
    __slots__ = []
 
228
    
 
229
    def _pack_inventory(self, inv):
 
230
        """Convert to XML Element"""
 
231
        e = Element('inventory')
 
232
        e.text = '\n'
 
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))
 
237
        return e
 
238
 
 
239
 
 
240
    def _pack_entry(self, ie):
 
241
        """Convert InventoryEntry to XML element"""
 
242
        e = Element('entry')
 
243
        e.set('name', ie.name)
 
244
        e.set('file_id', ie.file_id)
 
245
        e.set('kind', ie.kind)
 
246
 
 
247
        if ie.text_size != None:
 
248
            e.set('text_size', '%d' % ie.text_size)
 
249
 
 
250
        for f in ['text_version', 'text_sha1', 'entry_version']:
 
251
            v = getattr(ie, f)
 
252
            if v != None:
 
253
                e.set(f, v)
 
254
 
 
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)
 
261
 
 
262
        e.tail = '\n'
 
263
 
 
264
        return e
 
265
 
 
266
 
 
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,
 
275
                       )
 
276
        if rev.timezone:
 
277
            root.set('timezone', str(rev.timezone))
 
278
        root.text = '\n'
 
279
 
 
280
        msg = SubElement(root, 'message')
 
281
        msg.text = rev.message
 
282
        msg.tail = '\n'
 
283
 
 
284
        if rev.parents:
 
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')
 
290
                p.tail = '\n'
 
291
                assert rr.revision_id
 
292
                p.set('revision_id', rr.revision_id)
 
293
 
 
294
        return root
 
295
 
 
296
    
 
297
 
 
298
    def _unpack_inventory(self, elt):
 
299
        """Construct from XML Element
 
300
        """
 
301
        assert elt.tag == 'inventory'
 
302
        root_id = elt.get('file_id') or ROOT_ID
 
303
        inv = Inventory(root_id)
 
304
        for e in elt:
 
305
            ie = self._unpack_entry(e)
 
306
            if ie.parent_id == ROOT_ID:
 
307
                ie.parent_id = root_id
 
308
            inv.add(ie)
 
309
        return inv
 
310
 
 
311
 
 
312
    def _unpack_entry(self, elt):
 
313
        kind = elt.tag
 
314
        assert kind == 'directory' or kind == 'file'
 
315
 
 
316
        parent_id = elt.get('parent_id')
 
317
        if parent_id == None:
 
318
            parent_id = ROOT_ID
 
319
 
 
320
        ie = InventoryEntry(elt.get('file_id'),
 
321
                            elt.get('name'),
 
322
                            kind,
 
323
                            parent_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)
 
329
 
 
330
        return ie
 
331
 
 
332
 
 
333
    def _unpack_revision(self, elt):
 
334
        """XML Element -> Revision object"""
 
335
        assert elt.tag == 'revision'
 
336
        
 
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')
 
342
                       )
 
343
 
 
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)
 
349
 
 
350
        v = elt.get('timezone')
 
351
        rev.timezone = v and int(v)
 
352
 
 
353
        rev.message = elt.findtext('message') # text of <message>
 
354
        return rev
 
355
 
 
356
 
 
357
 
 
358
"""singleton instance"""
 
359
serializer_v4 = _Serializer_v4()
 
360
 
 
361
serializer_v5 = _Serializer_v5()