109 votes

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

Deux questions à ce sujet. 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. Est-ce que c'est 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

De plus, il serait idéal que nous puissions gérer différents encodages d'entrée sans le savoir explicitement (comme ASCII et UTF-16). Il semble que tout cela soit faisable. Existe-t-il une solution capable de prendre n'importe quel encodage Python connu et de le restituer en UTF-8 sans BOM ?

modifier 1 proposition de sol'n 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

Flash info

On me dit dans les commentaires que l'erreur vient du fait 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.

160voto

Martin Geisler Points 44779

Il suffit d'utiliser le "codec "utf-8-sig :

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

Cela vous donne une unicode sans la nomenclature. Vous pouvez alors utiliser

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

pour récupérer une chaîne encodée en UTF-8 normal dans s . Si vos fichiers sont volumineux, vous devez éviter de les lire tous en mémoire. Le BOM est simplement constitué 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 l'endroit 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 comme Réponse de newtover . Ce serait plus simple, mais cela utiliserait deux fois plus d'espace disque pendant une courte période.

Pour ce qui est de deviner l'encodage, il suffit de passer en boucle de l'encodage le plus spécifique 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 sera pas décodé en UTF-8, nous essayons donc d'abord avec UTF-8. En cas d'échec, nous essayons avec UTF-16. Enfin, nous utilisons Latin-1 - cela fonctionnera toujours puisque les 256 octets sont des valeurs légales en Latin-1. Vous pouvez renvoyer None dans ce cas, puisqu'il s'agit d'une solution de repli et que votre code pourrait vouloir gérer cela avec plus de soin (s'il le peut).

89voto

Geng Jiawen Points 85

En Python 3, c'est assez facile : 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)

8voto

newtover Points 12301
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)

6voto

Alto.Clef Points 91

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

Pour ceux qui cherchent 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 épargner des tonnes d'efforts en rendant inutile la suppression de l'en-tête de nomenclature du fichier.

(Je sais que cela n'a aucun rapport avec le sujet, mais j'espère que cela pourra aider les personnes qui luttent comme moi).

5voto

estevo Points 3

Il s'agit de mon implémentation pour convertir n'importe quel type d'encodage en UTF-8 sans BOM et en remplaçant les enlines de Windows 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X