Convertir UTF-8 avec nomenclature en UTF-8 sans nomenclature en Python

Deux questions ici. J'ai un ensemble de fichiers qui sont généralement UTF-8 avec BOM. Je voudrais les convertir (idéalement en place) en UTF-8 sans nomenclature. Il semble que codecs.StreamRecoder(stream, encode, decode, Reader, Writer, errors) gérerait cela. Mais je ne vois pas vraiment de bons exemples sur l'utilisation. Serait-ce la meilleure façon de gérer cela?

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

En outre, il serait idéal si nous pouvions gérer différents encodages d'entrée sans le savoir explicitement (vu ASCII et UTF-16). Il semble que tout cela devrait être faisable. Est-il une solution qui peut prenez n'importe quel encodage Python connu et sortie en UTF-8 sans nomenclature?

Edit 1 sol'n proposé par le bas (merci!)

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)

Cela me donne l'erreur suivante:

IOError: [Errno 9] Bad file descriptor

Newsflash

On me dit dans les commentaires que l'erreur est que j'ouvre le fichier avec le mode 'rw' au lieu de 'r+' / 'r + b', donc je devrais éventuellement rééditer ma question et supprimer la partie résolue.

50
demandé sur tzot 2012-01-17 20:37:52

6 réponses

Utilisez simplement le codec "utf-8-sig" :

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

Cela vous donne une chaîne unicode sans la nomenclature. Vous pouvez ensuite utiliser

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

Pour récupérer une chaîne codée UTF-8 normale dans s. Si vos fichiers sont volumineux, vous devriez éviter de les lire tous en mémoire. La nomenclature est simplement de trois octets au début du fichier, vous pouvez donc utiliser ce code pour les supprimer du fichier:

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()

, Il ouvre le fichier, lit un morceau, et l'écrit dans le fichier 3 octets plus tôt celui où il le lire. Le fichier est réécrit sur place. La solution la plus simple consiste à écrire le fichier le plus court dans un nouveau fichier comme la réponse de newtover. Ce serait plus simple, mais utilisez deux fois l'espace disque pendant une courte période.

Quant à deviner l'encodage, alors vous pouvez simplement parcourir l'encodage du plus au moins spécifique:

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

Un fichier encodé en UTF-16 ne décodera pas en UTF-8, donc nous essayons D'abord avec UTF-8. Si cela échoue, alors nous essayons avec UTF-16. Enfin, nous utilisons Latin-1 - cela fonctionnera toujours puisque tous les 256 octets sont des valeurs légales en Latin-1. Vous voudrez peut-être retourner None à la place dans ce cas, car c'est vraiment une solution de repli et votre code voudra peut-être gérer cela plus attentivement (si c'est possible).

79
répondu Martin Geisler 2018-07-18 20:33:46

En Python 3, c'est assez simple: lisez le fichier et réécrivez-le avec utf-8 encodage:

s = open(bom_file, mode='r', encoding='utf-8-sig').read()
open(bom_file, mode='w', encoding='utf-8').write(s)
23
répondu Geng Jiawen 2015-10-29 19:30:02
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)
6
répondu newtover 2012-01-17 17:03:45

C'est mon implémentation pour convertir n'importe quel type d'encodage en UTF-8 sans nomenclature et remplacer Windows enlines par un format universel:

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
4
répondu estevo 2014-05-14 08:04:53

Vous pouvez utiliser des codecs.

import codecs
content = open("test.txt",'r').read()
filehandle.close()
if content[:3] == codecs.BOM_UTF8
content = content[3:]
print content.decode("utf-8")
0
répondu wcc526 2015-02-09 11:52:05

J'ai trouvé cette question parce que j'ai des problèmes avec configparser.ConfigParser().read(fp) lors de l'ouverture de fichiers avec L'en-tête de nomenclature UTF8.

Pour ceux qui recherchent une solution pour supprimer L'en-tête afin que ConfigPhaser puisse ouvrir le fichier de configuration au lieu de signaler une erreur de: File contains no section headers, veuillez ouvrir le fichier comme suit:

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

Cela pourrait vous faire économiser des tonnes d'efforts en rendant inutile la suppression de l'en-tête de nomenclature du fichier.

(je sais que cela ne semble pas lié, mais j'espère que cela pourrait aider les gens qui luttent comme moi.)

0
répondu Alto.Clef 2017-09-07 01:22:18