C #: ¿clase para decodificar la encoding entre comillas imprimibles?

¿Hay una clase existente en C # que pueda convertir la encoding Quoted-Printable en String ? Haga clic en el enlace de arriba para obtener más información sobre la encoding.

A continuación, se cita el enlace de arriba para su conveniencia.

Cualquier valor de byte de 8 bits puede codificarse con 3 caracteres, un “=” seguido de dos dígitos hexadecimales (0-9 o A-F) que representan el valor numérico del byte. Por ejemplo, un carácter de alimentación de formulario US-ASCII (valor decimal 12) se puede representar por “= 0C”, y un signo igual de US-ASCII (valor decimal 61) se representa por “= 3D”. Todos los caracteres excepto los caracteres ASCII imprimibles o los caracteres de fin de línea deben codificarse de esta manera.

Todos los caracteres ASCII imprimibles (valores decimales entre 33 y 126) pueden representarse por sí mismos, excepto “=” (decimal 61).

Los caracteres ASCII de tabulación y espacio, valores decimales 9 y 32, pueden representarse por sí mismos, excepto si estos caracteres aparecen al final de una línea. Si uno de estos caracteres aparece al final de una línea, debe codificarse como “= 09” (pestaña) o “= 20” (espacio).

Si los datos que se codifican contienen saltos de línea significativos, se deben codificar como una secuencia ASCII CR LF, no como sus valores de bytes originales. Por el contrario, si los valores de byte 13 y 10 tienen significados distintos del final de línea, entonces deben codificarse como = 0D y = 0A.

Las líneas de datos codificados imprimibles entre comillas no deben tener más de 76 caracteres. Para satisfacer este requisito sin alterar el texto codificado, se pueden agregar saltos de línea suaves como se desee. Un salto de línea suave consiste en un “=” al final de una línea codificada, y no causa un salto de línea en el texto decodificado.

Hay una funcionalidad en las bibliotecas de marcos para hacer esto, pero no parece estar claramente expuesta. La implementación está en la clase interna System.Net.Mime.QuotedPrintableStream . Esta clase define un método llamado DecodeBytes que hace lo que quiere. El método parece ser utilizado solo por un método que se usa para decodificar encabezados MIME. Este método también es interno, pero se llama bastante directamente en un par de lugares, por ejemplo, el Attachment.Name setter. Una demostración:

 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); } } } 

Produce el resultado:

¡Hola señor!

Es posible que tenga que hacer algunas pruebas para asegurarse de que los retornos de transporte, etc. sean tratados correctamente, aunque en una prueba rápida sí lo hice. Sin embargo, puede que no sea prudente confiar en esta funcionalidad, a menos que su caso de uso esté lo suficientemente cerca de la deencoding de una cadena de encabezado MIME que no cree que se romperá por ningún cambio realizado en la biblioteca. Tal vez sea mejor que escriba su propio decodificador imprimible entre comillas.

Extendí la solución de Martin Murphy y espero que funcione en todos los casos.

 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; } 

Lo escribí muy rápido.

  public static string DecodeQuotedPrintables(string input) { var occurences = new Regex(@"=[0-9A-H]{2}", RegexOptions.Multiline); var matches = occurences.Matches(input); foreach (Match match in matches) { char hexChar= (char) Convert.ToInt32(match.Groups[0].Value.Substring(1), 16); input =input.Replace(match.Groups[0].Value, hexChar.ToString()); } return input.Replace("=\r\n", ""); } 

Si está decodificando entre comillas e imprimible con encoding UTF-8, deberá tener en cuenta que no puede decodificar cada secuencia entrecomillada e imprimible una a la vez como las demás han mostrado si hay corridas de caracteres imprimibles entrecomillados.

Por ejemplo, si tiene la siguiente secuencia = E2 = 80 = 99 y decodifica esto usando UTF8 uno a la vez, obtiene tres caracteres “extraños” – si en su lugar crea una matriz de tres bytes y convierte los tres bytes con la encoding UTF8 obtienes un solo apóstropo.

Obviamente, si está utilizando la encoding ASCII, entonces uno a la vez no es un problema; sin embargo, la deencoding se ejecuta significa que su código funcionará independientemente del codificador de texto utilizado.

Ah, y no lo olvides = 3D es un caso especial que significa que necesitas decodificar lo que tengas una vez más … ¡Eso es una ganga loca!

Espero que ayude

¡Este decodificador imprimible entre comillas funciona de maravilla!

 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; } 

El único que funcionó para mí.

http://sourceforge.net/apps/trac/syncmldotnet/wiki/Quoted%20Printable

Si solo necesita descodificar los QP, inserte dentro de su código esas tres funciones desde el enlace de arriba:

  HexDecoderEvaluator(Match m) HexDecoder(string line) Decode(string encodedText) 

Y luego solo:

 var humanReadable = Decode(myQPString); 

Disfrutar

Estaba buscando una solución dinámica y pasé 2 días probando diferentes soluciones. Esta solución admitirá caracteres japoneses y otros juegos de caracteres estándar

 private static string Decode(string input, string bodycharset) { var i = 0; var output = new List(); 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()); } } 

Entonces puedes llamar a la función con

 Decode("=E3=82=AB=E3=82=B9=E3", "utf-8") 

Esto fue encontrado originalmente aquí

Mejor solución

  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(@"\=(?[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); } 

A veces, la cadena en un archivo EML está compuesta por varias partes codificadas. Esta es una función para usar el método de Dave para estos casos:

 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; } 

Tenga en cuenta: las soluciones con “input.replace” están en todo Internet y aún así no son correctas.

Vea, si tiene UN símbolo decodificado y luego usa “reemplazar” , TODOS los símbolos en “entrada” serán reemplazados, y luego toda la siguiente desencoding se romperá.

Solución más correcta:

 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; }