/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to brzlib/xml_serializer.py

  • Committer: Jelmer Vernooij
  • Date: 2017-05-21 12:41:27 UTC
  • mto: This revision was merged to the branch mainline in revision 6623.
  • Revision ID: jelmer@jelmer.uk-20170521124127-iv8etg0vwymyai6y
s/bzr/brz/ in apport config.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""XML externalization support."""
18
18
 
 
19
from __future__ import absolute_import
 
20
 
19
21
# "XML is like violence: if it doesn't solve your problem, you aren't
20
22
# using enough of it." -- various
21
23
 
26
28
 
27
29
try:
28
30
    import xml.etree.cElementTree as elementtree
29
 
    from xml.etree.ElementTree import ParseError
 
31
    ParseError = getattr(elementtree, "ParseError", SyntaxError)
30
32
except ImportError:
31
33
    # Fall back to pure python implementation if C extension is unavailable
32
34
    import xml.etree.ElementTree as elementtree
35
37
    except ImportError:
36
38
        from xml.parsers.expat import ExpatError as ParseError
37
39
 
38
 
(ElementTree, SubElement, Element, fromstringlist, tostringlist, tostring,
39
 
 fromstring) = (
 
40
(ElementTree, SubElement, Element, XMLTreeBuilder, fromstring, tostring) = (
40
41
    elementtree.ElementTree, elementtree.SubElement, elementtree.Element,
41
 
    elementtree.fromstringlist, elementtree.tostringlist, elementtree.tostring,
42
 
    elementtree.fromstring)
43
 
 
44
 
 
45
 
from .. import (
 
42
    elementtree.XMLTreeBuilder, elementtree.fromstring, elementtree.tostring)
 
43
 
 
44
 
 
45
from brzlib import (
46
46
    cache_utf8,
47
47
    errors,
 
48
    inventory,
48
49
    lazy_regex,
49
 
    )
50
 
from . import (
51
 
    inventory,
52
50
    serializer,
53
51
    )
54
52
 
58
56
 
59
57
    squashes_xml_invalid_characters = True
60
58
 
61
 
    def read_inventory_from_lines(self, lines, revision_id=None,
62
 
                                  entry_cache=None, return_from_cache=False):
 
59
    def read_inventory_from_string(self, xml_string, revision_id=None,
 
60
                                   entry_cache=None, return_from_cache=False):
63
61
        """Read xml_string into an inventory object.
64
62
 
65
 
        :param chunks: The xml to read.
 
63
        :param xml_string: The xml to read.
66
64
        :param revision_id: If not-None, the expected revision id of the
67
65
            inventory. Some serialisers use this to set the results' root
68
66
            revision. This should be supplied for deserialising all
79
77
            make some operations significantly faster.
80
78
        """
81
79
        try:
82
 
            return self._unpack_inventory(fromstringlist(lines), revision_id,
 
80
            return self._unpack_inventory(fromstring(xml_string), revision_id,
83
81
                                          entry_cache=entry_cache,
84
82
                                          return_from_cache=return_from_cache)
85
 
        except ParseError as e:
86
 
            raise errors.UnexpectedInventoryFormat(str(e))
 
83
        except ParseError, e:
 
84
            raise errors.UnexpectedInventoryFormat(e)
87
85
 
88
86
    def read_inventory(self, f, revision_id=None):
89
87
        try:
90
88
            try:
91
89
                return self._unpack_inventory(self._read_element(f),
92
 
                                              revision_id=None)
 
90
                    revision_id=None)
93
91
            finally:
94
92
                f.close()
95
 
        except ParseError as e:
96
 
            raise errors.UnexpectedInventoryFormat(str(e))
 
93
        except ParseError, e:
 
94
            raise errors.UnexpectedInventoryFormat(e)
 
95
 
 
96
    def write_revision(self, rev, f):
 
97
        self._write_element(self._pack_revision(rev), f)
97
98
 
98
99
    def write_revision_to_string(self, rev):
99
 
        return b''.join(self.write_revision_to_lines(rev))
 
100
        return tostring(self._pack_revision(rev)) + '\n'
100
101
 
101
102
    def read_revision(self, f):
102
103
        return self._unpack_revision(self._read_element(f))
104
105
    def read_revision_from_string(self, xml_string):
105
106
        return self._unpack_revision(fromstring(xml_string))
106
107
 
 
108
    def _write_element(self, elt, f):
 
109
        ElementTree(elt).write(f, 'utf-8')
 
110
        f.write('\n')
 
111
 
107
112
    def _read_element(self, f):
108
113
        return ElementTree().parse(f)
109
114
 
121
126
    # aren't listed in the XML specification
122
127
    # (http://www.w3.org/TR/REC-xml/#NT-Char).
123
128
    return re.subn(u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]+',
124
 
                   lambda match: match.group(0).encode(
125
 
                       'unicode_escape').decode('ascii'),
126
 
                   message)
 
129
            lambda match: match.group(0).encode('unicode_escape'),
 
130
            message)
127
131
 
128
132
 
129
133
def get_utf8_or_ascii(a_str, _encode_utf8=cache_utf8.encode):
141
145
    # This is fairly optimized because we know what cElementTree does, this is
142
146
    # not meant as a generic function for all cases. Because it is possible for
143
147
    # an 8-bit string to not be ascii or valid utf8.
144
 
    if a_str.__class__ is str:
 
148
    if a_str.__class__ is unicode:
145
149
        return _encode_utf8(a_str)
146
150
    else:
147
 
        return a_str
148
 
 
149
 
 
150
 
_utf8_re = lazy_regex.lazy_compile(b'[&<>\'\"]|[\x80-\xff]+')
 
151
        return intern(a_str)
 
152
 
 
153
 
 
154
_utf8_re = lazy_regex.lazy_compile('[&<>\'\"]|[\x80-\xff]+')
151
155
_unicode_re = lazy_regex.lazy_compile(u'[&<>\'\"\u0080-\uffff]')
152
156
 
153
157
 
154
158
_xml_escape_map = {
155
 
    "&": '&amp;',
156
 
    "'": "&apos;",  # FIXME: overkill
157
 
    "\"": "&quot;",
158
 
    "<": "&lt;",
159
 
    ">": "&gt;",
 
159
    "&":'&amp;',
 
160
    "'":"&apos;", # FIXME: overkill
 
161
    "\"":"&quot;",
 
162
    "<":"&lt;",
 
163
    ">":"&gt;",
160
164
    }
161
165
 
162
166
 
188
192
    decode back into Unicode, and then use the XML escape code.
189
193
    """
190
194
    try:
191
 
        return _map[match.group().decode('ascii', 'replace')].encode()
 
195
        return _map[match.group()]
192
196
    except KeyError:
193
 
        return b''.join(b'&#%d;' % ord(uni_chr)
194
 
                        for uni_chr in match.group().decode('utf8'))
 
197
        return ''.join('&#%d;' % ord(uni_chr)
 
198
                       for uni_chr in match.group().decode('utf8'))
195
199
 
196
200
 
197
201
_to_escaped_map = {}
198
202
 
199
 
 
200
203
def encode_and_escape(unicode_or_utf8_str, _map=_to_escaped_map):
201
204
    """Encode the string into utf8, and escape invalid XML characters"""
202
205
    # We frequently get entities we have not seen before, so it is better
203
206
    # to check if None, rather than try/KeyError
204
207
    text = _map.get(unicode_or_utf8_str)
205
208
    if text is None:
206
 
        if isinstance(unicode_or_utf8_str, str):
 
209
        if unicode_or_utf8_str.__class__ is unicode:
207
210
            # The alternative policy is to do a regular UTF8 encoding
208
211
            # and then escape only XML meta characters.
209
212
            # Performance is equivalent once you use cache_utf8. *However*
210
213
            # this makes the serialized texts incompatible with old versions
211
214
            # of bzr. So no net gain. (Perhaps the read code would handle utf8
212
 
            # better than entity escapes, but cElementTree seems to do just
213
 
            # fine either way)
214
 
            text = _unicode_re.sub(
215
 
                _unicode_escape_replace, unicode_or_utf8_str).encode()
 
215
            # better than entity escapes, but cElementTree seems to do just fine
 
216
            # either way)
 
217
            text = str(_unicode_re.sub(_unicode_escape_replace,
 
218
                                       unicode_or_utf8_str)) + '"'
216
219
        else:
217
220
            # Plain strings are considered to already be in utf-8 so we do a
218
221
            # slightly different method for escaping.
219
222
            text = _utf8_re.sub(_utf8_escape_replace,
220
 
                                unicode_or_utf8_str)
 
223
                                unicode_or_utf8_str) + '"'
221
224
        _map[unicode_or_utf8_str] = text
222
225
    return text
223
226
 
297
300
                                     elt_get('name'),
298
301
                                     parent_id)
299
302
        ie.text_sha1 = elt_get('text_sha1')
300
 
        if ie.text_sha1 is not None:
301
 
            ie.text_sha1 = ie.text_sha1.encode('ascii')
302
303
        if elt_get('executable') == 'yes':
303
304
            ie.executable = True
304
305
        v = elt_get('text_size')
309
310
                                     parent_id)
310
311
        ie.symlink_target = elt_get('symlink_target')
311
312
    elif kind == 'tree-reference':
312
 
        file_id = get_utf8_or_ascii(elt.attrib['file_id'])
 
313
        file_id = elt.attrib['file_id']
313
314
        name = elt.attrib['name']
314
 
        parent_id = get_utf8_or_ascii(elt.attrib['parent_id'])
315
 
        revision = get_utf8_or_ascii(elt.get('revision'))
316
 
        reference_revision = get_utf8_or_ascii(elt.get('reference_revision'))
 
315
        parent_id = elt.attrib['parent_id']
 
316
        revision = elt.get('revision')
 
317
        reference_revision = elt.get('reference_revision')
317
318
        ie = inventory.TreeReference(file_id, name, parent_id, revision,
318
 
                                     reference_revision)
 
319
                                       reference_revision)
319
320
    else:
320
321
        raise errors.UnsupportedInventoryKind(kind)
321
322
    ie.revision = revision
330
331
 
331
332
 
332
333
def unpack_inventory_flat(elt, format_num, unpack_entry,
333
 
                          entry_cache=None, return_from_cache=False):
 
334
            entry_cache=None, return_from_cache=False):
334
335
    """Unpack a flat XML inventory.
335
336
 
336
337
    :param elt: XML element for the inventory
343
344
    if elt.tag != 'inventory':
344
345
        raise errors.UnexpectedInventoryFormat('Root tag is %r' % elt.tag)
345
346
    format = elt.get('format')
346
 
    if ((format is None and format_num is not None) or
347
 
            format.encode() != format_num):
 
347
    if format != format_num:
348
348
        raise errors.UnexpectedInventoryFormat('Invalid format version %r'
349
349
                                               % format)
350
350
    revision_id = elt.get('revision_id')
367
367
    """
368
368
    entries = inv.iter_entries()
369
369
    # Skip the root
370
 
    root_path, root_ie = next(entries)
 
370
    root_path, root_ie = entries.next()
371
371
    for path, ie in entries:
372
372
        if ie.parent_id != root_id:
373
 
            parent_str = b''.join(
374
 
                [b' parent_id="', encode_and_escape(ie.parent_id), b'"'])
 
373
            parent_str = ' parent_id="'
 
374
            parent_id  = encode_and_escape(ie.parent_id)
375
375
        else:
376
 
            parent_str = b''
 
376
            parent_str = ''
 
377
            parent_id  = ''
377
378
        if ie.kind == 'file':
378
379
            if ie.executable:
379
 
                executable = b' executable="yes"'
 
380
                executable = ' executable="yes"'
380
381
            else:
381
 
                executable = b''
 
382
                executable = ''
382
383
            if not working:
383
 
                append(b'<file%s file_id="%s" name="%s"%s revision="%s" '
384
 
                       b'text_sha1="%s" text_size="%d" />\n' % (
385
 
                           executable, encode_and_escape(ie.file_id),
386
 
                           encode_and_escape(ie.name), parent_str,
387
 
                           encode_and_escape(ie.revision), ie.text_sha1,
388
 
                           ie.text_size))
 
384
                append('<file%s file_id="%s name="%s%s%s revision="%s '
 
385
                    'text_sha1="%s" text_size="%d" />\n' % (
 
386
                    executable, encode_and_escape(ie.file_id),
 
387
                    encode_and_escape(ie.name), parent_str, parent_id,
 
388
                    encode_and_escape(ie.revision), ie.text_sha1,
 
389
                    ie.text_size))
389
390
            else:
390
 
                append(b'<file%s file_id="%s" name="%s"%s />\n' % (
 
391
                append('<file%s file_id="%s name="%s%s%s />\n' % (
391
392
                    executable, encode_and_escape(ie.file_id),
392
 
                    encode_and_escape(ie.name), parent_str))
 
393
                    encode_and_escape(ie.name), parent_str, parent_id))
393
394
        elif ie.kind == 'directory':
394
395
            if not working:
395
 
                append(b'<directory file_id="%s" name="%s"%s revision="%s" '
396
 
                       b'/>\n' % (
397
 
                           encode_and_escape(ie.file_id),
398
 
                           encode_and_escape(ie.name),
399
 
                           parent_str,
400
 
                           encode_and_escape(ie.revision)))
 
396
                append('<directory file_id="%s name="%s%s%s revision="%s '
 
397
                    '/>\n' % (
 
398
                    encode_and_escape(ie.file_id),
 
399
                    encode_and_escape(ie.name),
 
400
                    parent_str, parent_id,
 
401
                    encode_and_escape(ie.revision)))
401
402
            else:
402
 
                append(b'<directory file_id="%s" name="%s"%s />\n' % (
 
403
                append('<directory file_id="%s name="%s%s%s />\n' % (
403
404
                    encode_and_escape(ie.file_id),
404
405
                    encode_and_escape(ie.name),
405
 
                    parent_str))
 
406
                    parent_str, parent_id))
406
407
        elif ie.kind == 'symlink':
407
408
            if not working:
408
 
                append(b'<symlink file_id="%s" name="%s"%s revision="%s" '
409
 
                       b'symlink_target="%s" />\n' % (
410
 
                           encode_and_escape(ie.file_id),
411
 
                           encode_and_escape(ie.name),
412
 
                           parent_str,
413
 
                           encode_and_escape(ie.revision),
414
 
                           encode_and_escape(ie.symlink_target)))
 
409
                append('<symlink file_id="%s name="%s%s%s revision="%s '
 
410
                    'symlink_target="%s />\n' % (
 
411
                    encode_and_escape(ie.file_id),
 
412
                    encode_and_escape(ie.name),
 
413
                    parent_str, parent_id,
 
414
                    encode_and_escape(ie.revision),
 
415
                    encode_and_escape(ie.symlink_target)))
415
416
            else:
416
 
                append(b'<symlink file_id="%s" name="%s"%s />\n' % (
 
417
                append('<symlink file_id="%s name="%s%s%s />\n' % (
417
418
                    encode_and_escape(ie.file_id),
418
419
                    encode_and_escape(ie.name),
419
 
                    parent_str))
 
420
                    parent_str, parent_id))
420
421
        elif ie.kind == 'tree-reference':
421
422
            if ie.kind not in supported_kinds:
422
423
                raise errors.UnsupportedInventoryKind(ie.kind)
423
424
            if not working:
424
 
                append(b'<tree-reference file_id="%s" name="%s"%s '
425
 
                       b'revision="%s" reference_revision="%s" />\n' % (
426
 
                           encode_and_escape(ie.file_id),
427
 
                           encode_and_escape(ie.name),
428
 
                           parent_str,
429
 
                           encode_and_escape(ie.revision),
430
 
                           encode_and_escape(ie.reference_revision)))
 
425
                append('<tree-reference file_id="%s name="%s%s%s '
 
426
                    'revision="%s reference_revision="%s />\n' % (
 
427
                    encode_and_escape(ie.file_id),
 
428
                    encode_and_escape(ie.name),
 
429
                    parent_str, parent_id,
 
430
                    encode_and_escape(ie.revision),
 
431
                    encode_and_escape(ie.reference_revision)))
431
432
            else:
432
 
                append(b'<tree-reference file_id="%s" name="%s"%s />\n' % (
 
433
                append('<tree-reference file_id="%s name="%s%s%s />\n' % (
433
434
                    encode_and_escape(ie.file_id),
434
435
                    encode_and_escape(ie.name),
435
 
                    parent_str))
 
436
                    parent_str, parent_id))
436
437
        else:
437
438
            raise errors.UnsupportedInventoryKind(ie.kind)
438
 
    append(b'</inventory>\n')
 
439
    append('</inventory>\n')