Existe-t-il un moyen de déterminer le codage d'une chaîne en C #?
Dites, j'ai une chaîne de nom de fichier, mais je ne sais pas si elle est encodée en Unicode UTF-16 ou l'encodage par défaut du système, comment puis-je le savoir?
Existe-t-il un moyen de déterminer le codage d'une chaîne en C #?
Dites, j'ai une chaîne de nom de fichier, mais je ne sais pas si elle est encodée en Unicode UTF-16 ou l'encodage par défaut du système, comment puis-je le savoir?
Réponses:
Découvrez Utf8Checker, c'est une classe simple qui fait exactement cela en code managé pur. http://utf8checker.codeplex.com
Remarque: comme déjà indiqué, "déterminer l'encodage" n'a de sens que pour les flux d'octets. Si vous avez une chaîne, elle est déjà encodée par quelqu'un en cours de route qui connaissait déjà ou a deviné l'encodage pour obtenir la chaîne en premier lieu.
Le code ci-dessous présente les caractéristiques suivantes:
Comme d'autres l'ont dit, aucune solution ne peut être parfaite (et on ne peut certainement pas faire la différence entre les différents encodages ASCII étendus 8 bits utilisés dans le monde), mais nous pouvons être `` assez bons '' surtout si le développeur présente également à l'utilisateur une liste d'encodages alternatifs comme indiqué ici: Quel est l'encodage le plus courant de chaque langue?
Une liste complète des encodages peut être trouvée en utilisant Encoding.GetEncodings();
// Function to detect the encoding for UTF-7, UTF-8/16/32 (bom, no bom, little
// & big endian), and local default codepage, and potentially other codepages.
// 'taster' = number of bytes to check of the file (to save processing). Higher
// value is slower, but more reliable (especially UTF-8 with special characters
// later on may appear to be ASCII initially). If taster = 0, then taster
// becomes the length of the file (for maximum reliability). 'text' is simply
// the string with the discovered encoding applied to the file.
public Encoding detectTextEncoding(string filename, out String text, int taster = 1000)
{
byte[] b = File.ReadAllBytes(filename);
//////////////// First check the low hanging fruit by checking if a
//////////////// BOM/signature exists (sourced from http://www.unicode.org/faq/utf_bom.html#bom4)
if (b.Length >= 4 && b[0] == 0x00 && b[1] == 0x00 && b[2] == 0xFE && b[3] == 0xFF) { text = Encoding.GetEncoding("utf-32BE").GetString(b, 4, b.Length - 4); return Encoding.GetEncoding("utf-32BE"); } // UTF-32, big-endian
else if (b.Length >= 4 && b[0] == 0xFF && b[1] == 0xFE && b[2] == 0x00 && b[3] == 0x00) { text = Encoding.UTF32.GetString(b, 4, b.Length - 4); return Encoding.UTF32; } // UTF-32, little-endian
else if (b.Length >= 2 && b[0] == 0xFE && b[1] == 0xFF) { text = Encoding.BigEndianUnicode.GetString(b, 2, b.Length - 2); return Encoding.BigEndianUnicode; } // UTF-16, big-endian
else if (b.Length >= 2 && b[0] == 0xFF && b[1] == 0xFE) { text = Encoding.Unicode.GetString(b, 2, b.Length - 2); return Encoding.Unicode; } // UTF-16, little-endian
else if (b.Length >= 3 && b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF) { text = Encoding.UTF8.GetString(b, 3, b.Length - 3); return Encoding.UTF8; } // UTF-8
else if (b.Length >= 3 && b[0] == 0x2b && b[1] == 0x2f && b[2] == 0x76) { text = Encoding.UTF7.GetString(b,3,b.Length-3); return Encoding.UTF7; } // UTF-7
//////////// If the code reaches here, no BOM/signature was found, so now
//////////// we need to 'taste' the file to see if can manually discover
//////////// the encoding. A high taster value is desired for UTF-8
if (taster == 0 || taster > b.Length) taster = b.Length; // Taster size can't be bigger than the filesize obviously.
// Some text files are encoded in UTF8, but have no BOM/signature. Hence
// the below manually checks for a UTF8 pattern. This code is based off
// the top answer at: /programming/6555015/check-for-invalid-utf8
// For our purposes, an unnecessarily strict (and terser/slower)
// implementation is shown at: /programming/1031645/how-to-detect-utf-8-in-plain-c
// For the below, false positives should be exceedingly rare (and would
// be either slightly malformed UTF-8 (which would suit our purposes
// anyway) or 8-bit extended ASCII/UTF-16/32 at a vanishingly long shot).
int i = 0;
bool utf8 = false;
while (i < taster - 4)
{
if (b[i] <= 0x7F) { i += 1; continue; } // If all characters are below 0x80, then it is valid UTF8, but UTF8 is not 'required' (and therefore the text is more desirable to be treated as the default codepage of the computer). Hence, there's no "utf8 = true;" code unlike the next three checks.
if (b[i] >= 0xC2 && b[i] <= 0xDF && b[i + 1] >= 0x80 && b[i + 1] < 0xC0) { i += 2; utf8 = true; continue; }
if (b[i] >= 0xE0 && b[i] <= 0xF0 && b[i + 1] >= 0x80 && b[i + 1] < 0xC0 && b[i + 2] >= 0x80 && b[i + 2] < 0xC0) { i += 3; utf8 = true; continue; }
if (b[i] >= 0xF0 && b[i] <= 0xF4 && b[i + 1] >= 0x80 && b[i + 1] < 0xC0 && b[i + 2] >= 0x80 && b[i + 2] < 0xC0 && b[i + 3] >= 0x80 && b[i + 3] < 0xC0) { i += 4; utf8 = true; continue; }
utf8 = false; break;
}
if (utf8 == true) {
text = Encoding.UTF8.GetString(b);
return Encoding.UTF8;
}
// The next check is a heuristic attempt to detect UTF-16 without a BOM.
// We simply look for zeroes in odd or even byte places, and if a certain
// threshold is reached, the code is 'probably' UF-16.
double threshold = 0.1; // proportion of chars step 2 which must be zeroed to be diagnosed as utf-16. 0.1 = 10%
int count = 0;
for (int n = 0; n < taster; n += 2) if (b[n] == 0) count++;
if (((double)count) / taster > threshold) { text = Encoding.BigEndianUnicode.GetString(b); return Encoding.BigEndianUnicode; }
count = 0;
for (int n = 1; n < taster; n += 2) if (b[n] == 0) count++;
if (((double)count) / taster > threshold) { text = Encoding.Unicode.GetString(b); return Encoding.Unicode; } // (little-endian)
// Finally, a long shot - let's see if we can find "charset=xyz" or
// "encoding=xyz" to identify the encoding:
for (int n = 0; n < taster-9; n++)
{
if (
((b[n + 0] == 'c' || b[n + 0] == 'C') && (b[n + 1] == 'h' || b[n + 1] == 'H') && (b[n + 2] == 'a' || b[n + 2] == 'A') && (b[n + 3] == 'r' || b[n + 3] == 'R') && (b[n + 4] == 's' || b[n + 4] == 'S') && (b[n + 5] == 'e' || b[n + 5] == 'E') && (b[n + 6] == 't' || b[n + 6] == 'T') && (b[n + 7] == '=')) ||
((b[n + 0] == 'e' || b[n + 0] == 'E') && (b[n + 1] == 'n' || b[n + 1] == 'N') && (b[n + 2] == 'c' || b[n + 2] == 'C') && (b[n + 3] == 'o' || b[n + 3] == 'O') && (b[n + 4] == 'd' || b[n + 4] == 'D') && (b[n + 5] == 'i' || b[n + 5] == 'I') && (b[n + 6] == 'n' || b[n + 6] == 'N') && (b[n + 7] == 'g' || b[n + 7] == 'G') && (b[n + 8] == '='))
)
{
if (b[n + 0] == 'c' || b[n + 0] == 'C') n += 8; else n += 9;
if (b[n] == '"' || b[n] == '\'') n++;
int oldn = n;
while (n < taster && (b[n] == '_' || b[n] == '-' || (b[n] >= '0' && b[n] <= '9') || (b[n] >= 'a' && b[n] <= 'z') || (b[n] >= 'A' && b[n] <= 'Z')))
{ n++; }
byte[] nb = new byte[n-oldn];
Array.Copy(b, oldn, nb, 0, n-oldn);
try {
string internalEnc = Encoding.ASCII.GetString(nb);
text = Encoding.GetEncoding(internalEnc).GetString(b);
return Encoding.GetEncoding(internalEnc);
}
catch { break; } // If C# doesn't recognize the name of the encoding, break.
}
}
// If all else fails, the encoding is probably (though certainly not
// definitely) the user's local codepage! One might present to the user a
// list of alternative encodings as shown here: /programming/8509339/what-is-the-most-common-encoding-of-each-language
// A full list can be found using Encoding.GetEncodings();
text = Encoding.Default.GetString(b);
return Encoding.Default;
}
Cela dépend d'où la chaîne «vient». Une chaîne .NET est Unicode (UTF-16). La seule façon dont cela pourrait être différent si, par exemple, vous lisiez les données d'une base de données dans un tableau d'octets.
Cet article CodeProject peut être intéressant: Détecter l'encodage pour le texte entrant et sortant
Les chaînes de Jon Skeet en C # et .NET sont une excellente explication des chaînes .NET.
Je sais que c'est un peu tard - mais pour être clair:
Une chaîne n'a pas vraiment de codage ... dans .NET, une chaîne est une collection d'objets char. Essentiellement, s'il s'agit d'une chaîne, elle a déjà été décodée.
Cependant, si vous lisez le contenu d'un fichier, qui est composé d'octets, et que vous souhaitez le convertir en chaîne, alors le codage du fichier doit être utilisé.
.NET comprend des classes d'encodage et de décodage pour: ASCII, UTF7, UTF8, UTF32 et plus.
La plupart de ces codages contiennent certaines marques d'ordre des octets qui peuvent être utilisées pour distinguer le type de codage utilisé.
La classe .NET System.IO.StreamReader est capable de déterminer le codage utilisé dans un flux, en lisant ces marques d'ordre des octets;
Voici un exemple:
/// <summary>
/// return the detected encoding and the contents of the file.
/// </summary>
/// <param name="fileName"></param>
/// <param name="contents"></param>
/// <returns></returns>
public static Encoding DetectEncoding(String fileName, out String contents)
{
// open the file with the stream-reader:
using (StreamReader reader = new StreamReader(fileName, true))
{
// read the contents of the file into a string
contents = reader.ReadToEnd();
// return the encoding.
return reader.CurrentEncoding;
}
}
Encoding.Default
tant que paramètre StreamReader, mais le code ne détectera pas UTF8 sans la nomenclature.
Une autre option, très tardive, désolé:
http://www.architectshack.com/TextFileEncodingDetector.ashx
Cette petite classe uniquement C # utilise BOMS si elle est présente, essaie de détecter automatiquement d'éventuels codages Unicode dans le cas contraire, et revient si aucun des codages Unicode n'est possible ou probable.
Cela ressemble à UTF8Checker référencé ci-dessus fait quelque chose de similaire, mais je pense que sa portée est légèrement plus large - au lieu de juste UTF8, il vérifie également d'autres encodages Unicode possibles (UTF-16 LE ou BE) qui pourraient manquer une nomenclature.
J'espère que cela aide quelqu'un!
Le package Nuget SimpleHelpers.FileEncoding enveloppe un port C # du détecteur de jeu de caractères universel Mozilla dans une API très simple:
var encoding = FileEncoding.DetectFileEncoding(txtFile);
Ma solution consiste à utiliser des éléments intégrés avec quelques solutions de secours.
J'ai choisi la stratégie d'une réponse à une autre question similaire sur stackoverflow mais je ne la trouve pas maintenant.
Il vérifie d'abord la nomenclature en utilisant la logique intégrée de StreamReader, s'il y a une nomenclature, le codage sera autre chose que Encoding.Default
, et nous devons faire confiance à ce résultat.
Sinon, il vérifie si la séquence d'octets est une séquence UTF-8 valide. si c'est le cas, il devinera UTF-8 comme encodage, et sinon, encore une fois, l'encodage ASCII par défaut sera le résultat.
static Encoding getEncoding(string path) {
var stream = new FileStream(path, FileMode.Open);
var reader = new StreamReader(stream, Encoding.Default, true);
reader.Read();
if (reader.CurrentEncoding != Encoding.Default) {
reader.Close();
return reader.CurrentEncoding;
}
stream.Position = 0;
reader = new StreamReader(stream, new UTF8Encoding(false, true));
try {
reader.ReadToEnd();
reader.Close();
return Encoding.UTF8;
}
catch (Exception) {
reader.Close();
return Encoding.Default;
}
}
Remarque: il s'agissait d'une expérience pour voir comment l'encodage UTF-8 fonctionnait en interne. La solution proposée par vilicvane , utiliser un UTF8Encoding
objet initialisé pour lever une exception en cas d'échec de décodage, est beaucoup plus simple et fait essentiellement la même chose.
J'ai écrit ce morceau de code pour différencier UTF-8 et Windows-1252. Cependant, il ne devrait pas être utilisé pour des fichiers texte gigantesques, car il charge le tout en mémoire et le scanne complètement. Je l'ai utilisé pour les fichiers de sous-titres .srt, juste pour pouvoir les sauvegarder dans l'encodage dans lequel ils étaient chargés.
Le codage donné à la fonction en tant que ref doit être le codage de secours 8 bits à utiliser au cas où le fichier serait détecté comme n'étant pas valide UTF-8; généralement, sur les systèmes Windows, ce sera Windows-1252. Cela n'a rien d'extraordinaire comme vérifier les plages ascii valides réelles, et ne détecte pas UTF-16 même sur la marque d'ordre d'octet.
La théorie derrière la détection au niveau du bit peut être trouvée ici: https://ianthehenry.com/2015/1/17/decoding-utf-8/
Fondamentalement, la plage de bits du premier octet détermine combien après il fait partie de l'entité UTF-8. Ces octets après lui sont toujours dans la même plage de bits.
/// <summary>
/// Reads a text file, and detects whether its encoding is valid UTF-8 or ascii.
/// If not, decodes the text using the given fallback encoding.
/// Bit-wise mechanism for detecting valid UTF-8 based on
/// https://ianthehenry.com/2015/1/17/decoding-utf-8/
/// </summary>
/// <param name="docBytes">The bytes read from the file.</param>
/// <param name="encoding">The default encoding to use as fallback if the text is detected not to be pure ascii or UTF-8 compliant. This ref parameter is changed to the detected encoding.</param>
/// <returns>The contents of the read file, as String.</returns>
public static String ReadFileAndGetEncoding(Byte[] docBytes, ref Encoding encoding)
{
if (encoding == null)
encoding = Encoding.GetEncoding(1252);
Int32 len = docBytes.Length;
// byte order mark for utf-8. Easiest way of detecting encoding.
if (len > 3 && docBytes[0] == 0xEF && docBytes[1] == 0xBB && docBytes[2] == 0xBF)
{
encoding = new UTF8Encoding(true);
// Note that even when initialising an encoding to have
// a BOM, it does not cut it off the front of the input.
return encoding.GetString(docBytes, 3, len - 3);
}
Boolean isPureAscii = true;
Boolean isUtf8Valid = true;
for (Int32 i = 0; i < len; ++i)
{
Int32 skip = TestUtf8(docBytes, i);
if (skip == 0)
continue;
if (isPureAscii)
isPureAscii = false;
if (skip < 0)
{
isUtf8Valid = false;
// if invalid utf8 is detected, there's no sense in going on.
break;
}
i += skip;
}
if (isPureAscii)
encoding = new ASCIIEncoding(); // pure 7-bit ascii.
else if (isUtf8Valid)
encoding = new UTF8Encoding(false);
// else, retain given encoding. This should be an 8-bit encoding like Windows-1252.
return encoding.GetString(docBytes);
}
/// <summary>
/// Tests if the bytes following the given offset are UTF-8 valid, and
/// returns the amount of bytes to skip ahead to do the next read if it is.
/// If the text is not UTF-8 valid it returns -1.
/// </summary>
/// <param name="binFile">Byte array to test</param>
/// <param name="offset">Offset in the byte array to test.</param>
/// <returns>The amount of bytes to skip ahead for the next read, or -1 if the byte sequence wasn't valid UTF-8</returns>
public static Int32 TestUtf8(Byte[] binFile, Int32 offset)
{
// 7 bytes (so 6 added bytes) is the maximum the UTF-8 design could support,
// but in reality it only goes up to 3, meaning the full amount is 4.
const Int32 maxUtf8Length = 4;
Byte current = binFile[offset];
if ((current & 0x80) == 0)
return 0; // valid 7-bit ascii. Added length is 0 bytes.
Int32 len = binFile.Length;
for (Int32 addedlength = 1; addedlength < maxUtf8Length; ++addedlength)
{
Int32 fullmask = 0x80;
Int32 testmask = 0;
// This code adds shifted bits to get the desired full mask.
// If the full mask is [111]0 0000, then test mask will be [110]0 0000. Since this is
// effectively always the previous step in the iteration I just store it each time.
for (Int32 i = 0; i <= addedlength; ++i)
{
testmask = fullmask;
fullmask += (0x80 >> (i+1));
}
// figure out bit masks from level
if ((current & fullmask) == testmask)
{
if (offset + addedlength >= len)
return -1;
// Lookahead. Pattern of any following bytes is always 10xxxxxx
for (Int32 i = 1; i <= addedlength; ++i)
{
if ((binFile[offset + i] & 0xC0) != 0x80)
return -1;
}
return addedlength;
}
}
// Value is greater than the maximum allowed for utf8. Deemed invalid.
return -1;
}
else
déclaration après if ((current & 0xE0) == 0xC0) { ... } else if ((current & 0xF0) == 0xE0) { ... } else if ((current & 0xF0) == 0xE0) { ... } else if ((current & 0xF8) == 0xF0) { ... }
. Je suppose que le else
cas serait UTF8 invalide: isUtf8Valid = false;
. Voudriez-vous?
J'ai trouvé une nouvelle bibliothèque sur GitHub: CharsetDetector / UTF-unknown
Détecteur de jeu de caractères construit en C # - .NET Core 2-3, .NET standard 1-2 et .NET 4+
c'est aussi un port du Mozilla Universal Charset Detector basé sur d'autres référentiels.
CharsetDetector / UTF-unknown ont une classe nommée CharsetDetector
.
CharsetDetector
contient des méthodes de détection de codage statique:
CharsetDetector.DetectFromFile()
CharsetDetector.DetectFromStream()
CharsetDetector.DetectFromBytes()
le résultat détecté est dans la classe DetectionResult
a l'attribut Detected
qui est l'instance de la classe DetectionDetail
avec les attributs ci-dessous:
EncodingName
Encoding
Confidence
ci-dessous est un exemple pour montrer l'utilisation:
// Program.cs
using System;
using System.Text;
using UtfUnknown;
namespace ConsoleExample
{
public class Program
{
public static void Main(string[] args)
{
string filename = @"E:\new-file.txt";
DetectDemo(filename);
}
/// <summary>
/// Command line example: detect the encoding of the given file.
/// </summary>
/// <param name="filename">a filename</param>
public static void DetectDemo(string filename)
{
// Detect from File
DetectionResult result = CharsetDetector.DetectFromFile(filename);
// Get the best Detection
DetectionDetail resultDetected = result.Detected;
// detected result may be null.
if (resultDetected != null)
{
// Get the alias of the found encoding
string encodingName = resultDetected.EncodingName;
// Get the System.Text.Encoding of the found encoding (can be null if not available)
Encoding encoding = resultDetected.Encoding;
// Get the confidence of the found encoding (between 0 and 1)
float confidence = resultDetected.Confidence;
if (encoding != null)
{
Console.WriteLine($"Detection completed: {filename}");
Console.WriteLine($"EncodingWebName: {encoding.WebName}{Environment.NewLine}Confidence: {confidence}");
}
else
{
Console.WriteLine($"Detection completed: {filename}");
Console.WriteLine($"(Encoding is null){Environment.NewLine}EncodingName: {encodingName}{Environment.NewLine}Confidence: {confidence}");
}
}
else
{
Console.WriteLine($"Detection failed: {filename}");
}
}
}
}