Sí es una pregunta más frecuente, y este asunto me resulta vago y no sé mucho al respecto.
Pero me gustaría una forma muy precisa de encontrar una codificación de archivos. Tan preciso como es Notepad ++.
Sí es una pregunta más frecuente, y este asunto me resulta vago y no sé mucho al respecto.
Pero me gustaría una forma muy precisa de encontrar una codificación de archivos. Tan preciso como es Notepad ++.
Respuestas:
La StreamReader.CurrentEncoding
propiedad rara vez me devuelve la codificación correcta del archivo de texto. He tenido más éxito determinando el endianness de un archivo, analizando su marca de orden de bytes (BOM). Si el archivo no tiene una lista de materiales, esto no puede determinar la codificación del archivo.
* ACTUALIZADO 08/04/2020 para incluir detección UTF-32LE y devolver la codificación correcta para UTF-32BE
/// <summary>
/// Determines a text file's encoding by analyzing its byte order mark (BOM).
/// Defaults to ASCII when detection of the text file's endianness fails.
/// </summary>
/// <param name="filename">The text file to analyze.</param>
/// <returns>The detected encoding.</returns>
public static Encoding GetEncoding(string filename)
{
// Read the BOM
var bom = new byte[4];
using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
file.Read(bom, 0, 4);
}
// Analyze the BOM
if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0 && bom[3] == 0) return Encoding.UTF32; //UTF-32LE
if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return new UTF32Encoding(true, true); //UTF-32BE
// We actually have no idea what the encoding is if we reach this point, so
// you may wish to return null instead of defaulting to ASCII
return Encoding.ASCII;
}
StreamReader
que la implementación es lo que más gente va a querer. Hacen nuevas codificaciones en lugar de usar los Encoding.Unicode
objetos existentes , por lo que las verificaciones de igualdad fallarán (lo que rara vez sucederá de todos modos porque, por ejemplo, Encoding.UTF8
puede devolver diferentes objetos), pero (1) no usa el formato UTF-7 realmente extraño, (2) se establece de forma predeterminada en UTF-8 si no se encuentra una lista de materiales, y (3) se puede anular para utilizar una codificación predeterminada diferente.
00 00 FE FF
Encoding.UTF32
FF FE 00 00
El siguiente código funciona bien para mí, usando la StreamReader
clase:
using (var reader = new StreamReader(fileName, defaultEncodingIfNoBom, true))
{
reader.Peek(); // you need this!
var encoding = reader.CurrentEncoding;
}
El truco consiste en utilizar la Peek
llamada, de lo contrario .NET no ha hecho nada (y no ha leído el preámbulo, la lista de materiales). Por supuesto, si usa cualquier otra ReadXXX
llamada antes de verificar la codificación, también funciona.
Si el archivo no tiene BOM, defaultEncodingIfNoBom
se utilizará la codificación. También hay un StreamReader sin este método de sobrecarga (en este caso, la codificación predeterminada (ANSI) se utilizará como defaultEncodingIfNoBom), pero recomiendo definir lo que considera la codificación predeterminada en su contexto.
He probado esto con éxito con archivos con BOM para UTF8, UTF16 / Unicode (LE & BE) y UTF32 (LE & BE). No funciona para UTF7.
foreach($filename in $args) { $reader = [System.IO.StreamReader]::new($filename, [System.Text.Encoding]::default,$true); $peek = $reader.Peek(); $reader.currentencoding | select bodyname,encodingname; $reader.close() }
UTF-8 without BOM
Intentaría los siguientes pasos:
1) Compruebe si hay una marca de orden de bytes
2) Compruebe si el archivo es UTF8 válido
3) Utilice la página de códigos "ANSI" local (ANSI como lo define Microsoft)
El paso 2 funciona porque la mayoría de las secuencias que no son ASCII en páginas de códigos que no sean UTF8 no son UTF8 válidas.
Utf8Encoding
, puede pasar un parámetro adicional que determina si se debe lanzar una excepción o si prefiere la corrupción de datos silenciosa.
Mira esto.
Este es un puerto de Mozilla Universal Charset Detector y puede usarlo así ...
public static void Main(String[] args)
{
string filename = args[0];
using (FileStream fs = File.OpenRead(filename)) {
Ude.CharsetDetector cdet = new Ude.CharsetDetector();
cdet.Feed(fs);
cdet.DataEnd();
if (cdet.Charset != null) {
Console.WriteLine("Charset: {0}, confidence: {1}",
cdet.Charset, cdet.Confidence);
} else {
Console.WriteLine("Detection failed.");
}
}
}
The library is subject to the Mozilla Public License Version 1.1 (the "License"). Alternatively, it may be used under the terms of either the GNU General Public License Version 2 or later (the "GPL"), or the GNU Lesser General Public License Version 2.1 or later (the "LGPL").
Proporcionando los detalles de implementación para los pasos propuestos por @CodesInChaos:
1) Compruebe si hay una marca de orden de bytes
2) Compruebe si el archivo es UTF8 válido
3) Utilice la página de códigos "ANSI" local (ANSI como lo define Microsoft)
El paso 2 funciona porque la mayoría de las secuencias que no son ASCII en páginas de códigos que no sean UTF8 no son UTF8 válidas. https://stackoverflow.com/a/4522251/867248 explica la táctica con más detalles.
using System; using System.IO; using System.Text;
// Using encoding from BOM or UTF8 if no BOM found,
// check if the file is valid, by reading all lines
// If decoding fails, use the local "ANSI" codepage
public string DetectFileEncoding(Stream fileStream)
{
var Utf8EncodingVerifier = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());
using (var reader = new StreamReader(fileStream, Utf8EncodingVerifier,
detectEncodingFromByteOrderMarks: true, leaveOpen: true, bufferSize: 1024))
{
string detectedEncoding;
try
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
}
detectedEncoding = reader.CurrentEncoding.BodyName;
}
catch (Exception e)
{
// Failed to decode the file using the BOM/UT8.
// Assume it's local ANSI
detectedEncoding = "ISO-8859-1";
}
// Rewind the stream
fileStream.Seek(0, SeekOrigin.Begin);
return detectedEncoding;
}
}
[Test]
public void Test1()
{
Stream fs = File.OpenRead(@".\TestData\TextFile_ansi.csv");
var detectedEncoding = DetectFileEncoding(fs);
using (var reader = new StreamReader(fs, Encoding.GetEncoding(detectedEncoding)))
{
// Consume your file
var line = reader.ReadLine();
...
reader.Peek()
en lugar de while (!reader.EndOfStream) { var line = reader.ReadLine(); }
reader.Peek()
no lee toda la transmisión. Descubrí que con arroyos más grandes, Peek()
era inadecuado. Usé en su reader.ReadToEndAsync()
lugar.
var Utf8EncodingVerifier = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());
se usa en el try
bloque al leer una línea. Si el codificador no puede analizar el texto proporcionado (el texto no está codificado con utf8), Utf8EncodingVerifier arrojará. Se detecta la excepción y luego sabemos que el texto no es utf8, y por defecto es ISO-8859-1
Los siguientes códigos son mis códigos de Powershell para determinar si algunos archivos cpp oh o ml están codificados con ISO-8859-1 (Latin-1) o UTF-8 sin BOM, si ninguno de los dos, suponga que es GB18030. Soy un chino que trabaja en Francia y MSVC guarda como Latin-1 en una computadora francesa y guarda como GB en una computadora china, por lo que esto me ayuda a evitar problemas de codificación cuando realizo intercambios de archivos fuente entre mi sistema y mis colegas.
La forma es simple, si todos los caracteres están entre x00-x7E, ASCII, UTF-8 y Latin-1 son todos iguales, pero si leo un archivo no ASCII por UTF-8, encontraremos el carácter especial aparecerá , intente leer con Latin-1. En Latin-1, entre \ x7F y \ xAF está vacío, mientras que GB usa lleno entre x00-xFF, así que si tengo algo entre los dos, no es Latin-1
El código está escrito en PowerShell, pero usa .net, por lo que es fácil de traducir a C # o F #
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
foreach($i in Get-ChildItem .\ -Recurse -include *.cpp,*.h, *.ml) {
$openUTF = New-Object System.IO.StreamReader -ArgumentList ($i, [Text.Encoding]::UTF8)
$contentUTF = $openUTF.ReadToEnd()
[regex]$regex = '�'
$c=$regex.Matches($contentUTF).count
$openUTF.Close()
if ($c -ne 0) {
$openLatin1 = New-Object System.IO.StreamReader -ArgumentList ($i, [Text.Encoding]::GetEncoding('ISO-8859-1'))
$contentLatin1 = $openLatin1.ReadToEnd()
$openLatin1.Close()
[regex]$regex = '[\x7F-\xAF]'
$c=$regex.Matches($contentLatin1).count
if ($c -eq 0) {
[System.IO.File]::WriteAllLines($i, $contentLatin1, $Utf8NoBomEncoding)
$i.FullName
}
else {
$openGB = New-Object System.IO.StreamReader -ArgumentList ($i, [Text.Encoding]::GetEncoding('GB18030'))
$contentGB = $openGB.ReadToEnd()
$openGB.Close()
[System.IO.File]::WriteAllLines($i, $contentGB, $Utf8NoBomEncoding)
$i.FullName
}
}
}
Write-Host -NoNewLine 'Press any key to continue...';
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');
.NET no es muy útil, pero puede probar el siguiente algoritmo:
Aquí está la llamada:
var encoding = FileHelper.GetEncoding(filePath);
if (encoding == null)
throw new Exception("The file encoding is not supported. Please choose one of the following encodings: UTF8/UTF7/iso-8859-1");
Aquí está el código:
public class FileHelper
{
/// <summary>
/// Determines a text file's encoding by analyzing its byte order mark (BOM) and if not found try parsing into diferent encodings
/// Defaults to UTF8 when detection of the text file's endianness fails.
/// </summary>
/// <param name="filename">The text file to analyze.</param>
/// <returns>The detected encoding or null.</returns>
public static Encoding GetEncoding(string filename)
{
var encodingByBOM = GetEncodingByBOM(filename);
if (encodingByBOM != null)
return encodingByBOM;
// BOM not found :(, so try to parse characters into several encodings
var encodingByParsingUTF8 = GetEncodingByParsing(filename, Encoding.UTF8);
if (encodingByParsingUTF8 != null)
return encodingByParsingUTF8;
var encodingByParsingLatin1 = GetEncodingByParsing(filename, Encoding.GetEncoding("iso-8859-1"));
if (encodingByParsingLatin1 != null)
return encodingByParsingLatin1;
var encodingByParsingUTF7 = GetEncodingByParsing(filename, Encoding.UTF7);
if (encodingByParsingUTF7 != null)
return encodingByParsingUTF7;
return null; // no encoding found
}
/// <summary>
/// Determines a text file's encoding by analyzing its byte order mark (BOM)
/// </summary>
/// <param name="filename">The text file to analyze.</param>
/// <returns>The detected encoding.</returns>
private static Encoding GetEncodingByBOM(string filename)
{
// Read the BOM
var byteOrderMark = new byte[4];
using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
file.Read(byteOrderMark, 0, 4);
}
// Analyze the BOM
if (byteOrderMark[0] == 0x2b && byteOrderMark[1] == 0x2f && byteOrderMark[2] == 0x76) return Encoding.UTF7;
if (byteOrderMark[0] == 0xef && byteOrderMark[1] == 0xbb && byteOrderMark[2] == 0xbf) return Encoding.UTF8;
if (byteOrderMark[0] == 0xff && byteOrderMark[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
if (byteOrderMark[0] == 0xfe && byteOrderMark[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
if (byteOrderMark[0] == 0 && byteOrderMark[1] == 0 && byteOrderMark[2] == 0xfe && byteOrderMark[3] == 0xff) return Encoding.UTF32;
return null; // no BOM found
}
private static Encoding GetEncodingByParsing(string filename, Encoding encoding)
{
var encodingVerifier = Encoding.GetEncoding(encoding.BodyName, new EncoderExceptionFallback(), new DecoderExceptionFallback());
try
{
using (var textReader = new StreamReader(filename, encodingVerifier, detectEncodingFromByteOrderMarks: true))
{
while (!textReader.EndOfStream)
{
textReader.ReadLine(); // in order to increment the stream position
}
// all text parsed ok
return textReader.CurrentEncoding;
}
}
catch (Exception ex) { }
return null; //
}
}
Busque aquí c #
https://msdn.microsoft.com/en-us/library/system.io.streamreader.currentencoding%28v=vs.110%29.aspx
string path = @"path\to\your\file.ext";
using (StreamReader sr = new StreamReader(path, true))
{
while (sr.Peek() >= 0)
{
Console.Write((char)sr.Read());
}
//Test for the encoding after reading, or at least
//after the first read.
Console.WriteLine("The encoding used was {0}.", sr.CurrentEncoding);
Console.ReadLine();
Console.WriteLine();
}