Comment lire un fichier texte avec des encodages mixtes en Scala ou en Java?

J’essaie d’parsingr un fichier CSV, idéalement en utilisant weka.core.converters.CSVLoader. Cependant, le fichier que je possède n’est pas un fichier UTF-8 valide. C’est principalement un fichier UTF-8 mais certaines des valeurs de champs sont dans des encodages différents, donc il n’y a pas de codage dans lequel le fichier entier est valide, mais je dois quand même l’parsingr. En plus d’utiliser des bibliothèques Java comme Weka, je travaille principalement à Scala. Je ne suis même pas capable de lire le fichier dans scala.io.Source: Par exemple

Source. fromFile(filename)("UTF-8"). foreach(print); 

jette:

  java.nio.charset.MalformedInputException: Input length = 1 at java.nio.charset.CoderResult.throwException(CoderResult.java:277) at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:337) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:176) at java.io.InputStreamReader.read(InputStreamReader.java:184) at java.io.BufferedReader.fill(BufferedReader.java:153) at java.io.BufferedReader.read(BufferedReader.java:174) at scala.io.BufferedSource$$anonfun$iter$1$$anonfun$apply$mcI$sp$1.apply$mcI$sp(BufferedSource.scala:38) at scala.io.Codec.wrap(Codec.scala:64) at scala.io.BufferedSource$$anonfun$iter$1.apply(BufferedSource.scala:38) at scala.io.BufferedSource$$anonfun$iter$1.apply(BufferedSource.scala:38) at scala.collection.Iterator$$anon$14.next(Iterator.scala:150) at scala.collection.Iterator$$anon$25.hasNext(Iterator.scala:562) at scala.collection.Iterator$$anon$19.hasNext(Iterator.scala:400) at scala.io.Source.hasNext(Source.scala:238) at scala.collection.Iterator$class.foreach(Iterator.scala:772) at scala.io.Source.foreach(Source.scala:181) 

Je suis parfaitement content de jeter tous les personnages invalides ou de les remplacer par un mannequin. Je vais avoir beaucoup de texte comme celui-ci à traiter de diverses manières et je devrai peut-être transmettre les données à diverses bibliothèques tierces. Une solution idéale serait une sorte de paramètre global qui ferait en sorte que toutes les bibliothèques Java de bas niveau ignorent les octets non valides dans le texte, afin que je puisse appeler des bibliothèques tierces sur ces données sans modification.

SOLUTION:

 import java.nio.charset.CodingErrorAction import scala.io.Codec implicit val codec = Codec("UTF-8") codec.onMalformedInput(CodingErrorAction.REPLACE) codec.onUnmappableCharacter(CodingErrorAction.REPLACE) val src = Source. fromFile(filename). foreach(print) 

Merci à + Esailija de m’avoir orienté dans la bonne direction. Cela m’a conduit à Comment détecter les séquences d’octets UTF-8 illégales pour les remplacer dans le stream d’entrée Java? qui fournit la solution Java de base. Dans Scala, je peux définir ce comportement par défaut en rendant le codec implicite. Je pense que je peux en faire le comportement par défaut pour l’ensemble du package en le plaçant dans la définition implicite du codec dans l’object package.

Voici comment j’ai réussi à le faire avec java:

  FileInputStream input; Ssortingng result = null; try { input = new FileInputStream(new File("invalid.txt")); CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); decoder.onMalformedInput(CodingErrorAction.IGNORE); InputStreamReader reader = new InputStreamReader(input, decoder); BufferedReader bufferedReader = new BufferedReader( reader ); SsortingngBuilder sb = new SsortingngBuilder(); Ssortingng line = bufferedReader.readLine(); while( line != null ) { sb.append( line ); line = bufferedReader.readLine(); } bufferedReader.close(); result = sb.toSsortingng(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch( IOException e ) { e.printStackTrace(); } System.out.println(result); 

Le fichier non valide est créé avec des octets:

 0x68, 0x80, 0x65, 0x6C, 0x6C, 0xC3, 0xB6, 0xFE, 0x20, 0x77, 0xC3, 0xB6, 0x9C, 0x72, 0x6C, 0x64, 0x94 

Qui est en hellö wörld dans UTF-8 avec 4 octets invalides mélangés dans.

Avec .REPLACE vous voyez le caractère de remplacement unicode standard utilisé:

 //"h ellö  wö rld " 

Avec .IGNORE , vous voyez les octets non valides ignorés:

 //"hellö wörld" 

Sans spécifier .onMalformedInput , vous obtenez

 java.nio.charset.MalformedInputException: Input length = 1 at java.nio.charset.CoderResult.throwException(Unknown Source) at sun.nio.cs.StreamDecoder.implRead(Unknown Source) at sun.nio.cs.StreamDecoder.read(Unknown Source) at java.io.InputStreamReader.read(Unknown Source) at java.io.BufferedReader.fill(Unknown Source) at java.io.BufferedReader.readLine(Unknown Source) at java.io.BufferedReader.readLine(Unknown Source) 

La solution pour la source de scala (basée sur la réponse de @Esailija):

 def toSource(inputStream:InputStream): scala.io.BufferedSource = { import java.nio.charset.Charset import java.nio.charset.CodingErrorAction val decoder = Charset.forName("UTF-8").newDecoder() decoder.onMalformedInput(CodingErrorAction.IGNORE) scala.io.Source.fromInputStream(inputStream)(decoder) } 

Le codec de Scala a un champ de décodeur qui renvoie un java.nio.charset.CharsetDecoder :

 val decoder = Codec.UTF8.decoder.onMalformedInput(CodingErrorAction.IGNORE) Source.fromFile(filename)(decoder).getLines().toList 

Le problème avec l’ignorance des octets invalides est alors de décider quand ils seront à nouveau valides. Notez que UTF-8 autorise les codages d’octets de longueur variable pour les caractères, donc si un octet n’est pas valide, vous devez comprendre de quel octet commencer à lire pour obtenir à nouveau un stream de caractères valide.

En bref, je ne pense pas que vous trouverez une bibliothèque capable de «corriger» à la lecture. Je pense qu’une approche beaucoup plus productive consiste à essayer de nettoyer ces données en premier.

Je passe à un autre codec en cas d’échec.

Afin d’implémenter le pattern, je me suis inspiré de cette autre question de stackoverflow .

J’utilise une liste de codecs par défaut et je les passe récursivement. S’ils échouent tous, j’imprime les bits effrayants:

 private val defaultCodecs = List( io.Codec("UTF-8"), io.Codec("ISO-8859-1") ) def listLines(file: java.io.File, codecs:Iterable[io.Codec] = defaultCodecs): Iterable[Ssortingng] = { val codec = codecs.head val fileHandle = scala.io.Source.fromFile(file)(codec) try { val txtArray = fileHandle.getLines().toList txtArray } catch { case ex: Exception => { if (codecs.tail.isEmpty) { println("Exception: " + ex) println("Skipping file: " + file.getPath) List() } else { listLines(file, codecs.tail) } } } finally { fileHandle.close() } } 

J’apprends seulement Scala, donc le code peut ne pas être optimal.

Une solution simple serait d’interpréter votre stream de données en tant qu’ASCII, d’ignorer tous les caractères non textuels. Cependant, vous perdriez même des caractères UTF8 codés valides. Je ne sais pas si cela est acceptable pour vous.

EDIT: Si vous savez à l’avance quelles colonnes sont UTF-8 valides, vous pouvez écrire votre propre parsingur CSV pouvant être configuré, quelle stratégie utiliser sur quelle colonne.

Utilisez ISO-8859-1 comme encodeur; Cela vous donnera juste des valeurs d’octets empaquetées dans une chaîne. Ceci est suffisant pour parsingr CSV pour la plupart des encodages. (Si vous avez des blocs mixtes 8 bits et 16 bits, vous avez des problèmes, vous pouvez toujours lire les lignes dans ISO-8859-1, mais vous ne pourrez peut-être pas parsingr la ligne en bloc.)

Une fois que vous avez les champs individuels en tant que chaînes séparées, vous pouvez essayer

 new Ssortingng(oldssortingng.getBytes("ISO-8859-1"), "UTF-8") 

pour générer la chaîne avec le bon encodage (utilisez le nom de codage approprié par champ, si vous le connaissez).

Edit: vous devrez utiliser java.nio.charset.Charset.CharsetDecoder si vous souhaitez détecter des erreurs. Mapper à UTF-8 de cette façon vous donnera simplement 0xFFFF dans votre chaîne en cas d’erreur.

 val decoder = java.nio.charset.Charset.forName("UTF-8").newDecoder // By default will throw a MalformedInputException if encoding fails decoder.decode( java.nio.ByteBuffer.wrap(oldssortingng.getBytes("ISO-8859-1")) ).toSsortingng