~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/xml.py

fixme note for bzr status

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/env python
1
2
# -*- coding: UTF-8 -*-
2
 
#
 
3
 
3
4
# This program is free software; you can redistribute it and/or modify
4
5
# it under the terms of the GNU General Public License as published by
5
6
# the Free Software Foundation; either version 2 of the License, or
6
7
# (at your option) any later version.
7
 
#
 
8
 
8
9
# This program is distributed in the hope that it will be useful,
9
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
12
# GNU General Public License for more details.
12
 
#
 
13
 
13
14
# You should have received a copy of the GNU General Public License
14
15
# along with this program; if not, write to the Free Software
15
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
23
# importing this module is fairly slow because it has to load several
23
24
# ElementTree bits
24
25
 
25
 
from bzrlib.trace import mutter, warning
26
 
 
27
26
try:
28
 
    from cElementTree import (ElementTree, SubElement, Element,
29
 
                              XMLTreeBuilder, fromstring, tostring)
30
 
    import elementtree
31
 
    ParseError = SyntaxError
 
27
    from util.cElementTree import ElementTree, SubElement, Element
32
28
except ImportError:
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
 
29
    from util.elementtree.ElementTree import ElementTree, SubElement, Element
40
30
 
41
 
from bzrlib import errors
 
31
from bzrlib.inventory import ROOT_ID, Inventory, InventoryEntry
 
32
from bzrlib.revision import Revision, RevisionReference        
 
33
from bzrlib.errors import BzrError
42
34
 
43
35
 
44
36
class Serializer(object):
48
40
        elt = self._pack_inventory(inv)
49
41
        self._write_element(elt, f)
50
42
 
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
 
 
60
43
    def read_inventory(self, f):
61
 
        try:
62
 
            return self._unpack_inventory(self._read_element(f))
63
 
        except ParseError, e:
64
 
            raise errors.UnexpectedInventoryFormat(e)
 
44
        return self._unpack_inventory(self._read_element(f))
65
45
 
66
46
    def write_revision(self, rev, f):
67
47
        self._write_element(self._pack_revision(rev), f)
68
48
 
69
 
    def write_revision_to_string(self, rev):
70
 
        return tostring(self._pack_revision(rev)) + '\n'
71
 
 
72
49
    def read_revision(self, f):
73
50
        return self._unpack_revision(self._read_element(f))
74
51
 
75
 
    def read_revision_from_string(self, xml_string):
76
 
        return self._unpack_revision(fromstring(xml_string))
77
 
 
78
52
    def _write_element(self, elt, f):
79
53
        ElementTree(elt).write(f, 'utf-8')
80
54
        f.write('\n')
83
57
        return ElementTree().parse(f)
84
58
 
85
59
 
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
 
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()