web-dev-qa-db-fra.com

Conversion de UTF-8 avec BOM en UTF-8 sans BOM en Python

Deux questions ici. J'ai un ensemble de fichiers qui sont généralement UTF-8 avec BOM. J'aimerais les convertir (idéalement en place) en UTF-8 sans nomenclature. Il semble que codecs.StreamRecoder(stream, encode, decode, Reader, Writer, errors) s'en chargerait. Mais je ne vois pas vraiment de bons exemples d'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 codages d’entrée sans le savoir explicitement (vu ASCII et UTF-16). Il semble que tout cela devrait être faisable. Existe-t-il une solution pouvant prendre n’importe quel codage Python connu et générer le format UTF-8 sans nomenclature?

edit 1 sol'n proposé d'en 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 d'ouvrir le fichier avec le mode 'rw' au lieu de 'r +'/'r + b'. Je devrais donc éventuellement modifier ma question et supprimer la partie résolue.

54
timpone

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 alors utiliser

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

pour obtenir une chaîne encodée en UTF-8 normale dans s. Si vos fichiers sont volumineux, évitez de les lire tous en mémoire. La nomenclature se compose 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 que celui où il l'a lu. Le fichier est réécrit sur place. Une solution plus simple consiste à écrire le fichier le plus court dans un nouveau fichier tel que la réponse de newtover . Ce serait plus simple, mais utilisez deux fois plus d’espace disque pendant une courte période.

Pour deviner l’encodage, 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 se décodera pas en UTF-8, nous essayons donc d'abord avec UTF-8. Si cela échoue, 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. Dans ce cas, vous voudrez peut-être renvoyer None car il s'agit en réalité d'une solution de secours et votre code peut vouloir gérer cela plus attentivement (s'il le peut).

87
Martin Geisler

En Python 3, c'est très simple: lisez le fichier et réécrivez-le avec le codage utf-8:

s = open(bom_file, mode='r', encoding='utf-8-sig').read()
open(bom_file, mode='w', encoding='utf-8').write(s)
29
Geng Jiawen
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
newtover

Ceci est mon implémentation pour convertir tout type d'encodage en UTF-8 sans nomenclature et remplacer les 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
estevo

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

Pour ceux qui recherchent une solution pour supprimer l'en-tête afin que ConfigPhaser puisse ouvrir le fichier config au lieu de signaler une erreur de: File contains no section headers, veuillez l'ouvrir de la manière suivante:

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

Cela pourrait vous épargner des efforts considérables en rendant inutile la suppression de l'en-tête de nomenclature du fichier.

(Je sais que cela ne semble pas être lié, mais j'espère que cela pourrait aider des personnes qui luttent comme moi.)

0
Alto.Clef

Vous pouvez utiliser des codecs.

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")
0
wcc526