UTF-8 HTML и CSS файлы с BOM (и как удалить BOM с помощью Python) - PullRequest
12 голосов
/ 16 марта 2010

Во-первых, немного предыстории: я разрабатываю веб-приложение с использованием Python. Все мои (текстовые) файлы в настоящее время хранятся в UTF-8 с спецификацией. Это включает в себя все мои HTML-шаблоны и CSS-файлы. Эти ресурсы хранятся в виде двоичных данных (спецификация и все) в моей БД.

Когда я получаю шаблоны из БД, я декодирую их, используя template.decode('utf-8'). Когда HTML поступает в браузер, спецификация присутствует в начале тела ответа HTTP. Это приводит к очень интересной ошибке в Chrome:

Extra <html> encountered. Migrating attributes back to the original <html> element and ignoring the tag.

Похоже, что Chrome автоматически генерирует тег <html>, когда видит спецификацию и ошибочно принимает ее за содержание, что делает настоящий тег <html> ошибкой.

Итак, используя Python, каков наилучший способ удаления спецификации из моих кодированных шаблонов UTF-8 (если она существует - я не могу гарантировать это в будущем)?

Для других текстовых файлов, таких как CSS, будут ли основные браузеры правильно интерпретировать (или игнорировать) спецификацию? Они отправляются в виде простых двоичных данных без .decode('utf-8').

Примечание: я использую Python 2.5.

Спасибо!

Ответы [ 4 ]

23 голосов
/ 17 марта 2010

Так как вы заявляете:

Все мои (текстовые) файлы в настоящее время хранится в UTF-8 с спецификацией

затем используйте кодек utf-8-sig для их декодирования:

>>> s = u'Hello, world!'.encode('utf-8-sig')
>>> s
'\xef\xbb\xbfHello, world!'
>>> s.decode('utf-8-sig')
u'Hello, world!'

Он автоматически удаляет ожидаемую спецификацию и работает правильно, если эта спецификация также отсутствует.

10 голосов
/ 16 марта 2010

Проверьте первый символ после декодирования, чтобы увидеть, если это спецификация:

if u.startswith(u'\ufeff'):
  u = u[1:]
1 голос
/ 17 марта 2010

Ранее принятый ответ - НЕПРАВИЛЬНО.

u'\ufffe' не является символом.Если вы получите его в виде строки в кодировке Unicode, кто-то могучим образом набил.

Спецификация (также известная как ZERO WIDTH NO-BREAK SPACE) равна u'\ufeff'

>>> UNICODE_BOM = u'\N{ZERO WIDTH NO-BREAK SPACE}'
>>> UNICODE_BOM
u'\ufeff'
>>>

Читать this (Ctrl-F для поиска спецификации) и this и this (Ctrl-F для поиска спецификации).

Вот правильная и устойчивая к опечаткам и брейноответ:

Расшифруйте ваш ввод в unicode_str.Затем сделайте следующее:

# If I mistype the following, it's very likely to cause a SyntaxError.
UNICODE_BOM = u'\N{ZERO WIDTH NO-BREAK SPACE}'
if unicode_str and unicode_str[0] == UNICODE_BOM:
    unicode_str = unicode_str[1:]

Бонус: использование именованной константы дает вашим читателям немного больше информации о происходящем, чем набор произвольных, казалось бы, гексоглификов.

Обновление К сожалению, в стандартной библиотеке Python нет подходящей именованной константы.

Увы, модуль кодеков обеспечивает только "ловушку и заблуждение":

>>> import pprint, codecs
>>> pprint.pprint([(k, getattr(codecs, k)) for k in dir(codecs) if k.startswith('BOM')])
[('BOM', '\xff\xfe'),   #### aarrgghh!! ####
 ('BOM32_BE', '\xfe\xff'),
 ('BOM32_LE', '\xff\xfe'),
 ('BOM64_BE', '\x00\x00\xfe\xff'),
 ('BOM64_LE', '\xff\xfe\x00\x00'),
 ('BOM_BE', '\xfe\xff'),
 ('BOM_LE', '\xff\xfe'),
 ('BOM_UTF16', '\xff\xfe'),
 ('BOM_UTF16_BE', '\xfe\xff'),
 ('BOM_UTF16_LE', '\xff\xfe'),
 ('BOM_UTF32', '\xff\xfe\x00\x00'),
 ('BOM_UTF32_BE', '\x00\x00\xfe\xff'),
 ('BOM_UTF32_LE', '\xff\xfe\x00\x00'),
 ('BOM_UTF8', '\xef\xbb\xbf')]
>>>

Обновление 2 Если вы еще не декодировали свой ввод и хотите проверить его на предмет спецификации, вам нужно проверить ДВА разных спецификаций для UTF-16 и, по крайней мере, ДВА разные спецификации для UTF-32.Если бы был только один способ, то вам бы не понадобилась спецификация, не так ли?

Здесь дословно неоправданный по моему собственному коду мой способ решения этой проблемы:

def check_for_bom(s):
    bom_info = (
        ('\xFF\xFE\x00\x00', 4, 'UTF-32LE'),
        ('\x00\x00\xFE\xFF', 4, 'UTF-32BE'),
        ('\xEF\xBB\xBF',     3, 'UTF-8'),
        ('\xFF\xFE',         2, 'UTF-16LE'),
        ('\xFE\xFF',         2, 'UTF-16BE'),
        )
    for sig, siglen, enc in bom_info:
        if s.startswith(sig):
            return enc, siglen
    return None, 0

Входные данныеs должно быть по крайней мере первые 4 байта вашего ввода.Он возвращает кодировку, которую можно использовать для декодирования части ввода после BOM, плюс длину спецификации (если есть).

Если вы параноик, вы могли бы разрешить еще 2 (нестандарт) UTF-32, но Python не предоставляет для них кодировку, и я никогда не слышал о реальном происшествии, поэтому я не беспокоюсь.

0 голосов
/ 16 марта 2010

Вы можете использовать нечто похожее для удаления спецификации:

import os, codecs
def remove_bom_from_file(filename, newfilename):
    if os.path.isfile(filename):
        # open file
        f = open(filename,'rb')

        # read first 4 bytes
        header = f.read(4)

        # check if we have BOM...
        bom_len = 0
        encodings = [ ( codecs.BOM_UTF32, 4 ),
            ( codecs.BOM_UTF16, 2 ),
            ( codecs.BOM_UTF8, 3 ) ]

        # ... and remove appropriate number of bytes    
        for h, l in encodings:
            if header.startswith(h):
                bom_len = l
                break
        f.seek(0)
        f.read(bom_len)

        # copy the rest of file
        contents = f.read() 
        nf = open(newfilename)
        nf.write(contents)
        nf.close()
...