带有 BOM 的 UTF-8 HTML 和 CSS 文件(以及如何使用 Python 删除 BOM)
-
20-09-2019 - |
题
首先,一些背景:我正在使用 Python 开发一个 Web 应用程序。我的所有(文本)文件当前都以带有 BOM 的 UTF-8 格式存储。这包括我所有的 HTML 模板和 CSS 文件。这些资源作为二进制数据(BOM 和所有)存储在我的数据库中。
当我从数据库检索模板时,我使用 template.decode('utf-8')
. 。当 HTML 到达浏览器时,BOM 出现在 HTTP 响应正文的开头。这会在 Chrome 中产生一个非常有趣的错误:
Extra <html> encountered. Migrating attributes back to the original <html> element and ignoring the tag.
Chrome 似乎会生成一个 <html>
当它看到 BOM 并将其误认为是内容时,会自动标记,从而使真正的 <html>
标记一个错误。
那么,使用 Python,从我的 UTF-8 编码模板中删除 BOM 的最佳方法是什么(如果存在 - 我不能保证将来会这样做)?
对于 CSS 等其他基于文本的文件,主流浏览器能否正确解释(或忽略)BOM?它们以纯二进制数据的形式发送,无需 .decode('utf-8')
.
笔记:我正在使用Python 2.5。
谢谢!
解决方案
由于你状态:
我所有的(文本)文件正在 存储在UTF-8与BOM
然后使用 'UTF-8-SIG' 编解码器来解码:
>>> s = u'Hello, world!'.encode('utf-8-sig')
>>> s
'\xef\xbb\xbfHello, world!'
>>> s.decode('utf-8-sig')
u'Hello, world!'
有自动删除预期BOM,并且正常工作,如果BOM不存在为好。
其他提示
进行解码,以看是否它的BOM后检查第一个字符:
if u.startswith(u'\ufeff'):
u = u[1:]
之前接受的答案是错误的。
u'\ufffe'
不是一个字符。如果你把它放在一个 unicode 字符串中,那么有人已经把它塞满了。
BOM(又名零宽度无中断空间)是 u'\ufeff'
>>> UNICODE_BOM = u'\N{ZERO WIDTH NO-BREAK SPACE}'
>>> UNICODE_BOM
u'\ufeff'
>>>
读 这 (Ctrl-F 搜索 BOM)和 这 和 这 (Ctrl-F 搜索 BOM)。
这是一个正确且防错字/耐脑的答案:
将您的输入解码为 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 如果您尚未解码输入,并希望检查 BOM,则需要检查 二 UTF-16 的 BOM 不同,并且至少 二 UTF-32 的不同 BOM。如果每种方法只有一种,那么您就不需要 BOM,不是吗?
这里逐字未经过我自己的代码修饰是我对此的解决方案:
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 后部分的编码,以及 BOM 的长度(如果有)。
如果你偏执,你可以允许另外 2 个(非标准)UTF-32 排序,但 Python 不提供它们的编码,而且我从未听说过实际发生的情况,所以我不打扰。
您可以使用类似的东西删除BOM:
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()