2017-09-08 15 views
0

mon code lit des lignes de fichiers csv avec du texte corrompu - mélange de ASCII et oct. Et j'essaie de récupérer un texte original en UTF-8 mais il me manque quelque chose d'évident.Comment traiter le texte corrompu comme des données dans Python 3.x

line = "Tom\303\241\305\241 Vala" #Tomáš Vala 
print(a) 
Tomáš Vala #incorrect 

Si je tape la ligne manuellement dans la ligne de commande le résultat est correct:

>>> a = b"Tom\303\241\305\241 Vala" 
>>> a = a.decode("utf-8") 
'Tomáš Vala' # correct 

Mais comment imprimer la ligne comme il est déjà en octets?

>>> a = "Tom\303\241\305\241 Vala" 
>>> print(a) 
Tomáš Vala #incorrect 

>>> b = bytes(a, 'utf=8') 
>>> b.decode('utf=8') 
'Tomáš Vala' #incorrect 
+0

Votre * données réelles * contient des séquences '\ ddd'? C'est une barre oblique inverse littérale et trois chiffres. Ce n'est pas la même chose que d'utiliser '\ ddd' dans un littéral de chaîne Python, où ces séquences ont un sens. Pouvez-vous donner un échantillon de vos données réelles à la place? –

+0

Vos données peuvent être définies dans un littéral de chaîne Python comme 'r 'Tom \ 303 \ 241 \ 305 \ 241 Vala" ', notez le' r' au début, ou en doublant les antislashs. –

+0

Le fichier contient des lignes au format: Tom \ 303 \ 241 \ 305 \ 241 Vala, V \ 303 \ 241lec, 1.1.1984, – Panther

Répondre

0

Vous devez traduire toutes les séquences d'échappement antislash littérales. Vous pouvez le faire avec une expression régulière:

import re 

seq = re.compile(br'\\[0-8]{3}') 
decode_seq = lambda m: bytes([int(m.group()[1:], 8)]) 
def repair(data): 
    return seq.sub(decode_seq, data) 

Cette décode les données dans un objet bytes:

>>> broken = rb"Tom\303\241\305\241 Vala" 
>>> repair(broken) 
b'Tom\xc3\xa1\xc5\xa1 Vala' 
>>> repair(broken).decode('utf8') 
'Tomáš Vala' 

Pour envelopper un fichier existant, vous auriez à mettre en œuvre un io.BufferedIOBase subclass traduire octets que vous lire:

import re 
from io import BufferedIOBase 

class OctetEscapeDecodeWrapper(BufferedIOBase): 
    def __init__(self, buffer): 
     # we wrap a buffer, not a raw object, so don't use raw here. 
     self._buffer = buffer 
     self._remainder = b'' 

    def readable(self): 
     return True 

    def detach(self): 
     result, self._buffer = self._buffer, None 
     return result 

    def _decode(self, data, 
       _seq=re.compile(br'\\[0-8]{3}'), 
       _decode=lambda m: bytes([int(m.group()[1:], 8)])): 
     return _seq.sub(_decode, data) 

    def read1(self, size=-1): 
     self._remainder, data = b'', self._remainder + self._buffer.read1(size) 
     trail = data.rfind(b'\\', -3) 
     if trail > -1 and all(48 <= data[i] <= 57 for i in range(trail + 1, len(data))): 
      # data ends \dd or \d, retain until next read so we can decode then 
      self._remainder, data = data[trail:], data[:trail] 
     return self._decode(data) 

    read = read1 

    def readinto1(self, b): 
     data = self.read1(len(b)) 
     b[:len(data)] = data 
     return len(data) 

    readinto = readinto1 

Cela peut être utilisé pour envelopper un fichier binaire existant pour décoder vos données à la volée:

import csv 
from io import TextIOWrapper 

with open(path_to_file, 'rb') as binary: 
    text = TextIOWrapper(OctetEscapeDecodeWrapper(binary), encoding='utf8') 
    reader = csv.reader(text) 
    for row in reader: 
     # ... 

Démo:

>>> from io import BytesIO, TextIOWrapper 
>>> sample = BytesIO(b'Tom\303\241\305\241 Vala, V\303\241lec, 1.1.1984,') 
>>> b = OctetEscapeDecodeWrapper(sample) 
>>> t = TextIOWrapper(b, encoding='utf8') 
>>> import csv 
>>> next(csv.reader(t)) 
['Tomáš Vala', ' Válec', ' 1.1.1984', ''] 
+0

Fonctionne parfaitement. Mais comme je suis novice en python et en programmation, je dois d'abord comprendre ce que vous avez fait ici. Thx – Panther

+0

@Panther: J'ai ajouté un wrapper de décodage pour décoder les données de fichiers à la volée. –

+0

Merci beaucoup pour votre aide généreuse. J'ai appris quelque chose de nouveau aujourd'hui. – Panther