Inicio Proyectos Librería NRtfTree Tutorial NRtfTree (1) – Conversor de RTF a HTML

Tutorial NRtfTree (1) – Conversor de RTF a HTML

por sgoliver

Esta entrada forma parte de una serie de artículos dedicados a NRtfTree, la librería .NET para tratamiento de documentos RTF, entre los cuales podrás encontrar una descripción detallada de la librería, documentación técnica, ejemplos y tutoriales de uso que pueden ser de tu interés. No olvides consultar la página principal de NRtfTree para más información.

En este tutorial vamos a construir desde cero el pequeño conversor de RTF a HTML que se proporciona junto a la librería NRtfTree utilizando para ello la primera variante de la librería, es decir, utilizando un tratamiento al estilo DOM para parsear y recorrer el documento.

La conversión del documento RTF no será exhaustiva, sino que nuestro conversor se limitará a tradudir tan sólo algunas características de formato frecuentes:

  • Tipo de fuente.
  • Tamaño de fuente.
  • Color de fuente.
  • Estilo de fuente: negrita, cursiva y subrayado.

Tratamiento DOM: Clases RtfTree y RtfTreeNode

Esta variante quizá sea la más sencilla de utilizar para realizar fácilmente muchas tareas sobre un documento RTF. Esto se debe a que el documento se carga completo al principio de la ejecución y por tanto lo tenemos disponible en cualquier momento y lo podemos consultar tantas veces como se necesite haciendo uso de los métodos de navegación del árbol RTF construido. Sin embargo, hay circunstancias en las que esta aproximación al problema puede resultarnos poco viable en la práctica debido precisamente al hecho de cargar el documento completo en memoria, junto a todas las estructuras auxiliares necesarias para construir el árbol RTF, lo que dependiendo del tamaño del documento a procesar, puede causarnos problemas de espacio y eficiencia.

Nuestro conversor implementado con esta variante seguirá el siguiente esquema general de funcionamiento:

  1. Definirá e inicializará todas las estructuras necesarias y cargará el documento RTF.
  2. Obtendrá las tablas de colores y fuentes para poder consultarlas durante el resto de la ejecución.
  3. Buscará el principio del texto del documento, obviando así el resto de los datos de la cabecera.
  4. Recorrerá el árbol de forma recursiva a partir de ese nodo e irá traduciendo el texto encontrado a formato HTML. Para ello, deberá ir manteniedo y actualizando en todo momento el formato a aplicar al texto leido del documento RTF.

Para almacenar el formato de un fragmento de texto definiremos una clase auxiliar que encapsule los 6 parámetros de formato que tendrá en cuenta nuestro conversor:

public class EstadoRtf
{
    public bool negrita;        //Fuente negrita

    public bool cursiva;        //Fuente cursiva
    public bool subrayado;      //Fuente subrayada

    public string fuente;        //Tipo de fuente
    public int tamFuente;        //Tamaño de fuente
    public Color color;          //Color de fuente

    public EstadoRtf(string fue, int tam, Color col, bool neg, bool cur, bool sub)
    {
        fuente = fue;
        tamFuente = tam;
        color = col;

        negrita   = neg;
        cursiva   = cur;
        subrayado = sub;
    }
}

Por otro lado, a nuestra clase principal que contedrá los métodos para realizar la conversión la llamaremos TraductorRTF y contedrá los siguientes atributos privados:

public class TraductorRtf
{
    private RtfTree     tree;          //Analizador del documento RTF

    private string[]    tFuentes;      //Tabla de fuentes  (array de string)

    private Color[]     tColores;      //Tabla de colores  (array de Color)

    private ArrayList   estados;       //Pila de estados del documento (array de EstadoRtf)
    private EstadoRtf   estadoActual;  //Estado formato RTF actual

    [...]
}

El primero de los atributos (tree) corresponde al árbol RTF que construirá la librería al cargar el documento RTF. Los dos atributos siguientes se utilizarán para almacenar las tablas de fuentes (tFuentes) y colores (tColores) del documento. Por último, los dos atributos de estado se utilizarán para almacenar la pila de formatos (estados) por la que se va pasando durante el recorrido del árbol (necesaria debido al tratamiento recursivo que se realizará para hacer la traducción) y el formato actual (estadoActual) a medida que se traduce. Más tarde comprenderemos mejor la necesidad de estas dos variables.

Por su parte, el constructor de esta clase no tendrá ninguna dificultad, limitándose a inicializar la pila de estados y a cargar el documento para construir el árbol RTF:

public TraductorRtf(string rutaRTF)
{
    //Inicialización de la pila de estados (formatos RTF)

    estados = new ArrayList();

    //Carga del documento RTF
    tree = new RtfTree();
    tree.LoadRtfFile(rutaRTF);
}

Como puede observarse, para esta primera implementación del conversor utilizaremos la clase RtfTree para almacenar el documento. El método que utilizamos para cargarlo será LoadRtfFile(), al que le pasamos como parámetro la ruta del documento. Este método se encargará de parsear y construir internamente el árbol RTF que representará al documento cargado.

Una vez inicializadas las estruturas internas necesarias para realizar la conversión y cargado el documento RTF es el momento de iniciar el proceso de conversión. Para ello definiremos un primer método general denominado traducir() que realizará algunas acciones iniciales:

  1. Obtener la tabla de fuentes del documento.
  2. Obtener la tabla de colores del documento.
  3. Iniciar la traducción.

Veamos en primer lugar lo sencillo que resulta la implementación de este método utilizando los métodos proporcionados por la clase RtfTree:

public string traducir()
{
    string res = "";

    //Se extrae la tabla de fuentes del documento
    tFuentes = tree.GetFontTable();

    //Se extrae la tabla de colores del documento
    tColores = tree.GetColorTable();

    //Se lanza el proceso de traducción del documento a formato HTML

    res = traducirTexto();

    //Se devuelve el resultado del proceso
    return res;
}

Como se observa en la implementación, la obtención de las tablas de fuentes y colores del documento RTF se reduce a llamar a los métodos GetFontTable() y GetColorTable(), respectivamente. El primero de los métodos devolverá un array de cadenas de caracteres con los nombres de todas las fuentes contenidas en la cabecera del documento y, obviamente, en el mismo orden. Por su parte, el metodo para obtener la tabla de colores devolverá un array de elementos de tipo Color (que contiene un color definido en formato RGB) con todos los colores definidos en la cabecera del documento. Por último, se llamará al método traducirTexto() para comenzar la verdadera traducción del texto.

La implementación de este último método será la siguiente:

public string traducirTexto()
{
    //Cabecera del documento HTML resultante

    string res = "<html><head></head><body>";

    //Se establece el estado inicial por defecto
    estadoActual = new EstadoRtf((string)tFuentes[0],10,(Color)tColores[0],false,false,false);

    //Se aplica el formato inicial definido en 'estadoActual'

    inicioFormato();

    //Se busca el indice del primer nodo perteneciente al texto
    bool enc = false;        //Encontrado el comienzo del texto
    int i = 0;

    RtfTreeNode nodo = new RtfTreeNode();

    while(!enc && i < tree.RootNode.FirstChild.ChildNodes.Count)
    {
        nodo = tree.RootNode.FirstChild.ChildNodes&#91;i&#93;;

        //El texto comenzará tras el primer token "pard"

        if(nodo.NodeKey == "pard")
        {
            enc = true;
        }

        i++;
    }

    //Se comenzará a traducir a partir del nodo en la posición 'primerNodoTexto'
    int primerNodoTexto = i - 1;

    //Inmersion
    res += traducirTexto(tree.RootNode.FirstChild, primerNodoTexto);

    //Se finaliza el estado inicial

    res += finFormato();

    //Finaliza el documento HTML
    res += "</body></html>";

    return res;
}

Este método comienza creando la cabecera del documento HTML que se devolverá como salida, que será siempre la misma. Posteriormente se almacena el estado de formato inicial con el que se comenzará a traducir el documento. Este estado inicial estará formado por el primer tipo de fuente almacenado en la tabla de fuentes, un tamaño arbitrario que en nuestro ejemplo hemos establecido a 10, el primer color de la tabla de colores del documento RTF, y todos los flags adicionales de estado desactivados (negrita, cursiva y subrayado).

En el documento HTML estableceremos los formatos mediante la etiqueta <font> y sus atributos face (nombre de la fuente), size (tamaño de la fuente) y color (color de la fuente). El resto de estilos se obtendrán utilizando las etiquetas <b> (fuente negrita), <i> (fuente cursiva) y <u> (fuente subrayada).

Para aplicar estos formatos a medida que vamos traduciendo el documento se definirá un método que escribirá todas las etiquetas HTML necesarias para comenzar el estilo definido por el atributo estadoActual y otro para finalizarlo. Por tanto, para ir cambiando el formato del HTML de salida tan sólo deberemos preocuparnos de ir actualizando el atributo estadoActual a medida que leemos el documento RTF y llamando a los métodos inicioFormato() y finFormato() en los lugares adecuados (normalmente al comienzo y final del documento y cada vez que encontremos un nodo de tipo TEXT, o lo que es lo mismo, cada vez que encontremos un fragmento de texto en el documento RTF y antes de escribirlo al HTML de salida). La implementación de estos dos métodos es muy sencilla y se mostrará más adelante.

Tras aplicar el formato inicial llamando a inicioFormato() el siguiente paso necesario será buscar el comienzo del texto del documento, desechando todo el contenido de la cabecera del RTF. Para ello haremos una búsqueda a través de todos los nodos de primer nivel del árbol RTF hasta encontrar el primer nodo cuya palabra clave sea «pard». Para hacer esto implementamos una búsqueda secuencial sencilla sobre todos los nodos inmediatamente inferiores al nodo raíz del árbol.

Un detalle importante a tener en cuenta es que el nodo raíz real del árbol RTF no se corresponde con la propiedad RootNode de la clase RtfTree, sino con el primer nodo hijo de éste. La propieda RootNode no es más que un nodo ficticio (de tipo ROOT) utilizado únicamente como nodo de partida del árbol RTF real. Este nodo tendrá por tanto un único nodo hijo (raíz real del arbol RTF) de tipo GROUP que contendrá a su vez a todos los nodos de primer nivel del árbol, entre ellos todos los grupos de la cabecera del documento y el comienzo del texto del mismo. En la página de introducción a la librería NRtfTree se muestra una representación visual de un árbol RTF de ejemplo donde se pueden aclarar estos conceptos.

Los principales elementos implicados en la búsqueda son los siguientes:

RootNode     Nodo principal ficticio (tipo ROOT).
RootNode.FirstChild     Nodo raíz real del árbol RTF (tipo GROUP)
RootNode.FirstChild.ChildNodes     Nodos hijo del nodo raíz (array de objetos RtfTreeNode)
RootNode.FirstChild.ChildNodes.Count     Número de nodos hijo del nodo raíz.
RootNode.FirstChild.ChildNodes[i]     Hijo i-ésimo del nodo raíz.

Teniendo claro como acceder a todos estos elementos resulta relativamente sencillo implementar la búsqueda del primer nodo de tipo KEYWORD cuya palabra clave sea pard . El único cabo suelto hasta el momento es cómo obtener la palabra clave asociada a un nodo. Para esto no tendremos más que acceder a la propiedad NodeKey de un nodo RTF (clase RtfTreeNode).

La búsqueda anterior se ha implementado «a mano» para ilustrar la sencillez con la que puede recorrerse un árbol RTF utilizando los métodos y propiedades expuestas. Sin embargo, en la versión v0.2 de la librería se incluyen varios métodos de búsqueda en la clase RtfTreeNode que podían haber hecho aún más sencilla la búsqueda anterior (consultar en la documentación aportada con la librería el método SelectSingleChildNode()).

Una vez localizada la posición del primer nodo de texto se llamará al método extendido traducirTexto() pasándole esta vez un nuevo parámetro con la posición a partir de la cual comenzaremos a leer el texto a traducir.

Por último, tan sólo nos queda finalizar el formato inicial y escribir las etiquetas de cierre del documento HTML generado.

Y por fin llegamos al método que contiene el grueso de las acciones necesarias para la traducción. Este método recorrerá uno a uno todos los nodos del árbol a partir de la posición pasada como parámetro e irá realizando las actualizaciones necesarias de formato y la generación del documento HTML de salida. Para ello, durante el recorrido se realizará una primera discriminación de nodos por su tipo, identificando tres tipos de tareas principales a realizar:

  • Nodos de tipo KEYWORD –> Posible actualización del formato actual.
  • Nodos de tipo GROUP –> Almacenamiento del estado actual y establecimiento de un nuevo formato para el grupo.
  • Nodos de tipo TEXT y CONTROL –> Aplicación del formato actual al HTML y escritura del texto leido.

Expliquemos esto un poco mas. En primer lugar, el formato del documento RTF viene establecido por las diferentes palabras clave encargadas de ello. Sin embargo, como ya indicamos al comienzo de este texto, nuestro conversor no tendrá en cuenta todos las características de formato posibles en un documento RTF, sino tan sólo unas pocas de ellas. Por tanto, cada vez que nuestro conversor lea un nodo de tipo KEYWORD tendrá que obtener la palabra clave concreta contenida en él (propiedad NodeKey) y en caso de ser una de las elegidas realizar la actualización concreta del formato actual (atributo estadoActual). Los estilos sencillos (negrita, cursiva y subrayado) tan sólo implicarán la activación o desactivación de la propiedad equivalente en el atributo estadoActual, y los cambios de tipo o color de fuente harán necesaria la obtención de la fuente o color de la tabla correspondiente a partir del parámetro del nodo (propiedad Parameter). Esto queda implentado en el fragmento de código siguiente:

[...]
else if(nodo.NodeType == RTF_NODE_TYPE.KEYWORD)
{
    switch(nodo.NodeKey)
    {
        case "f":  //Tipo de fuente

            estadoActual.fuente = (string)tFuentes[nodo.Parameter];
            break;
        case "cf":  //Color de fuente
            estadoActual.color = (Color)tColores[nodo.Parameter];
            break;
        case "fs":    //Tamaño de fuente

            estadoActual.tamFuente = nodo.Parameter;
            break;
        case "b":    //Negrita
            if(!nodo.HasParameter || nodo.Parameter == 1) 
                estadoActual.negrita = true;
            else

                estadoActual.negrita = false;
            break;
        case "i":    //Cursiva
            if(!nodo.HasParameter || nodo.Parameter == 1) 
                estadoActual.cursiva = true;
            else

                estadoActual.cursiva = false;
            break;
        case "ul":    //Subrayado ON
            estadoActual.subrayado = true;
            break;
        case "ulnone":    //Subrayado OFF

            estadoActual.subrayado = false;
            break;
        case "par":    //Nuevo párrafo
            res += "<br>";
            break;
    }
}

[...]

En segundo lugar, si encontramos un nodo de tipo grupo las acciones a realizar serán las siguientes:

  1. Apilar el estado de formato actual (añadiendo el atributo estadoActual al array estados).
  2. Crear un nuevo estado actual por defecto.
  3. Traducir y formatear el texto contenido en el grupo, llamando recursivamente a traducirTexto() con la posición actual.
  4. Restaurar el estado anterior apilado.

Esto es necesario porque al texto contenido en un grupo RTF se le aplica únicamente el formato definido en ese mismo grupo, independietemente del formato del resto del documento. Sin embargo, al «salir» del grupo el formato debe seguir siendo el mismo que antes de «entrar» en él. Esto queda implementado de la siguiente manera:

if(nodo.NodeType == RTF_NODE_TYPE.GROUP)
{
    //Se apila el estado actual
    estados.Add(estadoActual);

    //Se crea un nueo estado inicial
    estadoActual = new EstadoRtf((string)tFuentes[0],10,(Color)tColores[0],false,false,false);    

    res += traducirTexto(nodo,0);

    //Se desapila el estado anterior

    estadoActual = (EstadoRtf)estados[estados.Count-1];
    estados.RemoveAt(estados.Count-1);
}

Por último, quedan por tratar los nodos de tipo TEXT o CONTROL. Dado que los únicos símbolos de control que vamos a tener en cuenta son los encargados de representar a caracteres especiales (en nuestro caso las vocales acentuadas y la letra ‘ñ’), que en definitiva siguen siendo texto, las acciones a realizar para ambos serán análogas, con la única diferencia de que para los nodos de tipo palabra clave el texto se obtendrá de la propiedad NodeKey y para los símbolos de control se obtendrá de la propiedad Parameter.

else if(nodo.NodeType == RTF_NODE_TYPE.CONTROL)
{
    if(nodo.NodeKey == "'")
    {
        res += inicioFormato();
        res += (char)nodo.Parameter;
        res += finFormato();
    }
}
else if(nodo.NodeType == RTF_NODE_TYPE.TEXT)
{
    res += inicioFormato();
    res += nodo.NodeKey;
    res += finFormato();
}

Los dos métodos utilizados para la aplicación y finalización del formato actual son los siguientes:

public string inicioFormato()
{
    string res = "";

    //Fuente (tipo, tamaño y color)

    res += "<font face='" + estadoActual.fuente + "' size='" + estadoActual.tamFuente/8 +
           "' color='" + toHTMLColor(estadoActual.color) + "'>";

    //Negrita, Cursiva, Subrayado
    if(estadoActual.negrita)
        res += "<b>";

    if(estadoActual.cursiva)
        res += "<i>";

    if(estadoActual.subrayado)
        res += "<u>";

    return res;
}

public string finFormato()
{
    string res = "";

    //Negrita, Cursiva, Subrayado
    if(estadoActual.negrita)
        res += "</b>";

    if(estadoActual.cursiva)
        res += "</i>";

    if(estadoActual.subrayado)
        res += "</u>";

    //Fuente

    res += "</font>";

    return res;
}

Como se puede observar la implementación es sumamente sencilla, limitándose a escribir en el HTML las etiquetas necesarias para reflejar el formato almacenado en el atributo estadoActual.

Y con esto finalizaría la implementación de nuestro conversor de RTF a HTML y ya sólo quedaría probarlo construyendo una pequeña aplicación de ejmplo como la distribuida con la librería. La llamada al conversor se haría como sigue:

TraductorRtf trad = new TraductorRtf("c:midocumento.rtf");

string documentoHTML = trad.traducir();

El código fuente mostrado en este tutorial se proporciona como parte de la aplicación de ejemplo distribuida con la librería NRtfTree, la cual puede obtenerse desde la página de descargas.

Esta entrada forma parte de una serie de artículos dedicados a NRtfTree, la librería .NET para tratamiento de documentos RTF, entre los cuales podrás encontrar una descripción detallada de la librería, documentación técnica, ejemplos y tutoriales de uso que pueden ser de tu interés. No olvides consultar la página principal de NRtfTree para más información.

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información. Aceptar Más Información

Política de Privacidad y Cookies