~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/xml5.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-01-17 17:21:14 UTC
  • mfrom: (2229.2.5 reserved-ids)
  • Revision ID: pqm@pqm.ubuntu.com-20070117172114-dc75493dad46088c
Ensure reserved ids are never stored

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
 
29
29
 
30
30
_utf8_re = None
31
 
_unicode_re = None
32
 
_xml_escape_map = {
 
31
_utf8_escape_map = {
33
32
    "&":'&',
34
33
    "'":"'", # FIXME: overkill
35
34
    "\"":""",
39
38
 
40
39
 
41
40
def _ensure_utf8_re():
42
 
    """Make sure the _utf8_re and _unicode_re regexes have been compiled."""
43
 
    global _utf8_re, _unicode_re
44
 
    if _utf8_re is None:
45
 
        _utf8_re = re.compile('[&<>\'\"]|[\x80-\xff]+')
46
 
    if _unicode_re is None:
47
 
        _unicode_re = re.compile(u'[&<>\'\"\u0080-\uffff]')
48
 
 
49
 
 
50
 
def _unicode_escape_replace(match, _map=_xml_escape_map):
 
41
    """Make sure the _utf8_re regex has been compiled"""
 
42
    global _utf8_re
 
43
    if _utf8_re is not None:
 
44
        return
 
45
    _utf8_re = re.compile(u'[&<>\'\"\u0080-\uffff]')
 
46
 
 
47
 
 
48
def _utf8_escape_replace(match, _map=_utf8_escape_map):
51
49
    """Replace a string of non-ascii, non XML safe characters with their escape
52
50
 
53
51
    This will escape both Standard XML escapes, like <>"', etc.
66
64
        return "&#%d;" % ord(match.group())
67
65
 
68
66
 
69
 
def _utf8_escape_replace(match, _map=_xml_escape_map):
70
 
    """Escape utf8 characters into XML safe ones.
71
 
 
72
 
    This uses 2 tricks. It is either escaping "standard" characters, like "&<>,
73
 
    or it is handling characters with the high-bit set. For ascii characters,
74
 
    we just lookup the replacement in the dictionary. For everything else, we
75
 
    decode back into Unicode, and then use the XML escape code.
76
 
    """
77
 
    try:
78
 
        return _map[match.group()]
79
 
    except KeyError:
80
 
        return ''.join('&#%d;' % ord(uni_chr)
81
 
                       for uni_chr in match.group().decode('utf8'))
82
 
 
83
 
 
84
 
_to_escaped_map = {}
85
 
 
86
 
def _encode_and_escape(unicode_or_utf8_str, _map=_to_escaped_map):
 
67
_unicode_to_escaped_map = {}
 
68
 
 
69
def _encode_and_escape(unicode_str, _map=_unicode_to_escaped_map):
87
70
    """Encode the string into utf8, and escape invalid XML characters"""
88
71
    # We frequently get entities we have not seen before, so it is better
89
72
    # to check if None, rather than try/KeyError
90
 
    text = _map.get(unicode_or_utf8_str)
 
73
    text = _map.get(unicode_str)
91
74
    if text is None:
92
 
        if unicode_or_utf8_str.__class__ == unicode:
93
 
            # The alternative policy is to do a regular UTF8 encoding
94
 
            # and then escape only XML meta characters.
95
 
            # Performance is equivalent once you use cache_utf8. *However*
96
 
            # this makes the serialized texts incompatible with old versions
97
 
            # of bzr. So no net gain. (Perhaps the read code would handle utf8
98
 
            # better than entity escapes, but cElementTree seems to do just fine
99
 
            # either way)
100
 
            text = str(_unicode_re.sub(_unicode_escape_replace,
101
 
                                       unicode_or_utf8_str)) + '"'
102
 
        else:
103
 
            # Plain strings are considered to already be in utf-8 so we do a
104
 
            # slightly different method for escaping.
105
 
            text = _utf8_re.sub(_utf8_escape_replace,
106
 
                                unicode_or_utf8_str) + '"'
107
 
        _map[unicode_or_utf8_str] = text
 
75
        # The alternative policy is to do a regular UTF8 encoding
 
76
        # and then escape only XML meta characters.
 
77
        # Performance is equivalent once you use cache_utf8. *However*
 
78
        # this makes the serialized texts incompatible with old versions
 
79
        # of bzr. So no net gain. (Perhaps the read code would handle utf8
 
80
        # better than entity escapes, but cElementTree seems to do just fine
 
81
        # either way)
 
82
        text = str(_utf8_re.sub(_utf8_escape_replace, unicode_str)) + '"'
 
83
        _map[unicode_str] = text
108
84
    return text
109
85
 
110
86
 
111
 
def _get_utf8_or_ascii(a_str,
112
 
                       _encode_utf8=cache_utf8.encode,
113
 
                       _get_cached_ascii=cache_utf8.get_cached_ascii):
114
 
    """Return a cached version of the string.
115
 
 
116
 
    cElementTree will return a plain string if the XML is plain ascii. It only
117
 
    returns Unicode when it needs to. We want to work in utf-8 strings. So if
118
 
    cElementTree returns a plain string, we can just return the cached version.
119
 
    If it is Unicode, then we need to encode it.
120
 
 
121
 
    :param a_str: An 8-bit string or Unicode as returned by
122
 
                  cElementTree.Element.get()
123
 
    :return: A utf-8 encoded 8-bit string.
124
 
    """
125
 
    # This is fairly optimized because we know what cElementTree does, this is
126
 
    # not meant as a generic function for all cases. Because it is possible for
127
 
    # an 8-bit string to not be ascii or valid utf8.
128
 
    if a_str.__class__ == unicode:
129
 
        return _encode_utf8(a_str)
130
 
    else:
131
 
        return _get_cached_ascii(a_str)
132
 
 
133
 
 
134
87
def _clear_cache():
135
88
    """Clean out the unicode => escaped map"""
136
 
    _to_escaped_map.clear()
 
89
    _unicode_to_escaped_map.clear()
137
90
 
138
91
 
139
92
class Serializer_v5(Serializer):
225
178
 
226
179
    def _pack_revision(self, rev):
227
180
        """Revision object -> xml tree"""
228
 
        # For the XML format, we need to write them as Unicode rather than as
229
 
        # utf-8 strings. So that cElementTree can handle properly escaping
230
 
        # them.
231
 
        decode_utf8 = cache_utf8.decode
232
 
        revision_id = rev.revision_id
233
 
        if isinstance(revision_id, str):
234
 
            revision_id = decode_utf8(revision_id)
235
181
        root = Element('revision',
236
182
                       committer = rev.committer,
237
183
                       timestamp = '%.3f' % rev.timestamp,
238
 
                       revision_id = revision_id,
 
184
                       revision_id = rev.revision_id,
239
185
                       inventory_sha1 = rev.inventory_sha1,
240
186
                       format='5',
241
187
                       )
252
198
                assert isinstance(parent_id, basestring)
253
199
                p = SubElement(pelts, 'revision_ref')
254
200
                p.tail = '\n'
255
 
                if isinstance(parent_id, str):
256
 
                    parent_id = decode_utf8(parent_id)
257
201
                p.set('revision_id', parent_id)
258
202
        if rev.properties:
259
203
            self._pack_revision_properties(rev, root)
282
226
                                % format)
283
227
        revision_id = elt.get('revision_id')
284
228
        if revision_id is not None:
285
 
            revision_id = cache_utf8.encode(revision_id)
 
229
            revision_id = cache_utf8.get_cached_unicode(revision_id)
286
230
        inv = Inventory(root_id, revision_id=revision_id)
287
231
        for e in elt:
288
232
            ie = self._unpack_entry(e)
296
240
        if not InventoryEntry.versionable_kind(kind):
297
241
            raise AssertionError('unsupported entry kind %s' % kind)
298
242
 
299
 
        get_cached = _get_utf8_or_ascii
 
243
        get_cached = cache_utf8.get_cached_unicode
300
244
 
301
245
        parent_id = elt.get('parent_id')
302
246
        if parent_id is None and not none_parents:
346
290
            if format != '5':
347
291
                raise BzrError("invalid format version %r on inventory"
348
292
                                % format)
349
 
        get_cached = _get_utf8_or_ascii
 
293
        get_cached = cache_utf8.get_cached_unicode
350
294
        rev = Revision(committer = elt.get('committer'),
351
295
                       timestamp = float(elt.get('timestamp')),
352
296
                       revision_id = get_cached(elt.get('revision_id')),