Saltar al contenido

¿Cómo detectar la codificación de caracteres de un archivo de texto?

Después de de nuestra extensa búsqueda de datos hemos podido solucionar esta interrogante que presentan ciertos de nuestros usuarios. Te regalamos la respuesta y esperamos que te resulte de mucha apoyo.

Solución:

No puede depender de que el archivo tenga una lista de materiales. UTF-8 no lo requiere. Y las codificaciones que no son Unicode ni siquiera tienen una lista de materiales. Sin embargo, existen otras formas de detectar la codificación.

UTF-32

La lista de materiales es 00 00 FE FF (para BE) o FF FE 00 00 (para LE).

Pero UTF-32 es fácil de detectar incluso sin una lista de materiales. Esto se debe a que el rango de puntos de código Unicode está restringido a U + 10FFFF y, por lo tanto, las unidades UTF-32 siempre tienen el patrón 00 00-10 xx xx (para BE) o xx xx 00-10 00 (para LE) . Si los datos tienen una longitud que es múltiplo de 4 y sigue uno de estos patrones, puede asumir con seguridad que es UTF-32. Los falsos positivos son casi imposibles debido a la rareza de 00 bytes en codificaciones orientadas a bytes.

US-ASCII

Sin lista de materiales, pero no la necesita. ASCII se puede identificar fácilmente por la falta de bytes en el rango 80-FF.

UTF-8

BOM es EF BB BF. Pero no puedes confiar en esto. Muchos archivos UTF-8 no tienen una lista de materiales, especialmente si se originaron en sistemas que no son Windows.

Pero puede asumir con seguridad que si un archivo se valida como UTF-8, es UTF-8. Los falsos positivos son raros.

Específicamente, dado que los datos no son ASCII, la tasa de falsos positivos para una secuencia de 2 bytes es solo del 3,9% (1920/49152). Para una secuencia de 7 bytes, es menos del 1%. Para una secuencia de 12 bytes, es menos del 0,1%. Para una secuencia de 24 bytes, es menos de 1 en un millón.

UTF-16

BOM es FE FF (para BE) o FF FE (para LE). Tenga en cuenta que la lista de materiales de UTF-16LE se encuentra al comienzo de la lista de materiales de UTF-32LE, por lo que debe verificar primero UTF-32.

Si tiene un archivo que consta principalmente de caracteres ISO-8859-1, tener la mitad de los bytes del archivo sea 00 también sería un fuerte indicador de UTF-16.

De lo contrario, la única forma confiable de reconocer UTF-16 sin una lista de materiales es buscar pares sustitutos (D[8-B]xx D[C-F]xx), pero los caracteres que no son BMP se utilizan con poca frecuencia para que este enfoque sea práctico.

XML

Si su archivo comienza con los bytes 3C 3F 78 6D 6C (es decir, los caracteres ASCII ” encoding= declaración. Si está presente, utilice esa codificación. Si está ausente, asuma UTF-8, que es la codificación XML predeterminada.

Si necesita admitir EBCDIC, busque también la secuencia equivalente 4C 6F A7 94 93.

En general, si tiene un formato de archivo que contiene una declaración de codificación, busque esa declaración en lugar de intentar adivinar la codificación.

Ninguna de las anteriores

Hay cientos de otras codificaciones que requieren más esfuerzo para detectarlas. Recomiendo probar el detector de juegos de caracteres de Mozilla o un puerto .NET del mismo.

Un incumplimiento razonable

Si ha descartado las codificaciones UTF y no tiene una declaración de codificación o una detección estadística que apunte a una codificación diferente, asuma ISO-8859-1 o el Windows-1252 estrechamente relacionado. (Tenga en cuenta que el último estándar HTML requiere una declaración “ISO-8859-1” para ser interpretada como Windows-1252.) Al ser la página de códigos predeterminada de Windows para inglés (y otros idiomas populares como español, portugués, alemán y francés), es la codificación más común que no sea UTF-8.

Si desea buscar una solución “simple”, puede encontrar útil esta clase que reuní:

http://www.architectshack.com/TextFileEncodingDetector.ashx

Primero realiza la detección de BOM automáticamente y luego intenta diferenciar entre las codificaciones Unicode sin BOM y alguna otra codificación predeterminada (generalmente Windows-1252, etiquetada incorrectamente como Encoding.ASCII en .Net).

Como se señaló anteriormente, una solución “más pesada” que involucre NCharDet o MLang puede ser más apropiada y, como señalo en la página de descripción general de esta clase, lo mejor es proporcionar alguna forma de interactividad con el usuario si es posible, porque simplemente ¡No es posible una tasa de detección del 100%!

Fragmento en caso de que el sitio esté desconectado:

using System;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;

namespace KlerksSoft

    public static class TextFileEncodingDetector
    
        /*
         * Simple class to handle text file encoding woes (in a primarily English-speaking tech 
         *      world).
         * 
         *  - This code is fully managed, no shady calls to MLang (the unmanaged codepage
         *      detection library originally developed for Internet Explorer).
         * 
         *  - This class does NOT try to detect arbitrary codepages/charsets, it really only
         *      aims to differentiate between some of the most common variants of Unicode 
         *      encoding, and a "default" (western / ascii-based) encoding alternative provided
         *      by the caller.
         *      
         *  - As there is no "Reliable" way to distinguish between UTF-8 (without BOM) and 
         *      Windows-1252 (in .Net, also incorrectly called "ASCII") encodings, we use a 
         *      heuristic - so the more of the file we can sample the better the guess. If you 
         *      are going to read the whole file into memory at some point, then best to pass 
         *      in the whole byte byte array directly. Otherwise, decide how to trade off 
         *      reliability against performance / memory usage.
         *      
         *  - The UTF-8 detection heuristic only works for western text, as it relies on 
         *      the presence of UTF-8 encoded accented and other characters found in the upper 
         *      ranges of the Latin-1 and (particularly) Windows-1252 codepages.
         *  
         *  - For more general detection routines, see existing projects / resources:
         *    - MLang - Microsoft library originally for IE6, available in Windows XP and later APIs now (I think?)
         *      - MLang .Net bindings: http://www.codeproject.com/KB/recipes/DetectEncoding.aspx
         *    - CharDet - Mozilla browser's detection routines
         *      - Ported to Java then .Net: http://www.conceptdevelopment.net/Localization/NCharDet/
         *      - Ported straight to .Net: http://code.google.com/p/chardetsharp/source/browse
         *  
         * Copyright Tao Klerks, 2010-2012, [email protected]
         * Licensed under the modified BSD license:
         * 
Redistribution and use in source and binary forms, with or without modification, are 
permitted provided that the following conditions are met:
 - Redistributions of source code must retain the above copyright notice, this list of 
conditions and the following disclaimer.
 - Redistributions in binary form must reproduce the above copyright notice, this list 
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
 - The name of the author may not be used to endorse or promote products derived from 
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
OF SUCH DAMAGE.
         * 
         * CHANGELOG:
         *  - 2012-02-03: 
         *    - Simpler methods, removing the silly "DefaultEncoding" parameter (with "??" operator, saves no typing)
         *    - More complete methods
         *      - Optionally return indication of whether BOM was found in "Detect" methods
         *      - Provide straight-to-string method for byte arrays (GetStringFromByteArray)
         */

        const long _defaultHeuristicSampleSize = 0x10000; //completely arbitrary - inappropriate for high numbers of files / high speed requirements

        public static Encoding DetectTextFileEncoding(string InputFilename)
        
            using (FileStream textfileStream = File.OpenRead(InputFilename))
            
                return DetectTextFileEncoding(textfileStream, _defaultHeuristicSampleSize);
            
        

        public static Encoding DetectTextFileEncoding(FileStream InputFileStream, long HeuristicSampleSize)
        
            bool uselessBool = false;
            return DetectTextFileEncoding(InputFileStream, _defaultHeuristicSampleSize, out uselessBool);
        

        public static Encoding DetectTextFileEncoding(FileStream InputFileStream, long HeuristicSampleSize, out bool HasBOM)
        
            if (InputFileStream == null)
                throw new ArgumentNullException("Must provide a valid Filestream!", "InputFileStream");

            if (!InputFileStream.CanRead)
                throw new ArgumentException("Provided file stream is not readable!", "InputFileStream");

            if (!InputFileStream.CanSeek)
                throw new ArgumentException("Provided file stream cannot seek!", "InputFileStream");

            Encoding encodingFound = null;

            long originalPos = InputFileStream.Position;

            InputFileStream.Position = 0;


            //First read only what we need for BOM detection
            byte[] bomBytes = new byte[InputFileStream.Length > 4 ? 4 : InputFileStream.Length];
            InputFileStream.Read(bomBytes, 0, bomBytes.Length);

            encodingFound = DetectBOMBytes(bomBytes);

            if (encodingFound != null)
            
                InputFileStream.Position = originalPos;
                HasBOM = true;
                return encodingFound;
            


            //BOM Detection failed, going for heuristics now.
            //  create sample byte array and populate it
            byte[] sampleBytes = new byte[HeuristicSampleSize > InputFileStream.Length ? InputFileStream.Length : HeuristicSampleSize];
            Array.Copy(bomBytes, sampleBytes, bomBytes.Length);
            if (InputFileStream.Length > bomBytes.Length)
                InputFileStream.Read(sampleBytes, bomBytes.Length, sampleBytes.Length - bomBytes.Length);
            InputFileStream.Position = originalPos;

            //test byte array content
            encodingFound = DetectUnicodeInByteSampleByHeuristics(sampleBytes);

            HasBOM = false;
            return encodingFound;
        

        public static Encoding DetectTextByteArrayEncoding(byte[] TextData)
        
            bool uselessBool = false;
            return DetectTextByteArrayEncoding(TextData, out uselessBool);
        

        public static Encoding DetectTextByteArrayEncoding(byte[] TextData, out bool HasBOM)
        
            if (TextData == null)
                throw new ArgumentNullException("Must provide a valid text data byte array!", "TextData");

            Encoding encodingFound = null;

            encodingFound = DetectBOMBytes(TextData);

            if (encodingFound != null)
            
                HasBOM = true;
                return encodingFound;
            
            else
            
                //test byte array content
                encodingFound = DetectUnicodeInByteSampleByHeuristics(TextData);

                HasBOM = false;
                return encodingFound;
            
        

        public static string GetStringFromByteArray(byte[] TextData, Encoding DefaultEncoding)
        
            return GetStringFromByteArray(TextData, DefaultEncoding, _defaultHeuristicSampleSize);
        

        public static string GetStringFromByteArray(byte[] TextData, Encoding DefaultEncoding, long MaxHeuristicSampleSize)
        
            if (TextData == null)
                throw new ArgumentNullException("Must provide a valid text data byte array!", "TextData");

            Encoding encodingFound = null;

            encodingFound = DetectBOMBytes(TextData);

            if (encodingFound != null)
            
                //For some reason, the default encodings don't detect/swallow their own preambles!!
                return encodingFound.GetString(TextData, encodingFound.GetPreamble().Length, TextData.Length - encodingFound.GetPreamble().Length);
            
            else
            
                byte[] heuristicSample = null;
                if (TextData.Length > MaxHeuristicSampleSize)
                
                    heuristicSample = new byte[MaxHeuristicSampleSize];
                    Array.Copy(TextData, heuristicSample, MaxHeuristicSampleSize);
                
                else
                
                    heuristicSample = TextData;
                

                encodingFound = DetectUnicodeInByteSampleByHeuristics(TextData) ?? DefaultEncoding;
                return encodingFound.GetString(TextData);
            
        


        public static Encoding DetectBOMBytes(byte[] BOMBytes)
        
            if (BOMBytes == null)
                throw new ArgumentNullException("Must provide a valid BOM byte array!", "BOMBytes");

            if (BOMBytes.Length < 2)
                return null;

            if (BOMBytes[0] == 0xff 
                && BOMBytes[1] == 0xfe 
                && (BOMBytes.Length < 4 
                    

        public static Encoding DetectUnicodeInByteSampleByHeuristics(byte[] SampleBytes)
        [xF1-xF3][x80-xBF]3"
                + @"

        private static bool IsCommonUSASCIIByte(byte testByte)
        

        private static int DetectSuspiciousUTF8SequenceLength(byte[] SampleBytes, long currentPos)
        
            int lengthFound = 0;

            if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xC2
                )
            
            else if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xC3
                )
            
                if (SampleBytes[currentPos + 1] >= 0x80 
                    && SampleBytes[currentPos + 1] <= 0xBF
                    )
                    lengthFound = 2;
            
            else if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xC5
                )
             SampleBytes[currentPos + 1] == 0xBE
                    )
                    lengthFound = 2;
            
            else if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xC6
                )
            
                if (SampleBytes[currentPos + 1] == 0x92)
                    lengthFound = 2;
            
            else if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xCB
                )
             SampleBytes[currentPos + 1] == 0x9C
                    )
                    lengthFound = 2;
            
            else if (SampleBytes.Length >= currentPos + 2 
                && SampleBytes[currentPos] == 0xE2
                )
            
                if (SampleBytes[currentPos + 1] == 0x80)
                
                else if (SampleBytes[currentPos + 1] == 0x82 
                    && SampleBytes[currentPos + 2] == 0xAC
                    )
                    lengthFound = 3;
                else if (SampleBytes[currentPos + 1] == 0x84 
                    && SampleBytes[currentPos + 2] == 0xA2
                    )
                    lengthFound = 3;
            

            return lengthFound;
        

    

Usar StreamReader y dirígelo para que detecte la codificación por ti:

using (var reader = new System.IO.StreamReader(path, true))

    var currentEncoding = reader.CurrentEncoding;

Y use Identificadores de página de códigos https://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx para cambiar la lógica en función de ella.

Eres capaz de añadir valor a nuestro contenido informacional contribuyendo tu veteranía en las observaciones.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *