~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-02-17 02:58:22 UTC
  • mfrom: (2249.5.20 knit_utf8_revision_ids)
  • Revision ID: pqm@pqm.ubuntu.com-20070217025822-306d98c244b53b08
(John Arbash Meinel) Clean up most internals to use utf-8 revision ids instead of Unicode.

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
 
29
29
 
30
30
_utf8_re = None
31
 
_utf8_escape_map = {
 
31
_unicode_re = None
 
32
_xml_escape_map = {
32
33
    "&":'&',
33
34
    "'":"'", # FIXME: overkill
34
35
    "\"":""",
38
39
 
39
40
 
40
41
def _ensure_utf8_re():
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):
 
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):
49
51
    """Replace a string of non-ascii, non XML safe characters with their escape
50
52
 
51
53
    This will escape both Standard XML escapes, like <>"', etc.
64
66
        return "&#%d;" % ord(match.group())
65
67
 
66
68
 
67
 
_unicode_to_escaped_map = {}
68
 
 
69
 
def _encode_and_escape(unicode_str, _map=_unicode_to_escaped_map):
 
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):
70
87
    """Encode the string into utf8, and escape invalid XML characters"""
71
88
    # We frequently get entities we have not seen before, so it is better
72
89
    # to check if None, rather than try/KeyError
73
 
    text = _map.get(unicode_str)
 
90
    text = _map.get(unicode_or_utf8_str)
74
91
    if text is None:
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
 
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
84
108
    return text
85
109
 
86
110
 
 
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
 
87
134
def _clear_cache():
88
135
    """Clean out the unicode => escaped map"""
89
 
    _unicode_to_escaped_map.clear()
 
136
    _to_escaped_map.clear()
90
137
 
91
138
 
92
139
class Serializer_v5(Serializer):
178
225
 
179
226
    def _pack_revision(self, rev):
180
227
        """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)
181
235
        root = Element('revision',
182
236
                       committer = rev.committer,
183
237
                       timestamp = '%.3f' % rev.timestamp,
184
 
                       revision_id = rev.revision_id,
 
238
                       revision_id = revision_id,
185
239
                       inventory_sha1 = rev.inventory_sha1,
186
240
                       format='5',
187
241
                       )
198
252
                assert isinstance(parent_id, basestring)
199
253
                p = SubElement(pelts, 'revision_ref')
200
254
                p.tail = '\n'
 
255
                if isinstance(parent_id, str):
 
256
                    parent_id = decode_utf8(parent_id)
201
257
                p.set('revision_id', parent_id)
202
258
        if rev.properties:
203
259
            self._pack_revision_properties(rev, root)
226
282
                                % format)
227
283
        revision_id = elt.get('revision_id')
228
284
        if revision_id is not None:
229
 
            revision_id = cache_utf8.get_cached_unicode(revision_id)
 
285
            revision_id = cache_utf8.encode(revision_id)
230
286
        inv = Inventory(root_id, revision_id=revision_id)
231
287
        for e in elt:
232
288
            ie = self._unpack_entry(e)
240
296
        if not InventoryEntry.versionable_kind(kind):
241
297
            raise AssertionError('unsupported entry kind %s' % kind)
242
298
 
243
 
        get_cached = cache_utf8.get_cached_unicode
 
299
        get_cached = _get_utf8_or_ascii
244
300
 
245
301
        parent_id = elt.get('parent_id')
246
302
        if parent_id is None and not none_parents:
290
346
            if format != '5':
291
347
                raise BzrError("invalid format version %r on inventory"
292
348
                                % format)
293
 
        get_cached = cache_utf8.get_cached_unicode
 
349
        get_cached = _get_utf8_or_ascii
294
350
        rev = Revision(committer = elt.get('committer'),
295
351
                       timestamp = float(elt.get('timestamp')),
296
352
                       revision_id = get_cached(elt.get('revision_id')),