Inicio > .NET Framework, Apunte, C# > Codificación de texto en .NET

Codificación de texto en .NET


Dado que últimamente, con eso de los flujos de datos y las expresiones regulares, he hablado mucho de texto, creo que valdría la pena hacer una entrada sobre la forma en la que podemos codificar nuestras cadenas de texto. Comencemos.

Cualquier tipo de dato en cualquier lenguaje no es más que un conjunto de bytes que se representan de una u otra forma. Así, cuando hablamos de caracteres hacemos referencia a un número natural que representa cada caracter.

La codificación hace referencia a qué número corresponde a qué caracter. Obviamente el conjunto de caracteres disponibles implica el tamaño en bytes. Por ejemplo, siete bits nos permite asignar hasta 128 caracteres, mientras que un byte nos permite asignar 256. Entre más caracteres queramos representar, más bytes ocuparemos por caracter.

Una de las primeras codificaciones que existieron es la famosa ASCII (American Standard Code for Information Interchange). ASCII asigna un caracter  a bytes de siete bits desde el 0 al 127. Estos caracteres incluyen el alfabeto inglés, números, signos de puntuación (inglesa;  no esperes encontrar caracteres como “¿”) y alguno que otro caracter especial.

Sin embargo, ASCII es una codificación que no sirve si quieres escribir texto en otro idioma que no sea el inglés. Para subsanar este problema, se creó la codificación ASCII extendida, que utiliza bytes de ocho bits, con posibles valores que varían del 0 al 256. De estos 256 caracteres, los primeros 128 (es decir, del 0 al 127) quedaron exactamente igual que el ASCII original, y los 128 restantes se utilizaron para representar caracteres de otros idiomas.

Para ello, se creó el término de código de página. Éste define los caracteres 128 al 255 que se utilizan, y por supuesto, existen diferentes según la región del planeta en la que uno se encuentre. Por ejemplo, el código de página 28591 Western European (ISO) contiene los caracteres (del 128 al 255) más comunes utilizados en los alfabetos europeos, que se basan en el alfabeto romano. En contraparte, el código de página 28597 Greek (ISO) contiene caracteres del alfabeto griego. Y así sucesivamente: existen códigos de página para el chino, el japonés, el árabe, el hebreo, el alfabeto cirílico y otros.

El problema con los códigos de página es que aunque tenemos más caracteres disponibles todavía no se abarcan todos. Así, un texto creado en China (osea, con código de página chino) es muy probable que no se vea en sistemas operativos que solo tengan instalado el Western European. Más aún, para algunos lenguajes como el chino o el árabe, los 128 caracteres que provee ASCII extendido son insuficientes.

En aras de solventar este problema fue que se creó otro tipo de codificación, llamado Unicode. Unicode es, en esencia, un código de página masivo que contiene decenas de miles de caracteres que soportan la mayoría de los lenguajes y alfabetos: latino, griego, cirílico, hebreo, árabe, chino y japonés.

Para ello, Unicode define varios tipos de codificación, llamados UTF (Unicode Transformation Format):

  • UTF-32. Esta codificación representa caracteres como números enteros de 32 bits, lo cual nos da un rango de hasta 4,294,967,296 posibles caracteres. Evidentemente, UTF-32 es el más amplio y abarca prácticamente cualquier caracter y signo utilizado por la humanidad. El problema es que ocupa cuatro bytes por caracter, y por ende cuatro veces más memoria que ASCII.
  • UTF-16. Aunque no abarca tantos caracteres como UTF-32, ésta codificación soporta hasta 65536 caracteres, suficientes para abarcar la mayoría de los caracteres y símbolos de los lenguajes más utilizados.
  • UTF-8. Es la codificación más sencilla, y equivale al ASCII extendido de 256 caracteres.
  • Para trabajar con estas codificaciones, .NET pone a nuestra disposición las clases equivalentes a los UTFs mencionados más la codificación ASCII. Por defecto, el .NET Framework utiliza UTF-16 (aunque a veces utiliza UTF-8 de forma interna), de tal suerte que en la mayoría de las ocasiones no es necesario especificar el tipo de codificación a emplear para una cadena de texto. Aún así, si quieres trabajar con diferentes codificaciones, puedes utilizar las clases contenidas en el espacio de nombres System.Text de la siguiente forma.

  • Para UTF-32, utiliza la clase UTF32Encoding.
  • Para UTF-16, utiliza la clase UnicodeEncoding.
  • Para UTF-8, utiliza la clase UTF8Encoding.
  • Para ASCII, utiliza la clase ASCIIEncoding.

Todas estas clases derivan de la clase base Encoding. Ésta, por cierto, contiene algunas propiedades que nos devuelven un objeto que representa las codificaciones mostradas en la lista anterior. Así, Encoding.UTF32, Encoding.UTF8, encoding.UTF7, Encoding.Unicode y Encoding.ASCII se usan para UTF32, UTF8, UTF7, UTF-16 y ASCII, respectivamente. Raramente necesitarás algo más que éstas.

Aún así, uno puede utilizar Encoding.GetEncoding si necesitas codificaciones extra, o cierto código de página específico. Por ejemplo, Encoding.GetEncoding(“Korean”) regresa una codificación que incluye código de página para caracteres coreanos. En fin, a menos que tengas una razón muy poderosa, no deberías necesitar más que los objetos que devuelven las propiedades estáticas de la clase Encoding.

Ahora bien, ¿para qué querría utilizar la codificación de texto? En principio el .NET Framework se encarga de la mayoría de problemas de texto de forma interna. De hecho la clase String utiliza UTF-16 como codificación por defecto. Hay, sin embargo, razones para las cuales necesitamos utilizar la codificación.

La primera es cuando realizamos una conversión de texto a bytes y viceversa. En efecto, cuando queremos obtener los bytes de una cadena necesitamos saber la codificación de ésta, ya que el tamaño en bytes de cada caracter varía (puede ser uno, dos o cuatro bytes por caracter, por ejemplo). De forma similar, al convertir bytes a texto se necesita la codificación, para saber cuántos bytes ocupa cada caracter. El siguiente ejemplo muestra cómo convertir texto a bytes y viceversa, utilizando UTF-16, y vemos lo que pasa al convertir a UTF-8.

using System;
using System.Text;

namespace Fermasmas.Wordpress.Com
{
  class Program
  {
    static void Main(string[] args)
    {
      string original = "Meine Schwester geht auf die Straße Spandauer";
      Console.WriteLine("Texto original: {0}", original);

      int count = Encoding.Unicode.GetByteCount(original);
      Console.WriteLine("El texto mide {0} bytes en UTF-16", count);
      count = Encoding.UTF8.GetByteCount(original);
      Console.WriteLine("El texto mide {0} bytes en UTF-8", count);

      byte[] buffer = Encoding.Unicode.GetBytes(original);
      string utf16 = Encoding.Unicode.GetString(buffer, 0, buffer.Length);
      string utf8  = Encoding.UTF8.GetString(buffer, 0, buffer.Length);

      Console.WriteLine("Decodificado en UTF-16: {0}", utf16);
      Console.WriteLine("Decodificado en UTF-8: {0}", utf8);

      Console.ReadKey(true);
    }
  }
}

Al ejecutarse, este código muestra lo siguiente en la consola:

Texto original: Meine Schwester geht auf die Straße Spandauer
El texto mide 90 bytes en UTF-16
El texto mide 46 bytes en UTF-8
Decodificado en UTF-16: Meine Schwester geht auf die Straße Spandauer
Decodificado en UTF-8: M e i n e   S c h w e s t e r   g e h t   a u f
d i e   S t r a ? e   S p a n d a u e r

Notarás, en primera instancia, que el tamaño en bytes de UTF-16 duplica al de UTF-8, lo cual era de esperarse. Nota también cómo al escribir el texto decodificado en UTF-8 tenemos la palabra Stra?e en lugar de Straße, lo cual también es obvio porque UTF-8 no reconoce ese valor. Y finalmente, notarás que la decodificación UTF-8 pone espacios en blanco entre cada letra. Esto es así porque el texto original utiliza dos bytes por cada caracter. Sin embargo, al decodificar en UTF-8, asume un caracter por un byte, por lo que un caracter UTF-16 lo interpreta como dos caracteres UTF-8.

Aparte de la conversión entre texto y bytes, otro uso importante es al momento de leer y escribir en archivos. Cuando escribimos binario, no nos importa mucho, pero cuando queremos escribir puro texto, la codificación es importante. Por defecto, la codificación utilizada es UTF-8 (ya que es la codificación estándar de Windows). Si en lugar de utilizar FileInfo utilizamos StreamReader y StreamWriter directamente, sus constructores nos permiten especificar el tipo de codificación, aunque StreamReader por defecto checa el texto y determina la codificación del archivo.

En el siguiente ejemplo escribimos a un archivo utilizando UTF-16 y lo leemos utilizando UTF-8.

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

namespace Fermasmas.Wordpress.Com
{
  class Program
  {
    static void Main(string[] args)
    {
      string original = "Meine Schwester geht auf die Straße Spandauer";
      string file = @"C:\users\fgomez\utf16test.txt";

      StreamWriter writer = new StreamWriter(file, false, Encoding.Unicode);
      writer.Write(original);
      writer.Close();

      StreamReader reader = new StreamReader(file, Encoding.UTF8, false);
      string text = reader.ReadToEnd();
      reader.Close();

      Console.WriteLine(original);
      Console.WriteLine(text);

      Console.ReadKey(true);
    }
  }
}

Al ejecutar este programa obtenemos el siguiente resultado.

Meine Schwester geht auf die Straße Spandauer
??M e i n e   S c h w e s t e r   g e h t   a u f   d i e   S t r a ? e
S p a n d a u e r

Al escribir en UTF-16, el archivo de texto coloca un par de bytes (llamados “preámbulo”) que indica la codificación que se utilizó (y por ello StreamReader puede determinar que se usó UTF-16), y por ello se muestra ?? cuando lo leemos con UTF-8, ya que esta codificación no supo cómo interpretar dichos bytes.

Bueno, ya para finalizar este apunte, y para poder irme a dormir (porque para variar ya casi son las tres de la mañana), me gustaría mencionar algunos miembros importantes de la clase Encoding. Ya vimos en los ejemplos que Encoding.GetByteCount nos regresa el número de bytes dada una cadena de texto, para la respectiva codificación. Por otra parte, Encoding.GetBytes hace la conversión propiamente dicha de un texto a bytes. En contraparte, GetString te convierte los bytes en cadena de texto. Una propiedad estática importante es Convert, que convierte un array de bytes de una codificación a otra (especificadas en el primer y segundo parámetro, respectivamente). Por otra parte, tenemos a Encoding.GetPreamble, que nos devuelve la marca que identifica un archivo de texto para esa codificación (recuerda las ?? del ejemplo anterior).

Algunas propiedades útiles son Encoding.EncodingName, que nos devuelve el nombre (Unicode, ASCII, UTF-32, etc.) del codificador empleado. Encoding.BodyName nos devuelve el nombre de la codificación en formato entendible para navegadores web. IsBrowserDisplay e IsMailNewsDisplay nos indican si una codificación puede ser desplegada por un navegador web, y por los clientes de correo y noticias. Y finalmente, Encoding.WindowsCodePage nos devuelve el equivalente código de página de la codificación en cuestión.

Anuncios
Categorías:.NET Framework, Apunte, C# Etiquetas:
  1. Alejandro
    junio 8, 2012 en 7:46 am

    amigos estoy teniendo un problema al generar el txt del banco de venezuela todo el formato lo estoy teniendo igual pero me dice el banco formato invalido, como puedo saber que tipo de codificacion se usa en el archivo original del banco porque puede ser eso necesito ayuda por favor

  2. edgar
    junio 24, 2015 en 3:29 pm

    muy bueno

  1. enero 2, 2011 en 12:12 pm

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s