包含BOM的UTF-8 HTML和CSS文件(以及如何使用Python删除BOM)

时间:2010-03-16 16:53:14

标签: python file utf-8 byte-order-mark

首先,一些背景知识:我正在使用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在看到BOM时会自动生成<html>代码并将其误认为内容,从而使真正的<html>代码出错。

那么,使用Python,从我的UTF-8编码模板中删除BOM的最佳方法是什么(如果存在 - 我将来不能保证这一点)?

对于其他基于文本的文件,如CSS,主要浏览器是否会正确解释(或忽略)BOM?它们被发送为没有.decode('utf-8')的纯二进制数据。

注意:我使用的是Python 2.5。

谢谢!

4 个答案:

答案 0 :(得分:23)

由于您声明:

  

目前我的所有(文本)文件   以BOM

存储在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!'

它会自动删除预期的BOM,并且如果BOM也不存在也能正常工作。

答案 1 :(得分:10)

解码后检查第一个字符,看它是否为BOM:

if u.startswith(u'\ufeff'):
  u = u[1:]

答案 2 :(得分:1)

之前接受的答案是错误的。

u'\ufffe'不是角色。如果你用一个unicode字符串得到它,那么有人已经把它塞满了。

BOM(又名ZERO WIDTH NO-BREAK SPACE)是u'\ufeff'

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

阅读this(Ctrl-F搜索BOM)和this以及this(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 如果您尚未解码输入,并希望检查其是否有物料清单,则需要检查UTF-16的 TWO 不同物料清单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不提供它们的编码,我从来没有听说过实际发生,所以我不烦。

答案 3 :(得分:0)

您可以使用类似的功能删除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()