Brad Abrams ha publicado en su blog una magnífica serie de entradas que a todos nos pueden interesar sobre Silverlight 3 y los nuevos .NET RIA Services. Se trata de una transcripción y ampliación de su charla impartida en el Mix09 llamada “Building Business Applications with Silverlight 3 and .NET RIA Services“. Podéis ver el video completo de la sesión accediendo a este enlace.

Os paso el índice de entradas:

Sin duda un fantástico y completo tutorial práctico de Silverlight 3 y .NET RIA Services.

, , , , ,

Hoy he descubierto por casualidad un blog que puede interesar a todos aquellos que estén empezando a aprender cositas sobre Windows Presentation Foundation (WPF). Se trata del blog de Oskar Alvarez, que desde que empezó a escribir artículos hace escasamente un mes, lleva ya publicadas más de una treintena de entradas, a modo de tutorial de introducción a WPF.

Tras leer algunas de ellas, os puedo decir que merece la pena echarles un vistazo, no por su exhaustividad, sino precisamente por todo lo contrario. Son entradas cortitas hablando sobre los temas más destacados de WPF, e intercalando ejemplos que ayudan a comprender mejor los conceptos. Y todo en español, que siempre es de agradecer.

Encontraréis por ejemplo una introducción a la arquitectura básica de WPF, algunas notas iniciales sobre XAML, layouts y contenedores en WPF (Canvas, StackPanel, DockPanel, WrapPanel …), uso de distintos controles en WPF como el Ribbon o el Grid, shapes, transformaciones, animaciones, data binding, integración y relación de WPF con otras tecnologías como WCF o Silverlight, y todo lo que se os pase por la cabeza.

Y esto parece sólo el principio, porque Oskar está imparable y seguro que nos tiene preparadas más entradas interesantes sobre esta tecnología. Os animo a visitar su blog.

De paso, os recuerdo también uno de los recursos de referencia a la hora de buscar información sobre Windows Forms y WPF en general (noticias, tutoriales, manuales, ejemplos…): http://windowsclient.net

, , ,

Algunos recursos con información valiosa sobre ASP.NET MVC:

  • La propia web oficial de ASP.NET MVC, con gran cantidad de información, tutoriales, videos, ejemplos…
  • La documentación oficial de ASP.NET MVC en MSDN.
  • Una lista de 10 cuestiones básicas sobre ASP.NET MVC que pueden ayudar a aclarar algunas dudas iniciales [Edito: Actulización de la lista de preguntas para la versión ASP.NET MVC 1.0].
  • Una serie de artículos de Scott Guthrie en cuatro entregas [1, 2, 3, 4].
  • Recordar también que el propio Scott Guthrie publicó en su blog hace unos días un fantástico tutorial de 185 páginas sobre ASP.NET MVC que forma parte del libro Professional ASP.NET MVC y cuyo enlace de descarga podéis encontrar en la entrada que dedicamos a dicha noticia.
  • Otro buen recurso de información sobre ASP.NET MVC es el ASP.NET MVC Training Kit publicado por Microsoft, que contiene entre otros Hand On Labs, demos y presentaciones sobre el framework.
  • Como utilizar ejemplos es casi siempre la mejor forma de aprender, os paso también una lista de aplicaciones de ejemplo [open source] construídas con ASP.NET MVC.
  • Y por último un par de artículos sobre consejos o buenas prácticas a la hora de utilizar ASP.NET MVC que pueden ayudarnos a hacer menos laboriosas algunas tareas.

Suficiente por el momento para comenzar a utilizar este fantástico framework.

, , , , ,

En la entrada anterior de esta serie hablamos de las alternativas más interesantes a la hora de elegir la distribución de Python y el IDE adecuados para desarrollar aplicaciones con este lenguaje. En esta nueva entrada vamos a detallar la instalación y preparación de un entorno de desarrollo para Python.

En nuestro caso nos vamos a adaptar a las restricciones del serivio de hosting donde vamos a hacer las pruebas, Google App Engine, el cual tan sólo soporta por el momento la versión de Python 2.5.x (de aquí en adelante entender “Python” como la distribución oficial de CPython), y en cuanto al IDE, teniendo en cuenta que este tutorial de Python va dirigido a programadres con experiencia en Java o .NET, las opciones más naturales entre todas las comentadas son obviamente las que se basan en un IDE ya existente para alguna de estas plataformas. Una vez más nos adaptamos a las condiciones de App Engine y nos decantamos finalmente por utilizar Eclipse con el plugin para Python PyDev, que soporta la distribución original de Python.

Veamos el proceso de instalación y configuración paso a paso:

  1. Descargar Python 2.5. La versión más reciente de la rama 2.5 es la versión de Python 2.5.4 [Página de descarga de Python 2.5.4].
  2. Descargar Eclipse. Si aún no tienes Eclipse instalado puedes descargar la última versión, Eclipse 3.4 o también conocido Eclipse Ganymede. Dado que el plugin PyDev tiene dependencias con algunas librerías no incluidas en la distribución básica de Eclipse for Java Developers recomiendo descargar la distribución llamada “Eclipse IDE for Java EE Developers“, ocupa algo más pero tendrás que descargar menos durante la instalación de PyDev. [Página de descarga de Eclipse]
  3. Instalar Python. La instalación de Python se realiza mediante un instalador estandar de Windows, en el que se pueden dejar seleccionadas todas las opciones por defecto.
    Instalación Python

    Instalación Python

  4. Instalar Eclipse. La instalación de Eclipse es aún más sencilla, ya que tan sólo habrá que descomprimir el zip descargado en la ubicación deseada para la aplicación.
  5. Instalar PyDev. La instalación de PyDev se realiza desde el propio gestor de actualizaciones de Eclipse. Para ello, seleccionar la opción de menú “Help / Software Updates…“.

    Eclipse - Menú Software Updates

    Eclipse - Menú Software Updates

    En la ventana “Software Updates and Add-ons” ir a la pestaña “Available Software” y pulsar el botón “Add Site…” para añadir la web de descarga de PyDev. Se debe añadir la dirección siguiente: “http://pydev.sourceforge.net/updates/”.

    Eclipse - Software Updates - PyDev

    Eclipse - Software Updates - PyDev

    Una vez añadido nuestro repositorio a la lista, debemos seleccionarlo e iniciar la instalación del plugin mediante el botón “Install…“, lo que iniciará la descarga e instalación en segundo plano del plugin PyDev y todas las librerías necesarias. Concluida la instalación habrá que reiniciar Eclipse para que se apliquen todos los cambios.

  6. Configurar PyDev. Una vez instalado PyDev en Eclipse tendremos que configurarlo para que utilice la distribución de Python que hemos instalado previamente. Para ello accedemos a las opciones de Eclipse a través del menú “Window / Preferences…“.
    Eclipse - Menú Preferencias

    Menú Preferencias

    En el apartado de opciones de PyDev accedemos a la sección “Interpreter – Python” y mediante el botón “New…” añadimos la ruta al fichero “python.exe” de nuestra distribución de Python.

    Preferencias Eclipse - PyDev

    Preferencias Eclipse - PyDev

    Una vez seleccionado PyDev buscará las librerías base de Python y nos mostrará una nueva ventana con las que encuentre. Podemos aceptar la selección por defecto:

    Eclipse - Preferencias PyDev - Librerías

    Eclipse - Preferencias PyDev - Librerías

Y con esto ya tenemos todo configurado para comenzar a desarrollar con Python sobre la base de Eclipse.

En la siguiente entrada crearemos nuestro primer proyecto Python y comentaremos algunos conceptos básicos del lenguaje y las herramientas de desarrollo que hemos instalado y configurado.

, , , , , , , , , , ,

Voy a comenzar este tutorial de Python para programadores presentando un poco las diferentes versiones, distribuciones y herramientas disponibles para el desarrollo de aplicaciones en este lenguaje.

En primer lugar hay que destacar que actualmente existen dos ramas del lenguaje: Python 2.x y Python 3.x (también conocido como Python 3000 o Py3K). La diferencia entre ambas no la marca simplemente algunas mejoras y el número de versión sino las importantes diferencias entre ellas que hacen que incluso se haya perdido la compatibilidad hacia entre versiones. De ahí la importancia de la elección entre Python 2.x o 3.x. Se pueden consultar algunas de las diferencias más importantes entre Python 2.x y Python 3.x en su web de documentación.

En cuanto a las distribuciones disponibles tenemos aún más alternativas. La distribución original y más extendida es CPython, que compila código para su propio runtime incluido en la instalación. Sin embargo, existen otras muchas implementaciones del lenguaje sobre otras plataformas, aunque hay que considerar en cada caso sus diferencias con CPython. Por nombrar las más interesantes:

  • Jython, implementación del lenguaje Python para la plataforma Java. Diferencias entre Jython y CPython.
  • IronPython, implementación del lenguaje Python para la plataforma .NET. Diferencias entre IronPython y CPython.

Le toca el turno a los entornos de desarrollo (IDEs) para Python. Existen algunos buenos IDEs comerciales para Python, como Wingware o Komodo, pero me voy a centrar en otras alternativas gratuitas y/o open source que son igualmente válidas, si no mejores.

En primer lugar podemos encontrar algunos IDEs completos como IDLE (incluido con la propia distribución de CPython, bastante básico) o PyScripter. Sin olvidar por supuesto que podemos utilizar nuestro editor de texto favorito (Vim, Emacs, Notepad++, …) para la edición de código y ejecutar las aplicaciones desde la línea de comandos.

Pero dado que esto pretende ser un tutorial de Python dirigido a programadores Java y .NET, existe otra alternativa más interesante para el desarrollo con Python, y consiste en utilizar algún plugin Python para nuestro propio IDE Java o .NET. Así, las opciones más extendidas son:

  • Netbeans + Python. Extensión de Netbeans que permite el desarrollo de aplicaciones Python sobre Netbeans. Esta extensión se encuentra aún en desarrollo por lo que su funcionalidad y estabilidad podrían no ser aún las deseadas.
  • Eclipse + PyDev. Plugin de Eclipse para el desarrollo con Python y Jython. Incorpora las opciones típicas: completado de código, coloreado de código, refactorización, depuración y muchas otras.
  • Visual Studio + IronPython. Implementación de Python para la plataforma .NET. Incluye integración con Visual Studio para poder aprovechar las características de este IDE en el desarrollo de aplicaciones Python.
  • SharpDevelopr + IronPython. En la última versión del fantástico IDE SharpDevelop se incluye soporte para el desarrollo con IronPython.
  • IronPython Studio. IDE completo desarrollado sobre la plataforma Visual Studio Shell, es decir, IDE construido sobre la base de Visual Studio pero completamente independiente de éste último.

En mi caso particular, he elegido la distribución de CPython versión 2.5 y utilizaré Eclipse y PyDev como entorno de desarrollo. Los motivos son simples, es ésta la versión de Python que soporta por ahora Google App Engine, que será el servicio de hosting Python que utilizaré durante las pruebas.

En la siguiente entrada de la serie detallaré la instalación y preparación del entorno de desarrollo indicado (Python 2.5 + Eclipse + PyDev).

, , , , , , , ,

Mientras preparo los artículos prometidos sobre inteligencia artificial (un avance: comenzaré publicando artículos sobre algoritmos genéticos), recordando además que uno de los objetivos que me marqué para este año era aprender un nuevo lenguaje de programación, y aprovechando que tenía en mente implementar una versión web de una pequeña aplicación de escritorio que desarrollo en mis ratos libres, decidí hace unos días aprender algo del lenguaje Python. ¿Y por qué Python? ¿Este blog no trataba sobre .NET y Java? Pues sí, casi siempre me centro en las plataformas .NET y Java, y existe tecnología web basada en ambas, pero por otros motivos muy distintos también necesitaba evaluar el servicio de hosting de Google App Engine, del que hablaré más adelante, y actualmente este servicio soporta únicamente aplicaciones escritas en Python.

Aclarado esto a modo de introducción para próximas entradas, también quería reflexionar un poco en voz alta sobre algo que he recordado mientras buscaba documentación sobre Python. Lanzo primero la pregunta, ¿por qué no existen manuales para programadores? Intento explicarme mejor. En estos tiempos que corren es fácil encontrar tanto en la red como en las librerías de toda la vida documentación variada sobre cualquier lenguaje de programación. Libros, manuales, tutoriales, todo lo que se pueda imaginar. Sin embargo, en mi humilde opinión hay algo demasiado común en toda la documentación de iniciación a un lenguaje de programación, y me refiero a todos esos conceptos básicos y/o triviales que todo programador ya experimentado debería conocer.

Sigo intentando explicarme. Si eres por ejemplo un programador con relativa experiencia y conocimientos en lenguajes imperativos de alto nivel orientados a objetos (algo para nada descabellado teniendo en cuenta que hoy día casi todo el mundo se ha criado o ha tenido al menos que lidiar con lenguajes como C++ o Java) y compras un libro para aprender Python, muy probáblemente no necesites que ese libro dedique 10 páginas a explicar los operadores aritméticos del lenguaje o que se reserve todo un capítulo a detallar las instrucciones condicionales e iterativas y a explicar para qué sirve una instrucción IF o un bucle WHILE. Desgraciadamente esto es más común de lo que debería ser, y la mayoría de los manuales sobre lenguajes de programación parten de un nivel demasiado bajo. Y ojo, no estoy diciendo que no deban existir referencias completas y exhaustivas sobre cualquier lenguaje, tan solo digo que sería interesante que existieran más libros donde se dedicaran sus páginas a detallar las particularidades de un lenguaje en relación a otros más comunes, y no limitarse a repetir los mismos conceptos una y otra vez. Algo así como “La sintáxis de una instrucción condicional en Python es de tal forma y su comportamiento es el habitual: el bloque de código se ejecutará cuando se cumpla la condición indicada”. Sin más, dos líneas de texto, a lo sumo tres, no cinco páginas. En definitiva, libros sobre programación dirigidos a programadores con una mínima base.

Para aportar mi pequeño granito de arena, voy a dedicar varias entradas a hacer un resúmen de las cosas que he aprendido sobre Python y Google App Engine durantes estos días. No esperéis conceptos demasiado avanzados, tan sólo una base para comenzar e implementar alguna aplicación web sencilla. Espero que algún programador que quiera dar sus primeros pasos con Python lo encuentre menos aburrido que una referencia de 500 páginas sobre este lenguaje.

Entradas de la serie (Tutorial de Python para programadores Java/C#):

  1. Tutorial de Python (I): Versiones, distribuciones e IDEs.
  2. Tutorial de Python (II): Instalación de Python + Eclipse + PyDev
  3. Tutorial de Python (III): Creación de proyectos en IDLE y Eclipse
  4. (En desarrollo…)
, , , , ,

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 segundo tutorial nos vamos a centrar en el tratamiento de documentos RTF utilizando la segunda variante de la librería NRtfTree, es decir, realizando un tratamiento basado en eventos al estilo SAX.

Para ilustrar esto pretendemos construir un pequeño conversor de RTF a algo parecido a un XML donde se marquen mediante etiquetas los fragmentos de texto con alguno de los tres formatos más básicos que conocemos: negrita, cursiva y subrayado. Obviamente este conversor no es nada útil pero pienso que su sencillez puede aclarar algunas dudas sobre la forma de proceder con este segundo módulo de la librería.

Como ejemplo práctico de lo que deberá hacer el conversor tenemos lo siguiente, dado el texto:

NRtfTree es una librería escrita íntegramente en C# para el tratamiento estructurado de documentos RTF.

debería convertirse en lo siguiente:

<documento>NRtfTree es una librería <u>escrita íntegramente en C#</u> para el <i>tratamiento estructurado</i> de <b>documentos RTF</b>.</documento>

Tratamiento SAX: Clases RtfReader y SARParser

El esquema de trabajo con estas dos clases será el siguiente:

  1. Se creará un clase heredada de SARParser donde se redefinirán todos los eventos de ésta última.
  2. Se creará un objeto RtfReader asociado a la clase anterior.
  3. Se cargará el documento RTF mediante el método LoadRtfFile() proporcionado por el objeto RtfReader.
  4. Se iniciará la lectura del documento mediante el método Parse() proporcionado por el objeto RtfReader.

Este último paso tendrá como efecto inmediato que se comience a llamar automáticamete a los métodos redefinidos del objeto heredado de SARParser a medida que se va leyendo el documento RTF. De esta forma, cada vez que se lea una palabra clave se llamará automáticamente al método RtfKeyword(), cada vez que se lea un fragmento de texto se llamará automáticamente al método RtfText() y de forma análoga para el resto de eventos.

El listado completo de eventos es el siguiente:

StartRtfDocument() Se ha comenzado a leer el documento RTF.
EndRtfDocument() Se ha terminado de leer el documento RTF.
StartRtfGroup() Se ha leido un comienzo de grupo RTF ( caracter ‘{‘ )
EndRtfGroup() Se ha leido un final de grupo RTF ( caracter ‘}’ )
RtfKeyword(key,hasParam,param) Se ha leido una palabra clave. Se reciben como parámetros la palabra clave, key, un indicador para saber si dicha palabra clave tiene algún parámetro, hasParam, y el parámetro en caso de existir, param.
RtfControl(key,hasParam,param) Se ha leido un símbolo de control. Se reciben como parámetros el símbolo de control, key, un indicador para saber si dicho símbolo de control tiene algún parámetro, hasParam, y el parámetro en caso de existir, param.
RtfText(text) Se ha leido un fragmento de texto. Se recibe como parámetro el texto leido, text. Se debe tener en cuenta que no sólo se considerará texto al texto real del documento, sino también al contenido por ejemplo en los llamados “destinations” en la especificación RTF.

Entendida esta forma de funcionamiento, queda claro que todas las acciones necesarias para nuestro conversor deberán implementarse dentro de la clase que heredemos de SARParser y distribuidas convenientemente entre los distintos eventos disponibles. Por tanto, pasemos directamente a los detalles de implementación de esta clase.

En primer lugar, nos fijaremos en las etiquetas <documento></documento> que debemos colocar al principio y final de nuestro fichero resultante. Estás etiquetas deben aparecer tan sólo una vez y en los extremos del documento por lo que el sitio ideal para escribirlas será dentro de los eventos StartDocument() y EndDocument(). Veamos cómo:

public class MiParser : SARParser
{
    public override void StartRtfDocument()
    {
        doc += "<documento>\r\n";
    }

    public override void EndRtfDocument()
    {
        doc += "\r\n</documento>";
    }

    [...]
}

Los estilos de formato nos vendrán indicados en el documento RTF mediante palabras clave, por lo que para detectar los cambios de formato que vamos a tener en cuenta para nuestro conversor y poder escribir las etiquetas correspondientes al documento de salida tendremos que redefinir el evento RtfKeyword() y realizar dentro de éste las acciones necesarias. Para llevar el control del formato actual definiremos tres variables booleanas que indiquen si se han iniciado los estilos negrita, cursiva o subrayado.

Por otro lado, hay que recordar que normalmente en un documento RTF existen muchos fragmentos de texto que realmente no aparecen en la representación final del RTF (por ejemplo en las palabras clave consideradas “destination”, para más detalles se puede consultar la especificación RTF). Estos fragmentos de texto no los queremos tener en cuenta en ningún sentido, por lo que deberemos llevar el control de cuándo comienza el texto real del documento, tras la primera palabra clave “\pard”. Para esto definiremos otra variable booleana desactivada por defecto, que sólo activaremos al encontrar el comienzo real del texto.

En caso de detectarse alguna de las palabras clave consideradas (\b, \i, \ul), se activará o desactivará su variable asociada según el valor del parámetro asociado y se escribirá al documento de salida la etiqueta correspondiente.

private bool enTexto        = false;
private bool negrita        = false;

private bool cursiva        = false;
private bool subrayado        = false;

[...]

public override void RtfKeyword(string key, bool hasParam, int param)
{
    if(key.Equals("pard"))
        enTexto = true;

    if(enTexto)
    {
        switch(key)
        {
            case "b":
                if(!hasParam || (hasParam && param == 1))
                {
                    doc += "<b>";
                    negrita = true;
                }
                else
                {
                    doc += "</b>";
                    negrita = false;
                }
                break;
            case "i":
                if(!hasParam || (hasParam && param == 1))
                {
                    doc += "<i>";
                    cursiva = true;
                }
                else
                {
                    doc += "</i>";
                    cursiva = false;
                }
                break;
            case "ul":
                doc += "<u>";
                subrayado = true;
                break;
            case "ulnone":
                doc += "</u>";
                subrayado = false;
                break;
        }
    }
}

En cuanto a los inicios y finales de grupo tan sólo tendremos que tener en cuenta éstos últimos. Esto es debido a que en RTF no hay por qué “cerrar” las etiquetas abiertas anteriormente como en HTML, es decir, que una etiuqeta \b no tiene por qué ir seguida de una \b0 para terminar de escribir en negrita, sino que la primera puede ir encerrada dentro de un grupo RTF y el formato definido dentro de un grupo sólo se aplica dentro de éste, independientemente del formato definido fuera de él. Por tanto, cada vez que detectmenos un final de grupo deberemos cerrar todas las etiquetas de nuestro documento de salida que estuvieran abiertas con anterioridad. Veamos el código:

public override void EndRtfGroup()
{
    if(enTexto)
    {
        if(negrita)
            doc += "</b>";

        if(cursiva)
            doc += "</i>";

        if(subrayado)
            doc += "</u>";

        negrita = false;
        cursiva = false;
        subrayado = false;
    }
}

Por último, sólo nos quedan los símbolos de control y los fragmentos de texto. Para nuestro conversor trataremos ambos igual dado que los únicos símbolos de control que vamos a tener en cuenta son los destinados a codificar caracteres especiales (en nuestro caso las vocales acentuadas y las ‘ñ’). Para ambos, lo único que haremos será escribir el fragmento de texto o el caracter especial al documento de salida, con la única diferencia de que para los caracteres especiales tendremos que hacer la conversión previa entre el valor del parámetro asociado al símbolo de control y el caracter en castellano:

public override void RtfControl(string key, bool hasParam, int param)
{
    if(key == "'") //Caracter especial
    {
        doc += translateAnsiCode(param);
    }
}

public override void RtfText(string text)
{
    if(enTexto)
    {
        doc += text;
    }
}

La implementación del método translateAnsiCode() es sumamente sencilla y puede consultarse en el código fuente proporcionado con la librería y su aplicación de demostración.

Vista la implementación de nuestra clase heredada de SARParser, ya sólo nos queda ver cómo cargar el documento RTF y lanzar la lectura del mismo para que se ejecuten las acciones definidas en los eventos de SARParser.

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

    //Construimos nuestro parser
    MiParser parser = new MiParser(res);

    //Construimos el RTFReader que tratará el documento a través del SARParser creado anteriormente.
    reader = new RtfReader(parser);

    //Cargamos el fichero RTF
    reader.LoadRtfFile(rutaRTF);

    //Comenzamos el análisis del documento
    reader.Parse();

    return parser.doc;
}

El método anterior se limita a seguir los cuatro pasos indicados al principio de este texto: crear un objeto de nuestra clase heredada, crear un objeto RtfReader asociándole el objeto anterior (se le pasa como parámetro al constructor), cargar el documento RTF mediante el método LoadRtfFile() y por último iniciar la lectura del documento mediante el método Parse().

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.

, , , , ,

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[i];

        //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.

, , , , ,

Hace ya algún tiempo pormetí que publicaría la documentación del proyecto FKScript en formato PDF para que pudiera ser descargada facilmente. El trabajo y otras ocupaciones no me han permitido hasta hace muy poco encontrar algo de tiempo para reunir y maquetar toda la información en un sólo documento. Sin embargo, desde hoy mismo disponéis de un primer borrador en PDF de unas 70 páginas para que podáis revisarlo.

He creado una pequeña página con información sobre el documento, la tabla de contenidos y el capítulo de introducción para que todo el mundo sepa qué puede esperar de este libro, tutorial o como queramos llamarlo.

Espero que este documento sea la referencia que pretendía que fuera para todos aquellos que queráis introduciros un poco en el desarrollo de compiladores y máquinas virtuales, y para aquellos que queráis aprender cómo y en qué medida os pueden ayudar herramientas como ANTLR durante el proceso de desarrollo.

Ni que decir tiene que si tenéis propuestas, modificaciones, ampliaciones o habéis encontrado algún error en el texto o los ejemplos no dudéis en contactar conmigo a través de la dirección de correo electrónico que aparece en la información de contacto.

, , , , , , , ,

A través de Digg, me entero de la existencia de una interesante sección del sitio de Google Code que no conocía.

Para quién no lo sepa Google Code aglutina todos los recursos relacionados con el desarrollo de software que pone Google a nuestra disposición. Entre estos recursos podemos encontrar documentación y ejemplos de todas las API de los servicios web de Google, información sobre los programas de desarrollo creados como iniciativa de esta compañía, como por ejemplo el Summer of Code, o el acceso al fantástico servicio de hosting para proyectos open source de Google.

A todo esto añado a partir de hoy la sección Google Code University (o Google Code for Educators), que no es más que un repositorio de información sobre distintas tecnologías y disciplinas de desarrollo como AJAX, sistemas distribuidos, seguridad web o lenguajes de propósito general como Java, C++ o Phyton. En este sitio podemos encontrar tutoriales, presentaciones, ejemplos, e incluso videos de presentaciones en directo.

En definitiva, un recurso interesante al que al menos merece la pena echar un vistazo.

, , , , ,