Подтвердить что ты не робот

Как читать текстовый файл со смешанными кодировками в Scala или Java?

Я пытаюсь разобрать CSV файл, в идеале используя weka.core.converters.CSVLoader. Однако файл, который у меня есть, не является допустимым файлом UTF-8. Это в основном файл UTF-8, но некоторые из значений поля находятся в разных кодировках, поэтому нет кодировки, в которой весь файл действителен, но мне все равно нужно разбирать его. Помимо использования java-библиотек, таких как Weka, я в основном работаю в Scala. Я даже не могу прочитать файл usin Scala.io.Source: Например

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

броски:

    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)

Я абсолютно счастлив выбросить всех недопустимых персонажей или заменить их каким-то манекеном. У меня будет много текста, подобного этому, для обработки различными способами и может потребоваться передать данные в различные сторонние библиотеки. Идеальное решение - это своего рода глобальная заставляют все низкоуровневые java-библиотеки игнорировать недопустимые байты в тексте, так что я могу вызывать сторонние библиотеки по этим данным без изменений.

РЕШЕНИЕ:

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)

Спасибо + Esailija за то, что указали мне в правильном направлении. Это привело меня к Как обнаружить незаконные последовательности байтов UTF-8 для их замены в java inputstream? который предоставляет основное Java-решение. В Scala я могу сделать это по умолчанию, сделав кодек неявным. Я думаю, что могу сделать это по умолчанию для всего пакета, поставив его в неявное определение кодека в объекте пакета.

4b9b3361

Ответ 1

Вот как мне удалось это сделать с помощью java:

    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);

Недействительный файл создается с байтами:

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

Что такое hellö wörld в UTF-8 с 4 недопустимыми байтами.

С .REPLACE вы видите используемый стандартный символ замены юникода:

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

С .IGNORE вы видите игнорируемые недопустимые байты:

//"hellö wörld"

Без указания .onMalformedInput вы получите

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)

Ответ 2

Решение для scala Source (на основе ответа @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)
}

Ответ 3

Scala Codec имеет поле декодера, которое возвращает java.nio.charset.CharsetDecoder:

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

Ответ 4

Проблема с игнорированием неверных байтов затем решает, когда они снова действительны. Обратите внимание, что UTF-8 позволяет кодировать байты переменной длины для символов, поэтому, если байт недействителен, вам нужно понять, какой байт начать читать, чтобы снова получить действительный поток символов.

Короче говоря, я не думаю, что вы найдете библиотеку, которая может "исправить", как она читает. Я думаю, что гораздо более продуктивный подход - сначала попытаться очистить эти данные.

Ответ 5

Я перехожу к другому кодеку, если не удается.

Чтобы реализовать шаблон, я получил вдохновение из этого другого вопроса о стеке_потока.

Я использую список кодеков по умолчанию и рекурсивно просматриваю их. Если все они терпят неудачу, я печатаю страшные биты:

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()
  }
}

Я просто изучаю Scala, поэтому код может быть не оптимальным.

Ответ 6

Простым решением будет интерпретировать ваш поток данных как ASCII, игнорировать все нетекстовые символы. Однако вы потеряете даже допустимые кодированные символы UTF8. Не знаю, приемлемо ли это для вас.

EDIT: Если вы заранее знаете, какие столбцы являются допустимыми UTF-8, вы можете написать свой собственный синтаксический анализатор CSV, который можно настроить, какую стратегию использовать в каком столбце.

Ответ 7

Использовать ISO-8859-1 как кодировщик; это просто даст вам байтовые значения, упакованные в строку. Этого достаточно для анализа CSV для большинства кодировок. (Если у вас смешанные 8-битные и 16-битные блоки, то у вас проблемы, вы все равно можете читать строки в ISO-8859-1, но вы не сможете анализировать строку как блок.)

Когда у вас есть отдельные поля в виде отдельных строк, вы можете попробовать

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

чтобы сгенерировать строку с надлежащим кодированием (используйте соответствующее имя кодировки для каждого поля, если вы это знаете).

Изменить: вам нужно будет использовать java.nio.charset.Charset.CharsetDecoder, если вы хотите обнаружить ошибки. Сопоставление с UTF-8 таким образом просто даст вам 0xFFFF в вашей строке при возникновении ошибки.

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