Если копнуть глубже, текст в почтовых сообщениях может быть еще разнообразнее, чем предполагалось до сих пор. В принципе, текстовые части внутри одного и того же почтового сообщения могут быть представлены в различных кодировках (например, три HTML-файла, вложенные в письмо, в разных кодировках, возможно, отличающихся от кодировки полного текста сообщения). Обращение с таким текстом как со строками двоичных байтов иногда позволяет ловко обойти проблемы кодирования, однако при сохранении таких частей в текстовых файлах необходимо учитывать оригинальные кодировки. Кроме того, любые операции по обработке текста, применяемые к этим частям, точно так же зависят от типа.
К счастью, пакет email добавляет заголовки с названием кодировок при создании текста сообщения и позволяет получить информацию о кодировках для частей, где она указана, при анализе текста сообщения. Например, при добавлении вложений с текстом, который не является текстом ASCII, нужно просто указать имя кодировки — соответствующие заголовки будут добавлены автоматически при создании полного текста сообщения, а кодировки можно будет получить непосредственно с помощью метода get_content_charset:
> >> s = b’A\xe4B’
> >> s.decode(‘latin1’)
‘A.a.B’
> >> from email.message import Message
> >> m = Message()
> >> m.set_payload(b’A\xe4B’, charset=’latin1′) # или ‘latin-1’: см. далее
> >> t = m.as_string()
> >> print(t)
MIME-Version: 1.0
Content-Type: text/plain; charset="latin1"
Content-Transfer-Encoding: base64
QeRC
> >> m.get_content_charset() ‘latin1’
Обратите внимание, что пакет email автоматически применяет MIME- кодирование Base64 к частям с текстом, который не является текстом ASCII, чтобы обеспечить соответствие стандартам электронной почты. То же самое справедливо для более специализированных подклассов с поддержкой преобразования в формат MIME класса Message:
> >> from email.mime.text import MIMEText
> >> m = MIMEText(b’A\xe4B’, _charset=’latin1′)
> >> t = m.as_string()
> >> print(t)
Content-Type: text/plain; charset="latin1"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
QeRC
> >> m.get_content_charset() ‘latin1’
Если теперь проанализировать текст этого сообщения с помощью пакета email, мы получим новый объект Message, содержимое которого в формате MIME Base64 представляет строку Юникода с символами не из диапазона ASCII. Если затребовать содержимое с декодированием из формата MIME, указав аргумент decode=1, будет возвращена строка байтов, которую мы ранее вложили в сообщение:
> >> from email.parser import Parser
>>> q = Parser().parsestr(t)
>>> q
<email.message.Message object at 0x019ECA50>
>>> q.get_content_type()
‘text/plain’
>>> q._payload
‘QeRC\n’
>>> q.get_payload()
‘QeRC\n’
> >> q.get_payload(decode=1)
b’A\xe4B’
Однако попытка получить текст декодированием этой строки байтов с использованием кодировки по умолчанию в Windows (UTF8) потерпит неудачу. Для большей точности и поддержки большего разнообразия типов текста необходимо использовать информацию о кодировках, сохраненную при анализе и прикрепленную к объекту Message. Это особенно важно, когда может возникнуть необходимость сохранить данные в файл — мы должны будем либо сохранять данные в двоичном режиме, либо указывать корректную (или, по крайней мере, совместимую) кодировку, чтобы сохранять такие строки в текстовых файлах. Декодирование вручную выполняется точно так же:
> >> q.get_payload(decode=1).decode()
UnicodeDecodeError: ‘utf8’ codec can’t decode bytes in position 1-2: unexpected…
(UnicodeDecodeError: кодек ‘utf8’ не может декодировать байты
в позиции 1—2: неожиданный…)
> >> q.get_content_charset()
‘latin1’
> >> q.get_payload(decode=1).decode(‘latin1’) # известный тип
‘A.a.B’
> >> q.get_payload(decode=1).decode(q.get_content_charset()) # для любого
# типа
‘A.a.B‘
На самом деле, в объектах Message имеется информация обо всех заголовках, нужно только знать, как ее найти. Информация о кодировке может вообще отсутствовать, и в этом случае возвращается значение None. Клиенты должны предусматривать политику обращения с таким неоднозначным текстом (они могут попытаться применить наиболее распространенные кодировки, определить кодировку по содержимому или обращаться с данными, как с простой строкой байтов):
>>> q[‘content-type’] # интерфейс отображения
‘text/plain; charset="latin1"’
>>> q.items()
[(‘Content-Type’, ‘text/plain; charset="latin1"’), (‘MIME-Version’, ‘1.0’), (‘Content-Transfer-Encoding’, ‘base64’)]
>> q.get_params(header=’Content-Type’) # интерфейс параметров
[(‘text/plain’, »), (‘charset’, ‘latin1’)]
>>> q.get_param(‘charset’, header=’Content-Type’)
‘latin1’
>>> charset = q.get_content_charset() # информация может отсутствовать
>>> if charset:
… print(q.get_payload(decode=1).decode(charset))
…
A.a.B
Так обрабатываются кодировки текстовых частей, полученных в результате анализа электронных писем. При составлении новых почтовых сообщений нам по-прежнему необходимо применять пользовательские настройки сеанса или дать пользователю возможность указывать кодировку для каждой части в интерактивном режиме. В некоторых клиентах электронной почты, представленных в этой книге, преобразование содержимого выполняется по мере необходимости. При этом используется информация о кодировках в заголовках сообщений, полученных в результате анализа, и предоставленная пользователями в ходе составления новых электронных писем.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011