Определить кодировку TextFile?

Мне нужно определить, соответствует ли содержимое текстового файла одному из этих текстовых кодировок:

System.Text.Encoding.ASCII
System.Text.Encoding.BigEndianUnicode ' UTF-L 16
System.Text.Encoding.Default ' ANSI
System.Text.Encoding.Unicode ' UTF16
System.Text.Encoding.UTF32
System.Text.Encoding.UTF7
System.Text.Encoding.UTF8

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

Ответ 1

Первый шаг - загрузить файл в виде байтового массива вместо строки. Строки всегда хранятся в памяти с кодировкой UTF-16, поэтому после ее загрузки в строку исходная кодировка теряется. Вот простой пример одного способа загрузки файла в массив байтов:

Dim data() As Byte = File.ReadAllBytes("test.txt")

Автоматическое определение правильной кодировки для заданного массива байтов, как известно, сложно. Иногда, чтобы быть полезным, автор данных вставляет данные, которые называются BOM (Byte Order Mark) в начале данных. Если присутствует спецификация, это делает отсутствие кодирования безболезненным, поскольку каждая кодировка использует другую спецификацию.

Самый простой способ автоматического определения кодировки из спецификации - позволить StreamReader сделать это за вас. В конструкторе StreamReader вы можете передать True для аргумента detectEncodingFromByteOrderMarks. Затем вы можете получить кодировку потока, обратившись к свойству CurrentEncoding. Однако свойство CurrentEncoding не будет работать до тех пор, пока StreamReader не прочитает спецификацию. Поэтому вам сначала нужно прочитать профайл перед тем, как получить кодировку, например:

Public Function GetFileEncoding(filePath As String) As Encoding
    Using sr As New StreamReader(filePath, True)
        sr.Read()
        Return sr.CurrentEncoding
    End Using
End Function

Однако проблема такого подхода заключается в том, что MSDN, по-видимому, подразумевает, что StreamReader может обнаруживать определенные виды кодировки:

Параметр detectEncodingFromByteOrderMarks определяет кодировку, просматривая первые три байта потока. Он автоматически распознает UTF-8, малоконечный Unicode и текстовый текст в формате Unicode, если файл начинается с соответствующих меток байтового байта. Дополнительную информацию см. В методе Encoding.GetPreamble.

Кроме того, если StreamReader не может определить кодировку из спецификации или если спецификация не существует, она будет просто по умолчанию кодировать UTF-8, не указывая на то, что она не удалась. Если вам требуется более подробный контроль, вы можете легко прочитать спецификацию и интерпретировать ее самостоятельно. Все, что вам нужно сделать, это сравнить первые несколько байтов в массиве байтов с известной, ожидаемой спецификацией, чтобы увидеть, соответствуют ли они. Вот список некоторых общих спецификаций:

  • UTF-8: EF BB BF
  • UTF-16 большой порядковый байтовый порядок: FE FF
  • UTF-16 маленький порядковый порядок байтов: FF FE
  • UTF-32 большой порядковый байтовый порядок: 00 00 FE FF
  • UTF-32 маленький порядковый порядок байтов: FF FE 00 00

Так, например, чтобы увидеть, существует ли в начале массива байтов спецификация UTF-16 (little endian), вы можете просто сделать что-то вроде этого:

If (data(0) = &HFF) And (data(1) = &HFE) Then
    ' Data starts with UTF-16 (little endian) BOM
End If

Удобно, класс Encoding в .NET содержит метод с именем GetPreamble, который возвращает спецификацию, используемую кодировкой, поэтому вам даже не нужно помнить, что это все. Итак, чтобы проверить, начинается ли байт-массив с спецификации для Unicode (UTF-16, little-endian), вы можете просто сделать это:

Function IsUtf16LittleEndian(data() as Byte) As Boolean
    Dim bom() As Byte = Encoding.Unicode.GetPreamble()
    If (data(0) = bom(0)) And (data(1) = bom(1) Then
        Return True
    Else
        Return False
    End If
End Function

Конечно, вышеприведенная функция предполагает, что данные имеют длину не менее двух байтов, а спецификация - это точно два байта. Таким образом, хотя он иллюстрирует, как сделать это как можно яснее, это не самый безопасный способ сделать это. Чтобы сделать его терпимым к разным размерам массива, тем более, что сами длины BOM могут варьироваться от одного кодирования к другому, было бы безопаснее сделать что-то вроде этого:

Function IsUtf16LittleEndian(data() as Byte) As Boolean
    Dim bom() As Byte = Encoding.Unicode.GetPreamble()
    Return data.Zip(bom, Function(x, y) x = y).All(Function(x) x)
End Function

Итак, тогда возникает проблема, как вы получаете список всех кодировок? Хорошо, так получилось, что класс .NET Encoding также предоставляет общий (статический) метод под названием GetEncodings, который возвращает список всех поддерживаемых объектов кодирования. Таким образом, вы можете создать метод, который проходит через все объекты кодирования, получает спецификацию каждого из них и сравнивает его с массивом байтов, пока не найдете тот, который соответствует. Например:

Public Function DetectEncodingFromBom(data() As Byte) As Encoding
    Return Encoding.GetEncodings().
        Select(Function(info) info.GetEncoding()).
        FirstOrDefault(Function(enc) DataStartsWithBom(data, enc))
End Function

Private Function DataStartsWithBom(data() As Byte, enc As Encoding) As Boolean
    Dim bom() As Byte = enc.GetPreamble()
    If bom.Length <> 0 Then
        Return data.
            Zip(bom, Function(x, y) x = y).
            All(Function(x) x)
    Else
        Return False
    End If
End Function

Как только вы создадите такую ​​функцию, вы можете обнаружить кодировку файла следующим образом:

Dim data() As Byte = File.ReadAllBytes("test.txt")
Dim detectedEncoding As Encoding = DetectEncodingFromBom(data)
If detectedEncoding Is Nothing Then
    Console.WriteLine("Unable to detect encoding")
Else
    Console.WriteLine(detectedEncoding.EncodingName)
End If

Однако проблема остается, как вы автоматически обнаруживаете правильную кодировку, когда нет спецификации? Технически он рекомендовал, чтобы вы не размещали спецификацию в начале своих данных при использовании UTF-8, и нет спецификации, определенной для любой из кодовых страниц ANSI. Так что, конечно, не исключено, что текстовый файл может не иметь спецификации. Если все файлы, с которыми вы имеете дело, находятся на английском языке, вероятно, можно с уверенностью предположить, что если никакой спецификации нет, то UTF-8 будет достаточным. Однако, если какой-либо из файлов использует что-то другое, без спецификации, это не сработает.

Как вы правильно заметили, есть приложения, которые все еще автоматически обнаруживают кодировку, даже если никакой спецификации нет, но они делают это через эвристику (то есть образованное угадывание), а иногда они не точны. В основном они загружают данные с использованием каждой кодировки, а затем видят, что данные "выглядят" понятными. Эта страница предлагает интересные сведения о проблемах внутри алгоритма автоматического обнаружения Notepad. Эта страница показывает, как вы можете использовать алгоритм автоматического обнаружения на основе COM, который использует Internet Explorer (в С#). Ниже приведен список некоторых библиотек С#, которые люди написали, которые пытаются автоматически определить кодировку массива байтов, что может оказаться полезным:

Даже если этот вопрос был для С#, вы также можете найти ответы на него полезными.