~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/xml_serializer.py

  • Committer: Robert Collins
  • Date: 2009-03-03 03:27:51 UTC
  • mto: (4070.2.5 integration)
  • mto: This revision was merged to the branch mainline in revision 4075.
  • Revision ID: robertc@robertcollins.net-20090303032751-ubyfhezgjul6y5ic
Get BzrDir.cloning_metadir working.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/env python
2
 
# -*- coding: UTF-8 -*-
3
 
 
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
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 import registry
 
26
from bzrlib.trace import mutter, warning
 
27
 
26
28
try:
27
 
    from util.cElementTree import ElementTree, SubElement, Element
 
29
    try:
 
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
 
34
    except ImportError:
 
35
        from cElementTree import (ElementTree, SubElement, Element,
 
36
                                  XMLTreeBuilder, fromstring, tostring)
 
37
        import elementtree.ElementTree
 
38
    ParseError = SyntaxError
28
39
except ImportError:
29
 
    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,
 
45
                                              fromstring, tostring)
 
46
    import util.elementtree as elementtree
 
47
    from xml.parsers.expat import ExpatError as ParseError
30
48
 
31
 
from bzrlib.inventory import ROOT_ID, Inventory, InventoryEntry
32
 
from bzrlib.revision import Revision, RevisionReference        
 
49
from bzrlib import errors
33
50
 
34
51
 
35
52
class Serializer(object):
36
53
    """Abstract object serialize/deserialize"""
 
54
 
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)
41
 
 
42
 
    def read_inventory(self, f):
43
 
        return self._unpack_inventory(self._read_element(f))
 
57
        raise NotImplementedError(self.write_inventory)
 
58
 
 
59
    def write_inventory_to_string(self, inv):
 
60
        raise NotImplementedError(self.write_inventory_to_string)
 
61
 
 
62
    def read_inventory_from_string(self, xml_string, revision_id=None,
 
63
                                   entry_cache=None):
 
64
        """Read xml_string into an inventory object.
 
65
 
 
66
        :param xml_string: The xml to read.
 
67
        :param revision_id: If not-None, the expected revision id of the
 
68
            inventory. Some serialisers use this to set the results' root
 
69
            revision. This should be supplied for deserialising all
 
70
            from-repository inventories so that xml5 inventories that were
 
71
            serialised without a revision identifier can be given the right
 
72
            revision id (but not for working tree inventories where users can
 
73
            edit the data without triggering checksum errors or anything).
 
74
        :param entry_cache: An optional cache of InventoryEntry objects. If
 
75
            supplied we will look up entries via (file_id, revision_id) which
 
76
            should map to a valid InventoryEntry (File/Directory/etc) object.
 
77
        """
 
78
        try:
 
79
            return self._unpack_inventory(fromstring(xml_string), revision_id,
 
80
                                          entry_cache=entry_cache)
 
81
        except ParseError, e:
 
82
            raise errors.UnexpectedInventoryFormat(e)
 
83
 
 
84
    def read_inventory(self, f, revision_id=None):
 
85
        try:
 
86
            return self._unpack_inventory(self._read_element(f),
 
87
                revision_id=None)
 
88
        except ParseError, e:
 
89
            raise errors.UnexpectedInventoryFormat(e)
44
90
 
45
91
    def write_revision(self, rev, f):
46
92
        self._write_element(self._pack_revision(rev), f)
47
93
 
 
94
    def write_revision_to_string(self, rev):
 
95
        return tostring(self._pack_revision(rev)) + '\n'
 
96
 
48
97
    def read_revision(self, f):
49
98
        return self._unpack_revision(self._read_element(f))
50
99
 
 
100
    def read_revision_from_string(self, xml_string):
 
101
        return self._unpack_revision(fromstring(xml_string))
 
102
 
51
103
    def _write_element(self, elt, f):
52
104
        ElementTree(elt).write(f, 'utf-8')
53
105
        f.write('\n')
56
108
        return ElementTree().parse(f)
57
109
 
58
110
 
59
 
 
60
 
class _Serializer_v4(Serializer):
61
 
    """Version 0.0.4 serializer"""
62
 
    
63
 
    __slots__ = []
64
 
    
65
 
    def _pack_inventory(self, inv):
66
 
        """Convert to XML Element"""
67
 
        e = Element('inventory')
68
 
        e.text = '\n'
69
 
        if inv.root.file_id not in (None, ROOT_ID):
70
 
            e.set('file_id', inv.root.file_id)
71
 
        for path, ie in inv.iter_entries():
72
 
            e.append(self._pack_entry(ie))
73
 
        return e
74
 
 
75
 
 
76
 
    def _pack_entry(self, ie):
77
 
        """Convert InventoryEntry to XML element"""
78
 
        e = Element('entry')
79
 
        e.set('name', ie.name)
80
 
        e.set('file_id', ie.file_id)
81
 
        e.set('kind', ie.kind)
82
 
 
83
 
        if ie.text_size != None:
84
 
            e.set('text_size', '%d' % ie.text_size)
85
 
 
86
 
        for f in ['text_id', 'text_sha1']:
87
 
            v = getattr(ie, f)
88
 
            if v != None:
89
 
                e.set(f, v)
90
 
 
91
 
        # to be conservative, we don't externalize the root pointers
92
 
        # for now, leaving them as null in the xml form.  in a future
93
 
        # version it will be implied by nested elements.
94
 
        if ie.parent_id != ROOT_ID:
95
 
            assert isinstance(ie.parent_id, basestring)
96
 
            e.set('parent_id', ie.parent_id)
97
 
 
98
 
        e.tail = '\n'
99
 
 
100
 
        return e
101
 
 
102
 
 
103
 
    def _unpack_inventory(self, elt):
104
 
        """Construct from XML Element
105
 
        """
106
 
        assert elt.tag == 'inventory'
107
 
        root_id = elt.get('file_id') or ROOT_ID
108
 
        inv = Inventory(root_id)
109
 
        for e in elt:
110
 
            ie = self._unpack_entry(e)
111
 
            if ie.parent_id == ROOT_ID:
112
 
                ie.parent_id = root_id
113
 
            inv.add(ie)
114
 
        return inv
115
 
 
116
 
 
117
 
    def _unpack_entry(self, elt):
118
 
        assert elt.tag == 'entry'
119
 
 
120
 
        ## original format inventories don't have a parent_id for
121
 
        ## nodes in the root directory, but it's cleaner to use one
122
 
        ## internally.
123
 
        parent_id = elt.get('parent_id')
124
 
        if parent_id == None:
125
 
            parent_id = ROOT_ID
126
 
 
127
 
        ie = InventoryEntry(elt.get('file_id'),
128
 
                              elt.get('name'),
129
 
                              elt.get('kind'),
130
 
                              parent_id)
131
 
        ie.text_id = elt.get('text_id')
132
 
        ie.text_sha1 = elt.get('text_sha1')
133
 
 
134
 
        ## mutter("read inventoryentry: %r" % (elt.attrib))
135
 
 
136
 
        v = elt.get('text_size')
137
 
        ie.text_size = v and int(v)
138
 
 
139
 
        return ie
140
 
 
141
 
 
142
 
    def _pack_revision(self, rev):
143
 
        """Revision object -> xml tree"""
144
 
        root = Element('revision',
145
 
                       committer = rev.committer,
146
 
                       timestamp = '%.9f' % rev.timestamp,
147
 
                       revision_id = rev.revision_id,
148
 
                       inventory_id = rev.inventory_id,
149
 
                       inventory_sha1 = rev.inventory_sha1,
150
 
                       )
151
 
        if rev.timezone:
152
 
            root.set('timezone', str(rev.timezone))
153
 
        root.text = '\n'
154
 
 
155
 
        msg = SubElement(root, 'message')
156
 
        msg.text = rev.message
157
 
        msg.tail = '\n'
158
 
 
159
 
        if rev.parents:
160
 
            pelts = SubElement(root, 'parents')
161
 
            pelts.tail = pelts.text = '\n'
162
 
            for rr in rev.parents:
163
 
                assert isinstance(rr, RevisionReference)
164
 
                p = SubElement(pelts, 'revision_ref')
165
 
                p.tail = '\n'
166
 
                assert rr.revision_id
167
 
                p.set('revision_id', rr.revision_id)
168
 
                if rr.revision_sha1:
169
 
                    p.set('revision_sha1', rr.revision_sha1)
170
 
 
171
 
        return root
172
 
 
173
 
    
174
 
    def _unpack_revision(self, elt):
175
 
        """XML Element -> Revision object"""
176
 
        
177
 
        # <changeset> is deprecated...
178
 
        if elt.tag not in ('revision', 'changeset'):
179
 
            raise bzrlib.errors.BzrError("unexpected tag in revision file: %r" % elt)
180
 
 
181
 
        rev = Revision(committer = elt.get('committer'),
182
 
                       timestamp = float(elt.get('timestamp')),
183
 
                       revision_id = elt.get('revision_id'),
184
 
                       inventory_id = elt.get('inventory_id'),
185
 
                       inventory_sha1 = elt.get('inventory_sha1')
186
 
                       )
187
 
 
188
 
        precursor = elt.get('precursor')
189
 
        precursor_sha1 = elt.get('precursor_sha1')
190
 
 
191
 
        pelts = elt.find('parents')
192
 
 
193
 
        if pelts:
194
 
            for p in pelts:
195
 
                assert p.tag == 'revision_ref', \
196
 
                       "bad parent node tag %r" % p.tag
197
 
                rev_ref = RevisionReference(p.get('revision_id'),
198
 
                                            p.get('revision_sha1'))
199
 
                rev.parents.append(rev_ref)
200
 
 
201
 
            if precursor:
202
 
                # must be consistent
203
 
                prec_parent = rev.parents[0].revision_id
204
 
                assert prec_parent == precursor
205
 
        elif precursor:
206
 
            # revisions written prior to 0.0.5 have a single precursor
207
 
            # give as an attribute
208
 
            rev_ref = RevisionReference(precursor, precursor_sha1)
209
 
            rev.parents.append(rev_ref)
210
 
 
211
 
        v = elt.get('timezone')
212
 
        rev.timezone = v and int(v)
213
 
 
214
 
        rev.message = elt.findtext('message') # text of <message>
215
 
        return rev
216
 
 
217
 
 
218
 
 
219
 
"""singleton instance"""
220
 
serializer_v4 = _Serializer_v4()
 
111
# performance tuning for elementree's serialiser. This should be
 
112
# sent upstream - RBC 20060523.
 
113
# the functions here are patched into elementtree at runtime.
 
114
import re
 
115
escape_re = re.compile("[&'\"<>]")
 
116
escape_map = {
 
117
    "&":'&amp;',
 
118
    "'":"&apos;", # FIXME: overkill
 
119
    "\"":"&quot;",
 
120
    "<":"&lt;",
 
121
    ">":"&gt;",
 
122
    }
 
123
def _escape_replace(match, map=escape_map):
 
124
    return map[match.group()]
 
125
 
 
126
def _escape_attrib(text, encoding=None, replace=None):
 
127
    # escape attribute value
 
128
    try:
 
129
        if encoding:
 
130
            try:
 
131
                text = elementtree.ElementTree._encode(text, encoding)
 
132
            except UnicodeError:
 
133
                return elementtree.ElementTree._encode_entity(text)
 
134
        if replace is None:
 
135
            return escape_re.sub(_escape_replace, text)
 
136
        else:
 
137
            text = replace(text, "&", "&amp;")
 
138
            text = replace(text, "'", "&apos;") # FIXME: overkill
 
139
            text = replace(text, "\"", "&quot;")
 
140
            text = replace(text, "<", "&lt;")
 
141
            text = replace(text, ">", "&gt;")
 
142
            return text
 
143
    except (TypeError, AttributeError):
 
144
        elementtree.ElementTree._raise_serialization_error(text)
 
145
 
 
146
elementtree.ElementTree._escape_attrib = _escape_attrib
 
147
 
 
148
escape_cdata_re = re.compile("[&<>]")
 
149
escape_cdata_map = {
 
150
    "&":'&amp;',
 
151
    "<":"&lt;",
 
152
    ">":"&gt;",
 
153
    }
 
154
def _escape_cdata_replace(match, map=escape_cdata_map):
 
155
    return map[match.group()]
 
156
 
 
157
def _escape_cdata(text, encoding=None, replace=None):
 
158
    # escape character data
 
159
    try:
 
160
        if encoding:
 
161
            try:
 
162
                text = elementtree.ElementTree._encode(text, encoding)
 
163
            except UnicodeError:
 
164
                return elementtree.ElementTree._encode_entity(text)
 
165
        if replace is None:
 
166
            return escape_cdata_re.sub(_escape_cdata_replace, text)
 
167
        else:
 
168
            text = replace(text, "&", "&amp;")
 
169
            text = replace(text, "<", "&lt;")
 
170
            text = replace(text, ">", "&gt;")
 
171
            return text
 
172
    except (TypeError, AttributeError):
 
173
        elementtree.ElementTree._raise_serialization_error(text)
 
174
 
 
175
elementtree.ElementTree._escape_cdata = _escape_cdata
 
176
 
 
177
 
 
178
class SerializerRegistry(registry.Registry):
 
179
    """Registry for serializer objects"""
 
180
 
 
181
 
 
182
format_registry = SerializerRegistry()
 
183
format_registry.register_lazy('4', 'bzrlib.xml4', 'serializer_v4')
 
184
format_registry.register_lazy('5', 'bzrlib.xml5', 'serializer_v5')
 
185
format_registry.register_lazy('6', 'bzrlib.xml6', 'serializer_v6')
 
186
format_registry.register_lazy('7', 'bzrlib.xml7', 'serializer_v7')
 
187
format_registry.register_lazy('8', 'bzrlib.xml8', 'serializer_v8')