Подтвердить что ты не робот

Преобразование UTF-8 с спецификацией в UTF-8 без спецификации в Python

Здесь два вопроса. У меня есть набор файлов, которые обычно являются UTF-8 с BOM. Я бы хотел конвертировать их (в идеале на место) в UTF-8 без спецификации. Кажется, что codecs.StreamRecoder(stream, encode, decode, Reader, Writer, errors) справится с этим. Но я не вижу хороших примеров использования. Будет ли это лучшим способом справиться с этим?

source files:
Tue Jan 17$ file brh-m-157.json 
brh-m-157.json: UTF-8 Unicode (with BOM) text

Кроме того, было бы идеально, если бы мы могли обрабатывать различную кодировку ввода без явного знания (см. ASCII и UTF-16). Похоже, это все должно быть осуществимо. Есть ли решение, которое может принимать любую известную кодировку Python и выводить ее как UTF-8 без спецификации?

отредактируйте 1 предложенный снимок снизу (спасибо!)

fp = open('brh-m-157.json','rw')
s = fp.read()
u = s.decode('utf-8-sig')
s = u.encode('utf-8')
print fp.encoding  
fp.write(s)

Это дает мне следующую ошибку:

IOError: [Errno 9] Bad file descriptor

Newsflash

Мне сообщают в комментариях, что ошибка заключается в том, что я открываю файл с режимом 'rw' вместо 'r +'/'r + b', поэтому я должен в конечном итоге изменить свой вопрос и удалить решенную часть.

4b9b3361

Ответ 1

Просто используйте кодек "utf-8-sig":

fp = open("file.txt")
s = fp.read()
u = s.decode("utf-8-sig")

Это дает вам строку unicode без спецификации. Вы можете использовать

s = u.encode("utf-8")

чтобы получить нормальную строку в кодировке UTF-8 обратно в s. Если ваши файлы большие, вам следует избегать их чтения в память. BOM - это просто три байта в начале файла, так что вы можете использовать этот код для удаления их из файла:

import os, sys, codecs

BUFSIZE = 4096
BOMLEN = len(codecs.BOM_UTF8)

path = sys.argv[1]
with open(path, "r+b") as fp:
    chunk = fp.read(BUFSIZE)
    if chunk.startswith(codecs.BOM_UTF8):
        i = 0
        chunk = chunk[BOMLEN:]
        while chunk:
            fp.seek(i)
            fp.write(chunk)
            i += len(chunk)
            fp.seek(BOMLEN, os.SEEK_CUR)
            chunk = fp.read(BUFSIZE)
        fp.seek(-BOMLEN, os.SEEK_CUR)
        fp.truncate()

Он открывает файл, читает фрагмент и записывает его в файл на 3 байта раньше, чем там, где он его прочитал. Файл переписан на месте. Как более простое решение - записать более короткий файл в новый файл, например, новый ответ. Это было бы проще, но за короткий промежуток времени удвоить дисковое пространство.

Что касается угадывания кодировки, то вы можете просто перебрать кодировку от самой к наименее определенной:

def decode(s):
    for encoding in "utf-8-sig", "utf-16":
        try:
            return s.decode(encoding)
        except UnicodeDecodeError:
            continue
    return s.decode("latin-1") # will always work

Файл в кодировке UTF-16 не будет декодироваться как UTF-8, поэтому сначала мы попробуем использовать UTF-8. Если это не удастся, тогда мы попробуем с UTF-16. Наконец, мы используем Latin-1 - это всегда будет работать, поскольку все 256 байтов являются допустимыми значениями в Latin-1. Возможно, вы захотите вернуть None вместо этого в этом случае, так как это действительно запасной вариант, и ваш код может захотеть обработать это более осторожно (если это возможно).

Ответ 2

В Python 3 это довольно просто: прочитайте файл и перепишите его с помощью utf-8 encoding:

s = open(bom_file, mode='r', encoding='utf-8-sig').read()
open(bom_file, mode='w', encoding='utf-8').write(s)

Ответ 3

import codecs
import shutil
import sys

s = sys.stdin.read(3)
if s != codecs.BOM_UTF8:
    sys.stdout.write(s)

shutil.copyfileobj(sys.stdin, sys.stdout)

Ответ 4

Это моя реализация для преобразования любого вида кодировки в UTF-8 без спецификации и замены окон за счет универсального формата:

def utf8_converter(file_path, universal_endline=True):
    '''
    Convert any type of file to UTF-8 without BOM
    and using universal endline by default.

    Parameters
    ----------
    file_path : string, file path.
    universal_endline : boolean (True),
                        by default convert endlines to universal format.
    '''

    # Fix file path
    file_path = os.path.realpath(os.path.expanduser(file_path))

    # Read from file
    file_open = open(file_path)
    raw = file_open.read()
    file_open.close()

    # Decode
    raw = raw.decode(chardet.detect(raw)['encoding'])
    # Remove windows end line
    if universal_endline:
        raw = raw.replace('\r\n', '\n')
    # Encode to UTF-8
    raw = raw.encode('utf8')
    # Remove BOM
    if raw.startswith(codecs.BOM_UTF8):
        raw = raw.replace(codecs.BOM_UTF8, '', 1)

    # Write to file
    file_open = open(file_path, 'w')
    file_open.write(raw)
    file_open.close()
    return 0

Ответ 5

Вы можете использовать кодеки.

import codecs
with open("test.txt",'r') as filehandle:
    content = filehandle.read()
if content[:3] == codecs.BOM_UTF8:
    content = content[3:]
print content.decode("utf-8")

Ответ 6

Недавно я попытался создать несколько старых проектов Android, над которыми я работал давно, но исходные файлы имели в них спецификацию и не могли скомпилироваться. Было сотни файлов, поэтому я попытался найти некоторые инструменты в ОС MAC, которые могли бы их автоматически преобразовывать, но не могли найти их, поэтому я написал python script, который удаляет спецификацию из файлов в папке:

BomSweeper

Надеюсь, это поможет вам.

Примечание. script основан на ответе @Martin Geisler, я бы хотел добавить комментарий к его ответу, но у меня недостаточно репутации, чтобы сделать это, поэтому я просто создаю новый ответ.

Ответ 7

Я нашел этот вопрос, потому что возникли проблемы с configparser.ConfigParser().read(fp) при открытии файлов с заголовком спецификации UTF8.

Для тех, кто ищет решение для удаления заголовка, чтобы ConfigPhaser мог открыть файл конфигурации вместо сообщения об ошибке: File contains no section headers, откройте файл следующим образом:

configparser.ConfigParser().read(config_file_path, encoding="utf-8-sig")

Это может сэкономить массу усилий, сделав ненужным удаление заголовка спецификации файла.

(Я знаю, это звучит несвязанно, но, надеюсь, это может помочь людям, борющимся как я.)