UTF-8 HTML и CSS файлы с спецификацией (и как удалить спецификацию с Python)
Во-первых, некоторый фон: я разрабатываю веб-приложение с использованием 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.
Спасибо!
Ответы
Ответ 1
Поскольку вы указываете:
Все мои (текстовые) файлы в настоящее время хранится в 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!'
Он автоматически удаляет ожидаемую спецификацию и работает правильно, если спецификация тоже отсутствует.
Ответ 2
Проверьте первый символ после декодирования, чтобы увидеть, является ли это спецификацией:
if u.startswith(u'\ufeff'):
u = u[1:]
Ответ 3
Ранее принятый ответ WRONG.
u'\ufffe'
не является символом. Если вы получите его в строке юникода, кто-то сильно наполнился.
Спецификация (так называемое ЗЕРНО-ШИРИННОЕ ЗОНЫ) - u'\ufeff'
>>> UNICODE_BOM = u'\N{ZERO WIDTH NO-BREAK SPACE}'
>>> UNICODE_BOM
u'\ufeff'
>>>
Прочитайте this (поиск по BOM) (Ctrl-F) и это и this (поиск по BOM) (Ctrl-F).
Здесь правильный и опечаток/мозговой резистентный ответ:
Декодируйте свой вход в unicode_str
. Затем сделайте следующее:
# If I mistype the following, it 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 Если вы еще не декодировали свой вход и хотите проверить его на спецификацию, вам нужно проверить наличие TWOstrong > разных спецификаций для UTF-16 и не менее TWOstrong > разных спецификаций для UTF-32. Если бы был только один способ, то вам не нужна спецификация, не так ли?
Здесь verbatim unprettified из моего собственного кода - это мое решение:
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 байтов вашего ввода. Он возвращает кодировку, которая может использоваться для декодирования части послепечатной части вашего ввода, плюс длина спецификации (если она есть).
Если вы параноик, вы можете разрешить еще 2 (нестандартных) заказа UTF-32, но Python не предоставляет кодировку для них, и я никогда не слышал о реальном возникновении, поэтому я не беспокоить.
Ответ 4
Вы можете использовать что-то похожее для удаления спецификации:
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()