C #: Classe de décodage-encodage imprimable Quoted?
-
19-09-2019 - |
Question
Y at-il une classe existante en C # qui peut convertir Quoted-Printable encodage String
? Cliquez sur le lien ci-dessus pour obtenir plus d'informations sur l'encodage.
Ce qui suit est cité à partir du lien ci-dessus pour votre commodité.
Toute valeur d'octet de 8 bits peut être codé avec 3 caractères, un « = » suivi par deux chiffres hexadécimaux (0-9 ou A-F) représentant la valeur numérique de l'octet. Par exemple, une forme US-ASCII alimentation caractère (valeur décimale 12) peut être représenté par "= 0C", et US-ASCII signe égal (valeur décimale 61) est représenté par "= 3D". tous les caractères sauf caractères ASCII imprimables ou caractères de fin de ligne doit être codé de cette façon.
Tous les caractères ASCII imprimables (valeurs décimales entre 33 et 126) peut être représenté par eux-mêmes, à l'exception "=" (code décimal 61).
caractères de tabulation ASCII et de l'espace, les valeurs décimales 9 et 32, peuvent être représenté par eux-mêmes, sauf si ces caractères apparaissent à la fin de une ligne. Si l'un de ces caractères apparaît à la fin d'une ligne, il faut coder en tant que "= 09" (tabulation) ou "= 20" (Espace).
Si les données codées contient significatives sauts de ligne, ils doivent être codées comme une séquence ASCII CR LF, non pas comme leurs valeurs d'octets d'origine. Inversement, si l'octet valeurs 13 et 10 ont des significations autres que l'extrémité de la ligne alors ils doivent être codés en = 0D et = 0A.
Lignes de données codées imprimable citée ne doit pas dépasser 76 caractères. Pour satisfaire à cette exigence sans modifier le texte codé, ligne souple pauses peuvent être ajoutés comme on le souhaite. Une douce saut de ligne se compose d'une « = » au fin d'une ligne codée, et ne pas provoquer un saut de ligne dans le décodée texte.
La solution
Il existe des fonctionnalités dans les bibliothèques cadres pour ce faire, mais il ne semble pas être proprement exposé. La mise en œuvre est dans le System.Net.Mime.QuotedPrintableStream
de classe interne. Cette classe définit une méthode appelée DecodeBytes
qui fait ce que vous voulez. La méthode semble être utilisée par une seule méthode qui est utilisée pour décoder les en-têtes MIME. Cette méthode est également interne, mais est appelé assez directement dans deux ou trois endroits, par exemple, le poseur de Attachment.Name
. Une démonstration:
using System;
using System.Net.Mail;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Attachment attachment = Attachment.CreateAttachmentFromString("", "=?iso-8859-1?Q?=A1Hola,_se=F1or!?=");
Console.WriteLine(attachment.Name);
}
}
}
Produit la sortie:
¡Hola, _señor!
Vous pourriez avoir à faire des tests pour assurer des retours chariot, etc sont traités correctement, bien que dans un test rapide, je l'ai fait, ils semblent être. Cependant, il ne peut pas être sage de compter sur cette fonctionnalité à moins que votre cas d'utilisation est assez proche de décodage d'une chaîne d'en-tête MIME que vous ne pensez pas qu'il sera brisé par toutes les modifications apportées à la bibliothèque. Vous pourriez être mieux écrire votre propre décodeur cité imprimable.
Autres conseils
Je tendis la solution de Martin Murphy et j'espère que cela fonctionnera dans tous les cas.
private static string DecodeQuotedPrintables(string input, string charSet)
{
if (string.IsNullOrEmpty(charSet))
{
var charSetOccurences = new Regex(@"=\?.*\?Q\?", RegexOptions.IgnoreCase);
var charSetMatches = charSetOccurences.Matches(input);
foreach (Match match in charSetMatches)
{
charSet = match.Groups[0].Value.Replace("=?", "").Replace("?Q?", "");
input = input.Replace(match.Groups[0].Value, "").Replace("?=", "");
}
}
Encoding enc = new ASCIIEncoding();
if (!string.IsNullOrEmpty(charSet))
{
try
{
enc = Encoding.GetEncoding(charSet);
}
catch
{
enc = new ASCIIEncoding();
}
}
//decode iso-8859-[0-9]
var occurences = new Regex(@"=[0-9A-Z]{2}", RegexOptions.Multiline);
var matches = occurences.Matches(input);
foreach (Match match in matches)
{
try
{
byte[] b = new byte[] { byte.Parse(match.Groups[0].Value.Substring(1), System.Globalization.NumberStyles.AllowHexSpecifier) };
char[] hexChar = enc.GetChars(b);
input = input.Replace(match.Groups[0].Value, hexChar[0].ToString());
}
catch { }
}
//decode base64String (utf-8?B?)
occurences = new Regex(@"\?utf-8\?B\?.*\?", RegexOptions.IgnoreCase);
matches = occurences.Matches(input);
foreach (Match match in matches)
{
byte[] b = Convert.FromBase64String(match.Groups[0].Value.Replace("?utf-8?B?", "").Replace("?UTF-8?B?", "").Replace("?", ""));
string temp = Encoding.UTF8.GetString(b);
input = input.Replace(match.Groups[0].Value, temp);
}
input = input.Replace("=\r\n", "");
return input;
}
J'ai écrit ce jusqu'à très rapide.
public static string DecodeQuotedPrintables(string input)
{
var occurences = new Regex(@"=[0-9A-H]{2}", RegexOptions.Multiline);
var matches = occurences.Matches(input);
var uniqueMatches = new HashSet<string>(matches);
foreach (string match in uniqueMatches)
{
char hexChar= (char) Convert.ToInt32(match.Substring(1), 16);
input =input.Replace(match, hexChar.ToString());
}
return input.Replace("=\r\n", "");
}
Si vous décodage cité imprimable avec UTF-8 d'encodage que vous devez être conscient que vous ne pouvez pas décoder chaque séquence cité imprimable une par un temps que les autres ont montré s'il y a des séries de caractères imprimables cités ensemble.
Par exemple - si vous avez la séquence suivante = E2 = 80 = 99 et décoder en utilisant UTF8 un à-un-temps, vous obtenez trois caractères « bizarres » - si vous construisez place un tableau de trois octets et convertir la trois octets avec l'UTF8 vous obtenez un aphostrope.
Il est évident que si vous utilisez le codage ASCII puis un à-un-temps est pas un problème de décodage mais fonctionne signifie que votre code ne fonctionnera indépendamment du codeur de texte utilisé.
Oh, et ne pas oublier = 3D est un cas particulier qui signifie que vous devez décoder ce que vous avez une fois de plus ... C'est un Gotcha fou!
L'espoir qui aide
Ce décodeur imprimable Quoted fonctionne très bien!
public static byte[] FromHex(byte[] hexData)
{
if (hexData == null)
{
throw new ArgumentNullException("hexData");
}
if (hexData.Length < 2 || (hexData.Length / (double)2 != Math.Floor(hexData.Length / (double)2)))
{
throw new Exception("Illegal hex data, hex data must be in two bytes pairs, for example: 0F,FF,A3,... .");
}
MemoryStream retVal = new MemoryStream(hexData.Length / 2);
// Loop hex value pairs
for (int i = 0; i < hexData.Length; i += 2)
{
byte[] hexPairInDecimal = new byte[2];
// We need to convert hex char to decimal number, for example F = 15
for (int h = 0; h < 2; h++)
{
if (((char)hexData[i + h]) == '0')
{
hexPairInDecimal[h] = 0;
}
else if (((char)hexData[i + h]) == '1')
{
hexPairInDecimal[h] = 1;
}
else if (((char)hexData[i + h]) == '2')
{
hexPairInDecimal[h] = 2;
}
else if (((char)hexData[i + h]) == '3')
{
hexPairInDecimal[h] = 3;
}
else if (((char)hexData[i + h]) == '4')
{
hexPairInDecimal[h] = 4;
}
else if (((char)hexData[i + h]) == '5')
{
hexPairInDecimal[h] = 5;
}
else if (((char)hexData[i + h]) == '6')
{
hexPairInDecimal[h] = 6;
}
else if (((char)hexData[i + h]) == '7')
{
hexPairInDecimal[h] = 7;
}
else if (((char)hexData[i + h]) == '8')
{
hexPairInDecimal[h] = 8;
}
else if (((char)hexData[i + h]) == '9')
{
hexPairInDecimal[h] = 9;
}
else if (((char)hexData[i + h]) == 'A' || ((char)hexData[i + h]) == 'a')
{
hexPairInDecimal[h] = 10;
}
else if (((char)hexData[i + h]) == 'B' || ((char)hexData[i + h]) == 'b')
{
hexPairInDecimal[h] = 11;
}
else if (((char)hexData[i + h]) == 'C' || ((char)hexData[i + h]) == 'c')
{
hexPairInDecimal[h] = 12;
}
else if (((char)hexData[i + h]) == 'D' || ((char)hexData[i + h]) == 'd')
{
hexPairInDecimal[h] = 13;
}
else if (((char)hexData[i + h]) == 'E' || ((char)hexData[i + h]) == 'e')
{
hexPairInDecimal[h] = 14;
}
else if (((char)hexData[i + h]) == 'F' || ((char)hexData[i + h]) == 'f')
{
hexPairInDecimal[h] = 15;
}
}
// Join hex 4 bit(left hex cahr) + 4bit(right hex char) in bytes 8 it
retVal.WriteByte((byte)((hexPairInDecimal[0] << 4) | hexPairInDecimal[1]));
}
return retVal.ToArray();
}
public static byte[] QuotedPrintableDecode(byte[] data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
MemoryStream msRetVal = new MemoryStream();
MemoryStream msSourceStream = new MemoryStream(data);
int b = msSourceStream.ReadByte();
while (b > -1)
{
// Encoded 8-bit byte(=XX) or soft line break(=CRLF)
if (b == '=')
{
byte[] buffer = new byte[2];
int nCount = msSourceStream.Read(buffer, 0, 2);
if (nCount == 2)
{
// Soft line break, line splitted, just skip CRLF
if (buffer[0] == '\r' && buffer[1] == '\n')
{
}
// This must be encoded 8-bit byte
else
{
try
{
msRetVal.Write(FromHex(buffer), 0, 1);
}
catch
{
// Illegal value after =, just leave it as is
msRetVal.WriteByte((byte)'=');
msRetVal.Write(buffer, 0, 2);
}
}
}
// Illegal =, just leave as it is
else
{
msRetVal.Write(buffer, 0, nCount);
}
}
// Just write back all other bytes
else
{
msRetVal.WriteByte((byte)b);
}
// Read next byte
b = msSourceStream.ReadByte();
}
return msRetVal.ToArray();
}
private string quotedprintable(string data, string encoding)
{
data = data.Replace("=\r\n", "");
for (int position = -1; (position = data.IndexOf("=", position + 1)) != -1;)
{
string leftpart = data.Substring(0, position);
System.Collections.ArrayList hex = new System.Collections.ArrayList();
hex.Add(data.Substring(1 + position, 2));
while (position + 3 < data.Length && data.Substring(position + 3, 1) == "=")
{
position = position + 3;
hex.Add(data.Substring(1 + position, 2));
}
byte[] bytes = new byte[hex.Count];
for (int i = 0; i < hex.Count; i++)
{
bytes[i] = System.Convert.ToByte(new string(((string)hex[i]).ToCharArray()), 16);
}
string equivalent = System.Text.Encoding.GetEncoding(encoding).GetString(bytes);
string rightpart = data.Substring(position + 3);
data = leftpart + equivalent + rightpart;
}
return data;
}
Je cherchais une solution dynamique et passé 2 jours à essayer différentes solutions. Cette solution soutiendra les caractères japonais et d'autres jeux de caractères standard
private static string Decode(string input, string bodycharset) {
var i = 0;
var output = new List<byte>();
while (i < input.Length) {
if (input[i] == '=' && input[i + 1] == '\r' && input[i + 2] == '\n') {
//Skip
i += 3;
} else if (input[i] == '=') {
string sHex = input;
sHex = sHex.Substring(i + 1, 2);
int hex = Convert.ToInt32(sHex, 16);
byte b = Convert.ToByte(hex);
output.Add(b);
i += 3;
} else {
output.Add((byte)input[i]);
i++;
}
}
if (String.IsNullOrEmpty(bodycharset))
return Encoding.UTF8.GetString(output.ToArray());
else {
if (String.Compare(bodycharset, "ISO-2022-JP", true) == 0)
return Encoding.GetEncoding("Shift_JIS").GetString(output.ToArray());
else
return Encoding.GetEncoding(bodycharset).GetString(output.ToArray());
}
}
Ensuite, vous pouvez appeler la fonction avec
Decode("=E3=82=AB=E3=82=B9=E3", "utf-8")
a été trouvé ici
Le seul qui a fonctionné pour moi.
http://sourceforge.net/apps/trac/syncmldotnet/wiki/ % 20Printable Quoted
Si vous avez juste besoin de décoder le QP, tirer à l'intérieur de votre code ces trois fonctions à partir du lien ci-dessus:
HexDecoderEvaluator(Match m)
HexDecoder(string line)
Decode(string encodedText)
Et puis juste:
var humanReadable = Decode(myQPString);
Profitez
Une meilleure solution
private static string DecodeQuotedPrintables(string input, string charSet)
{
try
{
enc = Encoding.GetEncoding(CharSet);
}
catch
{
enc = new UTF8Encoding();
}
var occurences = new Regex(@"(=[0-9A-Z]{2}){1,}", RegexOptions.Multiline);
var matches = occurences.Matches(input);
foreach (Match match in matches)
{
try
{
byte[] b = new byte[match.Groups[0].Value.Length / 3];
for (int i = 0; i < match.Groups[0].Value.Length / 3; i++)
{
b[i] = byte.Parse(match.Groups[0].Value.Substring(i * 3 + 1, 2), System.Globalization.NumberStyles.AllowHexSpecifier);
}
char[] hexChar = enc.GetChars(b);
input = input.Replace(match.Groups[0].Value, hexChar[0].ToString());
}
catch
{ ;}
}
input = input.Replace("=\r\n", "").Replace("=\n", "").Replace("?=", "");
return input;
}
public static string DecodeQuotedPrintables(string input, Encoding encoding)
{
var regex = new Regex(@"\=(?<Symbol>[0-9A-Z]{2})", RegexOptions.Multiline);
var matches = regex.Matches(input);
var bytes = new byte[matches.Count];
for (var i = 0; i < matches.Count; i++)
{
bytes[i] = Convert.ToByte(matches[i].Groups["Symbol"].Value, 16);
}
return encoding.GetString(bytes);
}
Parfois, la chaîne de caractères dans un fichier EML est constitué par plusieurs parties codées. Cette fonction est d'utiliser la méthode de Dave pour ces cas:
public string DecodeQP(string codedstring)
{
Regex codified;
codified=new Regex(@"=\?((?!\?=).)*\?=", RegexOptions.IgnoreCase);
MatchCollection setMatches = codified.Matches(cadena);
if(setMatches.Count > 0)
{
Attachment attdecode;
codedstring= "";
foreach (Match match in setMatches)
{
attdecode = Attachment.CreateAttachmentFromString("", match.Value);
codedstring+= attdecode.Name;
}
}
return codedstring;
}
S'il vous plaît noter: solutions avec « input.Replace » sont sur Internet et encore ils ne sont pas correctes.
Voir, si vous avez un symbole décodé puis utilisez "remplacer" , ALL symboles "entrée" seront remplacés, et tout le décodage suivant sera brisé.
Solution plus correcte:
public static string DecodeQuotedPrintable(string input, string charSet)
{
Encoding enc;
try
{
enc = Encoding.GetEncoding(charSet);
}
catch
{
enc = new UTF8Encoding();
}
input = input.Replace("=\r\n=", "=");
input = input.Replace("=\r\n ", "\r\n ");
input = input.Replace("= \r\n", " \r\n");
var occurences = new Regex(@"(=[0-9A-Z]{2})", RegexOptions.Multiline); //{1,}
var matches = occurences.Matches(input);
foreach (Match match in matches)
{
try
{
byte[] b = new byte[match.Groups[0].Value.Length / 3];
for (int i = 0; i < match.Groups[0].Value.Length / 3; i++)
{
b[i] = byte.Parse(match.Groups[0].Value.Substring(i * 3 + 1, 2), System.Globalization.NumberStyles.AllowHexSpecifier);
}
char[] hexChar = enc.GetChars(b);
input = input.Replace(match.Groups[0].Value, new String(hexChar));
}
catch
{ Console.WriteLine("QP dec err"); }
}
input = input.Replace("?=", ""); //.Replace("\r\n", "");
return input;
}