web-dev-qa-db-de.com

Wie lese ich eine Textdatei mit gemischten Codierungen in Scala oder Java?

Ich versuche, eine CSV-Datei zu analysieren, idealerweise mit weka.core.converters.CSVLoader. Die Datei, die ich habe, ist jedoch keine gültige UTF-8-Datei. Es handelt sich meistens um eine UTF-8-Datei, aber einige der Feldwerte sind unterschiedlich codiert. Es gibt also keine Codierung, in der die gesamte Datei gültig ist, aber ich muss sie trotzdem analysieren. Abgesehen von der Verwendung von Java - Bibliotheken wie Weka arbeite ich hauptsächlich in Scala. Ich kann die Datei in scala.io nicht einmal lesen. Quelle: Zum Beispiel

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

wirft:

    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)

Ich bin vollkommen glücklich, alle ungültigen Zeichen wegzuwerfen oder sie durch eine Attrappe zu ersetzen. Ich werde eine Menge solcher Texte haben, um sie auf verschiedene Arten zu verarbeiten, und muss sie möglicherweise an verschiedene Bibliotheken von Drittanbietern weitergeben. Eine ideale Lösung wäre eine globale Einstellung, bei der alle Bibliotheken der unteren Ebene Java ungültige Bytes im Text ignorieren, sodass ich Bibliotheken von Drittanbietern für diese Daten ohne Änderung aufrufen kann.

LÖSUNG:

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)

Vielen Dank an + Esailija, die mich in die richtige Richtung gelenkt hat. Dies führte mich zu Wie erkenne ich ungültige UTF-8-Byte-Sequenzen, um sie im Java - Eingabestrom zu ersetzen? , der die Kernlösung Java bereitstellt. In Scala kann ich dies zum Standardverhalten machen, indem ich den Codec implizite. Ich denke, ich kann es zum Standardverhalten für das gesamte Paket machen, indem ich es in die implizite Codec-Definition des Paketobjekts setze.

49
Daniel Mahler

So habe ich es mit Java geschafft:

    FileInputStream input;
    String 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 );
        StringBuilder sb = new StringBuilder();
        String line = bufferedReader.readLine();
        while( line != null ) {
            sb.append( line );
            line = bufferedReader.readLine();
        }
        bufferedReader.close();
        result = sb.toString();

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch( IOException e ) {
        e.printStackTrace();
    }

    System.out.println(result);

Die ungültige Datei wird mit Bytes erstellt:

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

Welches ist hellö wörld in UTF-8 mit 4 ungültigen Bytes gemischt.

Mit .REPLACE Sie sehen das verwendete Standard-Unicode-Ersetzungszeichen:

//"h�ellö� wö�rld�"

Mit .IGNORE, Sie sehen die ungültigen Bytes ignoriert:

//"hellö wörld"

Ohne Angabe von .onMalformedInput, du erhältst

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)
24
Esailija

Die Lösung für Scala Source (basierend auf der @ Esailija-Antwort):

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)
}
14
raisercostin

Scala's Codec hat ein Decoderfeld, das ein Java.nio.charset.CharsetDecoder:

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

Ich wechsle zu einem anderen Codec, wenn einer ausfällt.

Um das Muster zu implementieren, habe ich mich von dieser anderen Stapelüberlauf-Frage inspirieren lassen.

Ich verwende eine Standard-Codec-Liste und gehe sie rekursiv durch. Wenn sie alle scheitern, drucke ich die gruseligen Teile aus:

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[String] = {
  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()
  }
}

Ich lerne gerade Scala, daher ist der Code möglicherweise nicht optimal.

2
Harry Pehkonen

Das Problem beim Ignorieren ungültiger Bytes ist dann die Entscheidung, wann sie wieder gültig sind. Beachten Sie, dass UTF-8 Byte-Kodierungen mit variabler Länge für Zeichen zulässt. Wenn ein Byte also ungültig ist, müssen Sie wissen, von welchem ​​Byte Sie lesen müssen, um wieder einen gültigen Zeichenstrom zu erhalten.

Kurz gesagt, ich glaube nicht, dass Sie eine Bibliothek finden, die beim Lesen "korrigieren" kann. Ich denke, ein viel produktiverer Ansatz ist es, zuerst zu versuchen, diese Daten zu bereinigen.

2
Brian Agnew

Eine einfache Lösung wäre, Ihren Datenstrom als ASCII zu interpretieren und alle Nicht-Text-Zeichen zu ignorieren. Sie würden jedoch auch gültige UTF8-Zeichen verlieren. Ich weiß nicht, ob das für dich akzeptabel ist.

BEARBEITEN: Wenn Sie im Voraus wissen, welche Spalten für UTF-8 gültig sind, können Sie einen eigenen CSV-Parser schreiben, der konfiguriert werden kann, welche Strategie für welche Spalte verwendet werden soll.

0
mbelow

Verwenden Sie ISO-8859-1 Als Encoder. Dadurch erhalten Sie nur die in eine Zeichenfolge gepackten Bytewerte. Dies reicht aus, um CSV für die meisten Codierungen zu analysieren. (Wenn Sie 8-Bit- und 16-Bit-Blöcke gemischt haben, treten Probleme auf. Sie können die Zeilen in ISO-8859-1 weiterhin lesen, können die Zeile jedoch möglicherweise nicht als Block analysieren.)

Sobald Sie die einzelnen Felder als separate Zeichenfolgen haben, können Sie es versuchen

new String(oldstring.getBytes("ISO-8859-1"), "UTF-8")

um die Zeichenfolge mit der richtigen Codierung zu generieren (verwenden Sie den entsprechenden Codierungsnamen pro Feld, falls Sie ihn kennen).

Bearbeiten: Sie müssen Java.nio.charset.Charset.CharsetDecoder Verwenden, um Fehler zu erkennen. Wenn Sie UTF-8 auf diese Weise zuordnen, erhalten Sie nur 0xFFFF in Ihrer Zeichenfolge, wenn ein Fehler auftritt.

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(oldstring.getBytes("ISO-8859-1")) ).toString
0
Rex Kerr