~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/xml_serializer.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-09-27 20:44:31 UTC
  • mfrom: (1551.8.34 Aaron's mergeable stuff)
  • Revision ID: pqm@pqm.ubuntu.com-20060927204431-5871ab3ef6affaf3
Revert deletes files that don't exist in target if not locally modified

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/env python
2
1
# -*- coding: UTF-8 -*-
3
 
 
 
2
#
4
3
# This program is free software; you can redistribute it and/or modify
5
4
# it under the terms of the GNU General Public License as published by
6
5
# the Free Software Foundation; either version 2 of the License, or
7
6
# (at your option) any later version.
8
 
 
 
7
#
9
8
# This program is distributed in the hope that it will be useful,
10
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
11
# GNU General Public License for more details.
13
 
 
 
12
#
14
13
# You should have received a copy of the GNU General Public License
15
14
# along with this program; if not, write to the Free Software
16
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23
22
# importing this module is fairly slow because it has to load several
24
23
# ElementTree bits
25
24
 
 
25
from bzrlib.trace import mutter, warning
 
26
 
26
27
try:
27
 
    from util.cElementTree import ElementTree, SubElement, Element
 
28
    from cElementTree import (ElementTree, SubElement, Element,
 
29
                              XMLTreeBuilder, fromstring, tostring)
 
30
    import elementtree
 
31
    ParseError = SyntaxError
28
32
except ImportError:
29
 
    from util.elementtree.ElementTree import ElementTree, SubElement, Element
 
33
    mutter('WARNING: using slower ElementTree; consider installing cElementTree'
 
34
           " and make sure it's on your PYTHONPATH")
 
35
    from util.elementtree.ElementTree import (ElementTree, SubElement,
 
36
                                              Element, XMLTreeBuilder,
 
37
                                              fromstring, tostring)
 
38
    import util.elementtree as elementtree
 
39
    from xml.parsers.expat import ExpatError as ParseError
30
40
 
31
 
from bzrlib.inventory import ROOT_ID, Inventory, InventoryEntry
32
 
from bzrlib.revision import Revision, RevisionReference        
33
 
from bzrlib.errors import BzrError
 
41
from bzrlib import errors
34
42
 
35
43
 
36
44
class Serializer(object):
40
48
        elt = self._pack_inventory(inv)
41
49
        self._write_element(elt, f)
42
50
 
 
51
    def write_inventory_to_string(self, inv):
 
52
        return tostring(self._pack_inventory(inv)) + '\n'
 
53
 
 
54
    def read_inventory_from_string(self, xml_string):
 
55
        try:
 
56
            return self._unpack_inventory(fromstring(xml_string))
 
57
        except ParseError, e:
 
58
            raise errors.UnexpectedInventoryFormat(e)
 
59
 
43
60
    def read_inventory(self, f):
44
 
        return self._unpack_inventory(self._read_element(f))
 
61
        try:
 
62
            return self._unpack_inventory(self._read_element(f))
 
63
        except ParseError, e:
 
64
            raise errors.UnexpectedInventoryFormat(e)
45
65
 
46
66
    def write_revision(self, rev, f):
47
67
        self._write_element(self._pack_revision(rev), f)
48
68
 
 
69
    def write_revision_to_string(self, rev):
 
70
        return tostring(self._pack_revision(rev)) + '\n'
 
71
 
49
72
    def read_revision(self, f):
50
73
        return self._unpack_revision(self._read_element(f))
51
74
 
 
75
    def read_revision_from_string(self, xml_string):
 
76
        return self._unpack_revision(fromstring(xml_string))
 
77
 
52
78
    def _write_element(self, elt, f):
53
79
        ElementTree(elt).write(f, 'utf-8')
54
80
        f.write('\n')
57
83
        return ElementTree().parse(f)
58
84
 
59
85
 
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
 
    Packs objects into XML and vice versa.
226
 
 
227
 
    You should use the serialzer_v5 singleton."""
228
 
    
229
 
    __slots__ = []
230
 
    
231
 
    def _pack_inventory(self, inv):
232
 
        """Convert to XML Element"""
233
 
        e = Element('inventory')
234
 
        e.text = '\n'
235
 
        if inv.root.file_id not in (None, ROOT_ID):
236
 
            e.set('file_id', inv.root.file_id)
237
 
        for path, ie in inv.iter_entries():
238
 
            e.append(self._pack_entry(ie))
239
 
        return e
240
 
 
241
 
 
242
 
    def _pack_entry(self, ie):
243
 
        """Convert InventoryEntry to XML element"""
244
 
        assert ie.kind == 'directory' or ie.kind == 'file'
245
 
        e = Element(ie.kind)
246
 
        e.set('name', ie.name)
247
 
        e.set('file_id', ie.file_id)
248
 
 
249
 
        if ie.text_size != None:
250
 
            e.set('text_size', '%d' % ie.text_size)
251
 
 
252
 
        for f in ['text_version', 'text_sha1', 'entry_version']:
253
 
            v = getattr(ie, f)
254
 
            if v != None:
255
 
                e.set(f, v)
256
 
 
257
 
        # to be conservative, we don't externalize the root pointers
258
 
        # for now, leaving them as null in the xml form.  in a future
259
 
        # version it will be implied by nested elements.
260
 
        if ie.parent_id != ROOT_ID:
261
 
            assert isinstance(ie.parent_id, basestring)
262
 
            e.set('parent_id', ie.parent_id)
263
 
 
264
 
        e.tail = '\n'
265
 
 
266
 
        return e
267
 
 
268
 
 
269
 
    def _pack_revision(self, rev):
270
 
        """Revision object -> xml tree"""
271
 
        root = Element('revision',
272
 
                       committer = rev.committer,
273
 
                       timestamp = '%.9f' % rev.timestamp,
274
 
                       revision_id = rev.revision_id,
275
 
                       inventory_id = rev.inventory_id,
276
 
                       inventory_sha1 = rev.inventory_sha1,
277
 
                       )
278
 
        if rev.timezone:
279
 
            root.set('timezone', str(rev.timezone))
280
 
        root.text = '\n'
281
 
 
282
 
        msg = SubElement(root, 'message')
283
 
        msg.text = rev.message
284
 
        msg.tail = '\n'
285
 
 
286
 
        if rev.parents:
287
 
            pelts = SubElement(root, 'parents')
288
 
            pelts.tail = pelts.text = '\n'
289
 
            for rr in rev.parents:
290
 
                assert isinstance(rr, RevisionReference)
291
 
                p = SubElement(pelts, 'revision_ref')
292
 
                p.tail = '\n'
293
 
                assert rr.revision_id
294
 
                p.set('revision_id', rr.revision_id)
295
 
 
296
 
        return root
297
 
 
298
 
    
299
 
 
300
 
    def _unpack_inventory(self, elt):
301
 
        """Construct from XML Element
302
 
        """
303
 
        assert elt.tag == 'inventory'
304
 
        root_id = elt.get('file_id') or ROOT_ID
305
 
        inv = Inventory(root_id)
306
 
        for e in elt:
307
 
            ie = self._unpack_entry(e)
308
 
            if ie.parent_id == ROOT_ID:
309
 
                ie.parent_id = root_id
310
 
            inv.add(ie)
311
 
        return inv
312
 
 
313
 
 
314
 
    def _unpack_entry(self, elt):
315
 
        kind = elt.tag
316
 
        assert kind == 'directory' or kind == 'file'
317
 
 
318
 
        parent_id = elt.get('parent_id')
319
 
        if parent_id == None:
320
 
            parent_id = ROOT_ID
321
 
 
322
 
        ie = InventoryEntry(elt.get('file_id'),
323
 
                            elt.get('name'),
324
 
                            kind,
325
 
                            parent_id)
326
 
        ie.text_version = elt.get('text_version')
327
 
        ie.entry_version = elt.get('entry_version')
328
 
        ie.text_sha1 = elt.get('text_sha1')
329
 
        v = elt.get('text_size')
330
 
        ie.text_size = v and int(v)
331
 
 
332
 
        return ie
333
 
 
334
 
 
335
 
    def _unpack_revision(self, elt):
336
 
        """XML Element -> Revision object"""
337
 
        assert elt.tag == 'revision'
338
 
        
339
 
        rev = Revision(committer = elt.get('committer'),
340
 
                       timestamp = float(elt.get('timestamp')),
341
 
                       revision_id = elt.get('revision_id'),
342
 
                       inventory_id = elt.get('inventory_id'),
343
 
                       inventory_sha1 = elt.get('inventory_sha1')
344
 
                       )
345
 
 
346
 
        for p in elt.find('parents'):
347
 
            assert p.tag == 'revision_ref', \
348
 
                   "bad parent node tag %r" % p.tag
349
 
            rev_ref = RevisionReference(p.get('revision_id'))
350
 
            rev.parents.append(rev_ref)
351
 
 
352
 
        v = elt.get('timezone')
353
 
        rev.timezone = v and int(v)
354
 
 
355
 
        rev.message = elt.findtext('message') # text of <message>
356
 
        return rev
357
 
 
358
 
 
359
 
 
360
 
"""singleton instance"""
361
 
serializer_v4 = _Serializer_v4()
362
 
 
363
 
serializer_v5 = _Serializer_v5()
 
86
# performance tuning for elementree's serialiser. This should be
 
87
# sent upstream - RBC 20060523.
 
88
# the functions here are patched into elementtree at runtime.
 
89
import re
 
90
escape_re = re.compile("[&'\"<>]")
 
91
escape_map = {
 
92
    "&":'&amp;',
 
93
    "'":"&apos;", # FIXME: overkill
 
94
    "\"":"&quot;",
 
95
    "<":"&lt;",
 
96
    ">":"&gt;",
 
97
    }
 
98
def _escape_replace(match, map=escape_map):
 
99
    return map[match.group()]
 
100
 
 
101
def _escape_attrib(text, encoding=None, replace=None):
 
102
    # escape attribute value
 
103
    try:
 
104
        if encoding:
 
105
            try:
 
106
                text = elementtree.ElementTree._encode(text, encoding)
 
107
            except UnicodeError:
 
108
                return elementtree.ElementTree._encode_entity(text)
 
109
        if replace is None:
 
110
            return escape_re.sub(_escape_replace, text)
 
111
        else:
 
112
            text = replace(text, "&", "&amp;")
 
113
            text = replace(text, "'", "&apos;") # FIXME: overkill
 
114
            text = replace(text, "\"", "&quot;")
 
115
            text = replace(text, "<", "&lt;")
 
116
            text = replace(text, ">", "&gt;")
 
117
            return text
 
118
    except (TypeError, AttributeError):
 
119
        elementtree.ElementTree._raise_serialization_error(text)
 
120
 
 
121
elementtree.ElementTree._escape_attrib = _escape_attrib
 
122
 
 
123
escape_cdata_re = re.compile("[&<>]")
 
124
escape_cdata_map = {
 
125
    "&":'&amp;',
 
126
    "<":"&lt;",
 
127
    ">":"&gt;",
 
128
    }
 
129
def _escape_cdata_replace(match, map=escape_cdata_map):
 
130
    return map[match.group()]
 
131
 
 
132
def _escape_cdata(text, encoding=None, replace=None):
 
133
    # escape character data
 
134
    try:
 
135
        if encoding:
 
136
            try:
 
137
                text = elementtree.ElementTree._encode(text, encoding)
 
138
            except UnicodeError:
 
139
                return elementtree.ElementTree._encode_entity(text)
 
140
        if replace is None:
 
141
            return escape_cdata_re.sub(_escape_cdata_replace, text)
 
142
        else:
 
143
            text = replace(text, "&", "&amp;")
 
144
            text = replace(text, "<", "&lt;")
 
145
            text = replace(text, ">", "&gt;")
 
146
            return text
 
147
    except (TypeError, AttributeError):
 
148
        elementtree.ElementTree._raise_serialization_error(text)
 
149
 
 
150
elementtree.ElementTree._escape_cdata = _escape_cdata