~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/canonical/encoding.py

Merge with trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
"""Character encoding utilities"""
5
5
 
6
6
__metaclass__ = type
 
7
__all__ = [
 
8
    'ascii_smash',
 
9
    'escape_nonascii_uniquely',
 
10
    'guess',
 
11
    ]
 
12
 
7
13
import re
8
14
import codecs
9
15
import unicodedata
10
 
from htmlentitydefs import codepoint2name
11
16
from cStringIO import StringIO
12
17
 
13
 
__all__ = ['guess', 'ascii_smash']
14
 
 
15
18
_boms = [
16
19
    (codecs.BOM_UTF16_BE, 'utf_16_be'),
17
20
    (codecs.BOM_UTF16_LE, 'utf_16_le'),
151
154
    return unicode(s, 'ISO-8859-1', 'replace')
152
155
 
153
156
 
154
 
# def unicode_to_unaccented_str(text):
155
 
#     """Converts a unicode string into an ascii-only str, converting accented
156
 
#     characters to their plain equivalents.
157
 
#
158
 
#     >>> unicode_to_unaccented_str(u'')
159
 
#     ''
160
 
#     >>> unicode_to_unaccented_str(u'foo bar 123')
161
 
#     'foo bar 123'
162
 
#     >>> unicode_to_unaccented_str(u'viva S\xe3o Carlos!')
163
 
#     'viva Sao Carlos!'
164
 
#     """
165
 
#     assert isinstance(text, unicode)
166
 
#     L = []
167
 
#     for char in text:
168
 
#         charnum = ord(char)
169
 
#         codepoint = codepoint2name.get(charnum)
170
 
#         if codepoint is not None:
171
 
#             strchar = codepoint[0]
172
 
#         else:
173
 
#             try:
174
 
#                 strchar = char.encode('ascii')
175
 
#             except UnicodeEncodeError:
176
 
#                 strchar = ''
177
 
#         L.append(strchar)
178
 
#     return ''.join(L)
179
 
 
180
 
 
181
157
def ascii_smash(unicode_string):
182
158
    """Attempt to convert the Unicode string, possibly containing accents,
183
159
    to an ASCII string.
370
346
    if match is not None:
371
347
        return match.group(1)
372
348
 
373
 
    # Something we can"t represent. Return empty string.
 
349
    # Something we can't represent. Return empty string.
374
350
    return ""
375
351
 
 
352
 
 
353
def escape_nonascii_uniquely(bogus_string):
 
354
    """Replace non-ascii characters with a hex representation.
 
355
 
 
356
    This is mainly for preventing emails with invalid characters from causing
 
357
    oopses. The nonascii characters could have been removed or just converted
 
358
    to "?", but this provides some insight into what the bogus data was, and
 
359
    it prevents the message-id from two unrelated emails matching because
 
360
    all the nonascii characters have been replaced with the same ascii
 
361
    character.
 
362
 
 
363
    Unfortunately, all the strings below are actually part of this
 
364
    function's docstring, so python processes the backslash once before
 
365
    doctest, and then python processes it again when doctest runs the
 
366
    test. This makes it confusing, since four backslashes will get
 
367
    converted into a single ascii character.
 
368
 
 
369
    >>> print len('\xa9'), len('\\xa9'), len('\\\\xa9')
 
370
    1 1 4
 
371
    >>> print escape_nonascii_uniquely('hello \xa9')
 
372
    hello \\xa9
 
373
    >>> print escape_nonascii_uniquely('hello \\xa9')
 
374
    hello \\xa9
 
375
 
 
376
    This string only has ascii characters, so escape_nonascii_uniquely()
 
377
    actually has no effect.
 
378
 
 
379
    >>> print escape_nonascii_uniquely('hello \\\\xa9')
 
380
    hello \\xa9
 
381
    """
 
382
    nonascii_regex = re.compile(r'[\200-\377]')
 
383
    # By encoding the invalid ascii with a backslash, x, and then the
 
384
    # hex value, it makes it easy to decode it by pasting into a python
 
385
    # interpreter. quopri() is not used, since that could caused the
 
386
    # decoding of an email to fail.
 
387
    def quote(match):
 
388
        return '\\x%x' % ord(match.group(0))
 
389
    return nonascii_regex.sub(quote, bogus_string)