Archivo Mensual de Junio, 2008

Jugando con ArrayList en VisualBasic.NET (II).

En el anterior post estuve hablando de los ArrayList en VisualBasic.NET y comenté que para que el método BinarySearch funcione es necesario que los objetos dentro del ArrayList estén ordenados. Al menos para que funcione correctamente, claro.

Una de las maneras de ordenarlos era utilizando el método Sort. Otra consistía en añadir los objetos mediante el método Insert y hacerlo en la posición adecuada para que el ArrayList estuviera siempre ordenado. Así que en cualquier caso necesitaremos que los objetos que llenan el ArrayList se puedan comparar. Si lo hacemos tirando de método Sort porque tendrán que poder ordenarse. Si no, porque BinarySearch necesita poder comparar dos objetos para determinar si son el mismo en función de los criterios que le digamos.

En mi ejemplo teníamos un ArrayList lleno de personas que eran instancias de la clase clsPerson. Así que vamos a ver cómo hacer que dos objetos clsPerson se puedan comparar. Para ello ya dije que tenemos dos alternativas: mediante una clase externa que implemente un IComparer, o haciendo que clsPerson implemente IComparable.

En este post explico el segundo sistema: hacer que clsPerson implemente IComparable. En realidad es muy sencillo, puesto que sólo tenemos que crear la clase como lo haríamos en circunstancias normales y tener en cuenta que tenemos que hacerle dos pequeñas modificaciones:

1) Tras la definición de la clase (Public Class clsPerson) informarle que va a ser una clase que implementará IComparable (Implements IComparable).

2) Crearle un método CompareTo para poder comparar la instancia actual con otra instancia de clsPerson. De hecho al pulsar Enter después de añadir el Implements IComparable ya se nos creará el método CompareTo automáticamente.

Adjunto código básico:

'--------------------------------------------------------------------
' Author:      Albert Mata (www.albertmata.net)
' Date:        20080622
' Description: Class to show how to work with ArrayList. 
'--------------------------------------------------------------------
Public Class clsPerson
    Implements IComparable 'in order to be able to be compared

    '----------------------------------------------------------------
    ' Attributes.
    '----------------------------------------------------------------
    Public Name As String
    Public Age As Integer

    '----------------------------------------------------------------
    ' Constructor method.
    '----------------------------------------------------------------
    Public Sub New(ByVal Name As String, ByVal Age As Integer)
        Me.Name = Name
        Me.Age = Age
    End Sub

    '----------------------------------------------------------------
    ' Comparator method. Returns:
    '  -1 if current instance is "smaller" than parameter object.
    '   0 if current instance is "equal to" parameter object.
    '  +1 if current instance is "bigger" than parameter object.
    '----------------------------------------------------------------
    Public Function CompareTo(ByVal obj As Object) As Integer _
    Implements System.IComparable.CompareTo
        'Converting parameter object to clsPerson type.
        Dim Compare As clsPerson = CType(obj, clsPerson)
        'First criterium to be used to compare.
        Dim Result As Integer = Me.Name.CompareTo(Compare.Name)
        'When draw happens, next criterium to be used to compare.
        If Result = 0 Then
            Result = Me.Age.CompareTo(Compare.Age)
        End If
        'Returning result.
        Return Result
    End Function

End Class

Como se puede ver, el método CompareTo no entraña dificultad. Simplemente hay que ir dándole criterios con los que poder comparar la instancia sobre la que trabajamos con otra instancia que la función recibirá como argumento. En principio en nuestro código no necesitaremos invocar directamente esta función, ya que es algo que hará de manera interna el método Sort o BinarySearch del ArrayList que contiene los objetos de tipo clsPerson. No obstante como método normal y corriente que es nada nos impide utilizarlo también de manera directa. Para ella creamos por ejemplo 5 objetos con las instrucciones:

Dim P1 As New clsPerson("Albert", 29)
Dim P2 As New clsPerson("Morgan", 32)
Dim P3 As New clsPerson("Albert", 35)
Dim P4 As New clsPerson("Morgan", 32)
Dim P5 As New clsPerson("Zoe", 17)

Y a continuación podemos realizar llamadas y obtener estos resultados:

P1.CompareTo(P2) returns... -1
P5.CompareTo(P1) returns...  1
P3.CompareTo(P1) returns...  1
P2.CompareTo(P4) returns...  0

Lo que nos dice que Albert-29 es más “pequeño” que Morgan-32 (ojo, la ordenación va por nombre, no por edad), Zoe-17 es más “grande” que Albert-29, Albert-35 es más “grande” que Albert-29 (si la ordenación por nombre no puede discernir, pasa a las edades) y finalmente Morgan-32-P2 es igual a Morgan-32-P4, lo cual es correcto porque ambos objetos son iguales (es sólo un ejercicio teórico, en realidad no existirían los dos objetos porque de hecho sería un objeto duplicado).

Jugando con ArrayList en VisualBasic.NET (I).

¿Quién no hizo sus primeros pinitos en su día con simples arrays -o matrices o vectores- gozando de esa manera ordenada de acumular valores? Primero los unidimensionales… después sufriendo un poquito más con los multidimensionales… ¡qué tiempos! Ahora VisualStudio.NET (como también hace Java e imagino que cualquier lenguaje orientado a objetos) nos ofrece el ArrayList, esto es… una clase contenedora que vendría a ser el array de toda la vida pero MUY mejorado, ya que incluye multitud de métodos que nos ayudan a hacer fácilmente lo que antes con arrays teníamos que programar nosotros. El caso es que los ArrayList son precisamente lo último con lo que me he peleado seriamente, ya que necesito crear listas de objetos propios con muchísimos elementos (varios cientos de miles) y quería ver bajo qué circunstancias ofrece un mejor rendimiento.

Vamos al grano. He estado haciendo simulaciones de rendimiento con dos sistemas distintos. A nivel general tengo:

Private ListOfPersons As ArrayList
Me.ListOfPersons = New ArrayList()

Y una clase clsPerson de la que crearé numerosísimas instancias (objetos) para ver qué tal funciona el ArrayList.

En el sistema 1 tendremos algo del estilo de:

Dim TempPerson As clsPerson
For i As Integer = 1 To OBJETOS_POTENCIALES
    TempPerson = New clsPerson(Mid(Rnd.Next.ToString, 1, _
                                   LONGITUD_NOMBRE))
    If Me.ListOfPersons.BinarySearch(TempPerson) < 0 Then
        Me.ListOfPersons.Add(TempPerson)
        Me.ListOfPersons.Sort()
    End If
Next

Mientras que en el sistema 2 tendremos:

Dim TempPerson As clsPerson
For i As Integer = 1 To OBJETOS_POTENCIALES
    TempPerson = New clsPerson(Mid(Rnd.Next.ToString, 1, _
                                   LONGITUD_NOMBRE))
    If Not Me.PersonExist(TempPerson.Name) Then
        Me.ListOfPersons.Add(TempPerson)
    End If
Next

Donde PersonExist es una función para determinar si el objeto clsPerson en cuestión existe ya o no (el único criterio que pongo para discernir si dos personas son o no la misma es el nombre -o el código al azar que genero a modo de nombre-). Para ello utiliza un enumerador de la siguiente forma:

Public Function PersonExist(ByVal Name As String) As Boolean
    Dim IT As IEnumerator = Me.ListOfPersons.GetEnumerator()
    Dim CurrentPerson As clsPerson
    Dim Found As Boolean = False
    While IT.MoveNext And Not Found
        CurrentPerson = IT.Current
        'Two persons are the same when names are equal.
        If CurrentPerson.Name = Name Then
            Found = True
        End If
    End While
    Return Found
End Function

Tanto en un escenario como en el otro, OBJETOS_POSIBLES será el número de objetos que crearemos para añadir al ArrayList, aunque algunos de ellos no se añadirán si ya están en el ArrayList, puesto que no queremos crear dos objetos para una misma persona. Por su parte, LONGITUD_NOMBRE marcará de alguna manera el número máximo de objetos distintos que podremos tener, ya que con un valor 1 admitirá máximo 9, con un valor 2 admitirá 99, con 3 admitirá 999 y así sucesivamente. De este modo, jugando con ambos valores podremos evaluar los dos métodos bajo distintos escenarios.

+----------------------------------------+
| OBJETOS_POTENCIALES = 100.000          |
| LONGITUD_NOMBRE     = 3                |
+----------------------------------------+
| Sistema 1 tarda...   1368 milisegundos |
| Sistema 2 tarda...   1682 milisegundos |
+----------------------------------------+

+----------------------------------------+
| OBJETOS_POTENCIALES = 500.000          |
| LONGITUD_NOMBRE     = 3                |
+----------------------------------------+
| Sistema 1 tarda...   2606 milisegundos |
| Sistema 2 tarda...   8705 milisegundos |
+----------------------------------------+

+----------------------------------------+
| OBJETOS_POTENCIALES = 50.000           |
| LONGITUD_NOMBRE     = 4                |
+----------------------------------------+
| Sistema 1 tarda... 130602 milisegundos |
| Sistema 2 tarda...   7354 milisegundos |
+----------------------------------------+

Vemos pues que cuando trabajamos con pocos objetos (LONGITUD_NOMBRE = 3, por tanto máximo 999 objetos), el sistema con el BinarySearch se muestra más rápido. Sin embargo a la que crece el número de objetos (LONGITUD_NOMBRE = 4, por tanto máximo 9.999 objetos) el sistema del BinarySearch es terriblemente más lento que el sistema más manual.

Esto es así porque aunque el sistema que utiliza el BinarySearch es en principio más elegante (al fin y al cabo se trata de un método propio del ArrayList en lugar de un método nuevo creado a mano), para funcionar necesita que el ArrayList esté siempre ordenado, de lo contrario no funciona. Para ello en principio tenemos que recurrir a efectuar un Sort tras cada nuevo objeto añadido al ArrayList, pero como hemos visto, cuando estamos trabajando con muchos objetos este proceso se vuelve lentísimo y el método manual tirando de iterador resulta más eficiente.

Ahora bien, en realidad no es necesario hacer esto -ordenar el ArrayList tras cada incorporación- ya que el ArrayList nos ofrece un método ideal para solventar esta situación: el Insert. Con el Insert podemos añadir nuevos objetos al ArrayList igual que hacemos con el Add, sólo que en este caso le decimos en qué posición queremos que nos lo añada, corriendo un lugar hacia delante el resto de objetos desde esa posición hasta el final. ¿Qué conseguimos con esto? No necesitar ya el Sort para nada, puesto que el ArrayList estará siempre ordenado -si tenemos la precaución de situar cada nuevo objeto donde le corresponde- y por tanto podremos utilizar el BinarySearch. Llegamos así al sistema 3 y mejor de todos los expuestos:

Dim TempPerson As clsPerson
Dim Index As Integer
For i As Integer = 1 To OBJETOS_POTENCIALES
    TempPerson = New clsPerson(Mid(Rnd.Next.ToString, 1, _
                                   LONGITUD_NOMBRE))
    Index = Me.ListOfPersons.BinarySearch(TempPerson)
    If Index < 0 Then
        Me.ListOfPersons.Insert((Index * (-1)) - 1, TempPerson)
    End If
Next

¿Pero cómo sabemos en qué posición debemos añadir el objeto? Debemos jugar con que BinarySearch además de devolvernos el valor de la posición (en base cero) del objeto coincidente cuando existe, también nos devuelve en negativo el valor de la posición (en base uno) del primer objeto mayor que el nuestro. Así pues, aplicamos eso de (Index * (-1)) - 1 y obtenemos la posición en la que queremos insertar el nuevo registro.

Analizando los resultados con el nuevo sistema vemos que no hay color:

+----------------------------------------+
| OBJETOS_POTENCIALES = 100.000          |
| LONGITUD_NOMBRE     = 3                |
+----------------------------------------+
| Sistema 1 tarda...   1368 milisegundos |
| Sistema 2 tarda...   1682 milisegundos |
| Sistema 3 tarda...    319 milisegundos |
+----------------------------------------+

+----------------------------------------+
| OBJETOS_POTENCIALES = 500.000          |
| LONGITUD_NOMBRE     = 3                |
+----------------------------------------+
| Sistema 1 tarda...   2606 milisegundos |
| Sistema 2 tarda...   8705 milisegundos |
| Sistema 3 tarda...   1547 milisegundos |
+----------------------------------------+

+----------------------------------------+
| OBJETOS_POTENCIALES = 50.000           |
| LONGITUD_NOMBRE     = 4                |
+----------------------------------------+
| Sistema 1 tarda... 130602 milisegundos |
| Sistema 2 tarda...   7354 milisegundos |
| Sistema 3 tarda...    238 milisegundos |
+----------------------------------------+

Hay que tener en cuenta que para que BinarySearch funcione, los objetos clsPerson se tienen que poder comparar. Para ello habrá que implementar de una manera u otra algún comparador, ya sea mediante una clase externa que implemente un IComparer, ya sea haciendo que clsPerson implemente IComparable. En los dos próximos posts (siguiente y siguiente) mostraré estas dos alternativas con ejemplos de código minimalista para que funcione.

Crear librería .dll en .NET… ¡y utilizarla!

Aunque la cantidad de controles y clases que nos ofrece VisualStudio2005 para utilizar en un proyecto es realmente importante y variada, es probable que en algún momento queramos crearnos un control personalizado o una clase para responder a una necesidad frecuente y particular de nuestro proyecto concreto. En mis proyectos, por ejemplo, estoy utilizando mucho un control propio creado por mí al que llamo Matchcode y que funciona de modo ligeramente parecido a como lo hacen los matchcodes en SAP.

Para el que no conozca SAP, se trata de un simple textbox que tiene que recoger un valor y que dispone de ayudas para ello. Imaginemos por ejemplo que tenemos que introducir un código de cliente en un textbox. Pues bien, con mi Matchcode si sabemos el código del cliente porque es un cliente habitual, podemos introducirlo directamente y validarlo con Enter. Pero si no lo sabemos podemos introducir cualquier parte de su nombre y pulsar F4. Entonces se nos desplegará un pop-up con todas las coincidencias encontradas. Cuando hagamos doble click en cualquiera de ellas el resultado (el código del cliente, en este caso) se trasladará al textbox original. Además, si al pulsar F4 sólo se halla una coincidencia se trasladará automáticamente al textbox sin pasar por ningún pop-up (que mostraría un único resultado y por tanto sería absurdo).

Pongo un ejemplo con imágenes que se entenderá mejor. En un formulario tengo un textbox para introducir un país. Sin embargo yo los países los tengo codificados en una tabla de países de tal manera que España es el 1, Alemania el 2, Marruecos el 3 y así… Pues bien, en el formulario tengo lo siguiente:

Sólo que en realidad eso no es un textbox sino un Matchcode. Si quiero introducir el país Marruecos y sé que tiene el código 3 simplemente escribo 3:

Y cuando pulso Enter se me valida la introducción:

Si introdujera un código que no correspondiera a ningún país simplemente se vaciaría el cuadro. Pero si lo que ocurre es que no sé que Marruecos es el 3 puedo simplemente escribir una parte del nombre:

Y al pulsar F4 veré todos los posibles resultados:

Si hago doble click sobre la fila de Marruecos validaré la introducción nuevamente:

Pues bien, como tengo creada una .dll que incluye el formulario que hace de pop-up y toda la configuración para que funcione, en cualquier nuevo proyecto sólo tengo que importar dicha .dll y acto seguido puedo utilizar tantos Matchcodes como quiera.

Para crear la .dll:

1) Al crear el nuevo proyecto en VisualBasic.NET seleccionar Biblioteca de controles de Windows o Biblioteca de clases según lo que vayamos a construir. En cualquier caso, en las propiedades del proyecto nos aparecerá que el tipo de aplicación es una Biblioteca de clases.

2) Crear todos los controles y clases que corresponda hasta que tengamos el proyecto listo.

3) Generar (Volver a generar) el proyecto y con ello obtendremos ya el archivo .dll correspondiente. Concretamente en el directorio bin\Release de nuestro proyecto.

Ya tenemos pues el archivo nombreDeNuestraLibreria.dll. Ahora para poder utilizar dicha .dll y por tanto todos los controles y clases que contenga en cualquier aplicación lo que tenemos que hacer es lo siguiente:

1) En el nuevo proyecto, ir a Proyecto (en el menú superior), seleccionar Agregar referencia, ir a la pestaña Examinar y desde allí buscar el archivo .dll correspondiente y aceptar.

2) Abrir el Cuadro de herramientas y Agregar ficha.

3) Dentro de la ficha recién creada, seleccionar Elegir elementos.

4) En el cuadro que se nos abrirá, seleccionar Examinar y buscar la .dll que hemos creado en la primera fase.

Con ello tendremos disponibles en el Cuadro de herramientas los controles que incluya la librería-biblioteca que hemos creado y seleccionado. Si no incluye controles sino que es únicamente una librería de clases, podemos ahorrarnos los pasos 2, 3 y 4.

Finalmente si queremos utilizar las clases contenidas en la .dll desde cualquier otra clase, debemos incorporar al inicio una llamada de importación:

Imports nombreDeNuestraLibreria

Listos. ;-)

Formato no disponible en Snapshot de Access.

Hace un tiempo, cuando me dedicaba fundamentalmente a programar aplicaciones en Access, me encontré con un problema que irremediablemente todos mis clientes sufrían tarde o temprano. Es un problema que se refiere a las versiones XP y 2003 (ignoro si alguna más) y que ocurre cuando se quiere generar un informe en formato Snapshot. Este formato es muy útil para exportar informes en archivos .snp que se pueden visualizar desde prácticamente cualquier ordenador (y si no existe un visualizador gratuito de Microsoft).

Bien, el problema increíblemente reside en que al instalar Microsoft Access en español, la instalación crea una clave en el registro de Windows (regedit.exe) con la descripción en castellano:

snp,,1,Formato Snapshot (*.snp),0

Pero cuando el propio Access busca el valor para esa clave espera encontrarlo en inglés:

snp,,1,Snapshot Format (*.snp),0

Y al no encontrarlo muestra un mensaje diciendo que el formato en cuestión no está disponible. O sea, una chapuza monumental de los señores de Microsoft, sí.

Para solucionar esto simplemente hay que cambiar la entrada correspondiente en el registro de Windows para cambiar el primer valor por el segundo. Ojito, todos sabemos que puede ser crítico cambiar cosas en dicho registro, así que cada cual sabrá lo que hace (pero este cambio es bastante inofensivo, eso sí ;-) ). En cualquier caso la entrada a modificar es la siguiente para Access XP:

HKEY_LOCAL_MACHINE\_
     SOFTWARE\Microsoft\Office\10.0\Access\Report Formats

Y esta otra para Access 2003:

HKEY_LOCAL_MACHINE\_
     SOFTWARE\Microsoft\Office\11.0\Access\Report Formats

Y la entrada concreta a la que hay que cambiarle el valor es Snapshot Format.

A mis clientes solía enviarles un archivo .bat para facilitarles la modificación, que consistía simplemente en la instrucción:

REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
Office\11.0\Access\Report Formats" /v "Snapshot Format" /d "snp,
,1,Snapshot Format(*.snp),0" /f

Todo junto en la misma línea y sin espacios en los saltos de línea, ¿ok? Ah, y en donde pone 11.0 cambiarlo por 10.0 cuando la versión de Access sea la XP.

Por último añadir que en mi caso el uso del formato Snapshot era como paso intermedio para la creación de archivos PDF directamente desde Access sin necesidad de tener instalada ninguna impresora PDF, utilizando para ello la magnífica herramienta de Lebans.

Actualización: Iván aporta amablemente en los comentarios de esta misma entrada que en su caso para que pasara a funcionarle tuvo que cambiar el valor en castellano por este otro:

Formats\snp,,1,Snapshot Format (*.snp),0

Es decir, añadiéndole el Formats\ delante. Así que si estáis intentando resolver este problema y con las indicaciones del post no se os resuelve, no dejéis de probar también esta alternativa.

Copias de seguridad en MySQL con mysqldump.

En los pocos días de vida que tiene este blog es posible que haya mencionado ya que en el proyecto en el que estoy trabajando actualmente estamos utilizando una base de datos MySQL. Sí, lo sé, unas cuantas veces llevo ya… pero es que realmente va muy bien, estamos muy contentos con sus prestaciones hasta la fecha. Se está mostrando rápida y muy fiable… y todo ello con una licencia GPL, no lo olvidemos.

Bueno, el caso es que como es evidente, cuando trabajamos con cualquier sistema tenemos que tener muy bien previsto un método decente de copias de seguridad. En el caso de MySQL tenemos varias alternativas, pero la que a mí más me ha convencido ha sido la herramienta mysqldump. Es realmente sencilla de utilizar y tremendamente funcional. Su funcionamiento exacto con las decenas de opciones que admite se puede encontrar en el propio manual de referencia de MySQL. No requiere instalación alguna, simple y llanamente que dispongamos del archivo mysqldump.exe que conseguiremos sin problemas en la página oficial de MySQL y que si hemos instalado el servidor MySQL probablemente tendremos ya en nuestro equipo.

La herramienta mysqldump nos genera archivos con extensión .sql que incluyen -según hayamos configurado- las instrucciones necesarias para restaurar una base de datos entera -o todas las que tengamos en el servidor MySQL-, desde su creación hasta la adición de los datos pasando por la creación de las tablas. Realmente completito. Para una restauración sólo habría que hacer algo así en una línea de comandos (localhost o servidor según corresponda):

mysql -u root -p -h localhost < C:\archivo_backup.sql

No obstante lo que quiero explicar hoy es cómo he automatizado estas copias de seguridad que hago con mysqldump a través de un archivo batch. Expongo código y después lo comento.

@echo off

set path_mysqldump="C:\Program Files\MySQL\MySQL Server 5.0\bin"
set path_backups="\\99.24.13.29\BBDD\Backups"
set user=root
set password=mi_password
set host=99.24.13.26

if %time:~0,2% GEQ 10 goto :DespuesDeLas10

:AntesDeLas10
%path_mysqldump%\mysqldump --user=%user% --password=%password% 
      -h %host% --databases db_offers --single-transaction 
      > %path_backups%\backup_%date:~6,4%%date:~3,2%%date:~0,2%
        _0%time:~1,1%%time:~3,2%.sql
goto :Salir

:DespuesDeLas10
%path_mysqldump%\mysqldump --user=%user% --password=%password%
      -h %host% --databases db_offers --single-transaction 
      > %path_backups%\backup_%date:~6,4%%date:~3,2%%date:~0,2%
        _%time:~0,2%%time:~3,2%.sql
:Salir

Antes que nada, comentar que las líneas que comienzan por %path… aparecen aquí cortadas por temas de espacio (en píxeles, no en bytes ;-) ) en el blog, pero en realidad deben formar una sola línea que termina con .sql. Debe haber un espacio entre %password% y -h y después otro entre transaction y > %path_, pero en cambio la última linea va sin espacio (%date:~0,2%_0%time va todo seguido, ¿ok?).

Paso a explicarlo. Las primeras cinco líneas después del archiconocido @echo off son asignaciones de valores a las variables que después utilizaremos. Es la parte de configuración del archivo batch.

1) path_mysqldump contiene la ruta del directorio donde se encuentra el archivo mysqldump.exe.

2) path_backups contiene la ruta del directorio donde se almacenará la copia de seguridad, en este ejemplo he puesto una carpeta en un servidor.

3) user es el usuario con el que se ejecutará mysqldump. Parece una perogrullada, pero hay que tener en cuenta que debe disponer de permisos suficientes, aunque no es necesario que sea el usuario root (de hecho mucho mejor si no lo es, ya que así no quedará en un archivo batch visualizable el password del usuario root).

4) password es… sí, eso.

5) host es la máquina donde se encuentra el servidor MySQL que queremos backupear. Si es nuestro propio ordenador escribiremos localhost. A destacar que el servidor MySQL debe estar corriendo en el momento de lanzar el mysqldump.

Lo que sigue es ya la ejecución. En mi caso las copias de seguridad las quiero en formato backup_YYYYMMDD_HHMM con la fecha y la hora de esta manera, ya que así una simple ordenación alfabética de los archivos lleva a cabo también una ordenación por fechas. Para lograr eso hay que jugar con los date:~6,4 y similares y además hay que programar dos ramas en función de que sean antes o después de las 10 de la mañana, ya que hasta entonces necesito concatenar un 0 y un time:~1,1 para la hora con dos dígitos y a partir de las 10 me vale con time:~0,2. Podemos prescindir de hacer estas dos alternativas si vamos a lanzar siempre el batch a la misma hora y será más tardía que esas 10am de la madrugada. En cualquier caso para discernir entre si seguir un camino u otro utilizamos el…

if %time:~0,2% GEQ 10 goto :DespuesDeLas10

…donde GEQ equivale a lo que en lenguaje de programación normal y corriente diríamos >=.

Por último comentar que con esto obtenemos un archivo .bat que podemos ejecutar manualmente o programar para que se ejecute periódicamente a nuestro antojo. Por ejemplo con una simple tarea programada de Windows si no nos queremos complicar demasiado la vida.

Sobrevivir a los cáràctërês extraños.

Pues aquí está lo que comentaba en el post anterior: una pequeña y simple aplicación que sirve para traducir masivamente caracteres a sus correspondientes entidades seguras. Digo pequeña y simple porque realmente lo es: no está especialmente optimizada ni tiene muchas opciones que digamos. Apenas un textbox para introducir el texto a… mmmh “codificar”, y un botón para hacerlo. Nada más. Se me ocurre que se podría mejorar haciendo que sólo cambie un espacio por &#160; cuando haya más de uno consecutivo con el fin de no inundar de &#160; un texto normal convirtiéndolo así en ilegible. También se podría ofrecer al usuario de la aplicación la opción de parametrizar qué entidades quiere reemplazar y cuáles no, ya que yo por ejemplo sí que reemplazo < y > porque en realidad no utilizo la aplicación para páginas web, sino para los fragmentos de código de este propio blog. Pero está claro que alguien que utilice una aplicación así para páginas web no querrá reemplazar esos dos caracteres ni tampoco la barra /.

En fin, sólo sugiero posibles modificaciones que le puede hacer al programita quien lo desee.

Aquí dejo el link para descargarse el ejecutable correspondiente. No requiere instalación alguna de mínimo que es el programita. Eso sí, como está desarrollado en .NET requerirá el correspondiente Framework. Mucha gente lo tiene ya en su ordenador sin siquiera saberlo, y si no se puede descargar gratuitamente desde la página de Microsoft. No es más que una especie de máquina virtual más o menos análoga a la MVJ de Java.

El programa está basado sólo en un formulario y una clase auxiliar. He aquí el código del formulario principal:

'----------------------------------------------------------
' Author: Albert Mata (www.albertmata.net)
' Date: 20080601
'----------------------------------------------------------

'----------------------------------------------------------
' Main class to replace all problematic characters in a
' text to make it XML-valid.
'----------------------------------------------------------
Public Class frmXML

    '------------------------------------------------------
    ' Attributes.
    '------------------------------------------------------
    Private arrEntities As New ArrayList() 'of clsEntity

    '------------------------------------------------------
    ' Loads all desired entities translations.
    '------------------------------------------------------
    Private Sub frmXML_Load(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Me.Load
        Me.LoadEntities()
    End Sub

    '------------------------------------------------------
    ' Main method to make the replacements.
    '------------------------------------------------------
    Private Sub cmdMain_Click(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles cmdMain.Click
        Me.txtMain.Text = FullReplace(Me.txtMain.Text)
    End Sub

    '------------------------------------------------------
    ' Iterates all problematic entities and replaces them
    ' in the original text.
    '------------------------------------------------------
    Private Function FullReplace(ByVal FullString As String) _
    As String
        Dim Temp As String = FullString
        Dim IT As IEnumerator = arrEntities.GetEnumerator()
        While IT.MoveNext
            Dim Current As clsEntity = IT.Current
            Temp = Replace(Temp, Current.Bad, Current.Safe)
        End While
        Return Temp
    End Function

    '------------------------------------------------------
    ' Loads all entities to be replaced. Make here all the
    ' changes you like!
    '------------------------------------------------------
    Private Sub LoadEntities()
        'Avoiding "&#;" characters are replaced twice.
        arrEntities.Add(New clsEntity("&", "TEMP00001"))
        arrEntities.Add(New clsEntity("#", "TEMP00002"))
        arrEntities.Add(New clsEntity(";", "TEMP00003"))
        arrEntities.Add(New clsEntity("!", "&#33;"))
        arrEntities.Add(New clsEntity("""", "&#34;"))
        'HE ELIMINADO DECENAS DE ENTIDADES AQUÍ POR NO HACER
        'UN POST KILOMÉTRICO...
        'Avoiding "&#;" characters are replaced twice.
        arrEntities.Add(New clsEntity("TEMP00001", "&#38;"))
        arrEntities.Add(New clsEntity("TEMP00002", "&#35;"))
        arrEntities.Add(New clsEntity("TEMP00003", "&#59;"))
    End Sub

End Class

Y aquí la clase auxiliar que he utilizado:

'----------------------------------------------------------
' Author: Albert Mata (www.albertmata.net)
' Date: 20080601
'----------------------------------------------------------

'----------------------------------------------------------
' Auxiliar class representing an entity (its problematic
' character and the safe one).
'----------------------------------------------------------
Public Class clsEntity

    '------------------------------------------------------
    ' Attributes.
    '------------------------------------------------------
    Public Bad As String
    Public Safe As String

    '------------------------------------------------------
    ' Constructor method.
    '------------------------------------------------------
    Public Sub New(ByVal Bad As String, ByVal Safe As String)
        Me.Bad = Bad
        Me.Safe = Safe
    End Sub

End Class

Con esto se puede reconstruir la aplicación sin problemas, pero de todos modos si alguien está interesado en que le envíe los archivos fuente no tiene más que pedirlo por correo electrónico (mi dirección aparece en la página Acerca de mí…) y con gusto se los remitiré.

Cáràctërês extraños en XML.

Todo aquel que en algún momento haya programado (o incluso simplemente diseñado) alguna página web se habrá encontrado con los típicos problemas al poner tildes y caracteres extraños que algunos navegadores interpretan bien y otros, oh sorpresa, no lo hacen. A mí al menos me ha pasado. Y ya no digamos si en lugar de un documento HTML (o ASP o PHP) se trata de uno XML. En ese caso ya no hay duda posible: un documento XML no nos aceptará sin quejarse un carácter acentuado ni fuera de lo común. Total, que a menudo nos vemos obligados a recurrir a lo que se conoce como entidades, o lo que es lo mismo, en lugar de utilizar los caracteres de la primera columna utilizar los de la segunda:

  <     &#60;
  @     &#64;
  á     &#225;
  ñ     &#241;

También puede resultar útil si queremos poner más de un espacio consecutivo, ya que por defecto los navegadores igualan esto:

Albert     Mata

A esto otro:

Albert Mata

Para evitarlo podemos sustituir el espacio por su propia entidad (&#160;) tantas veces como deseemos y entonces se nos respetarán los espacios (de hecho así es como lo he hecho en el ejemplo anterior).

Total, que de vez en cuando me encuentro una vez más buceando en internet en busca de una tabla de entidades para buscar cuál era el código de una en concreto. Así que para ya no tener que hacerlo nunca más me he creado una propia donde aparecen las más habituales. La enlazo a continuación. ¡Si buscando como tantas veces antes he buscado yo das con ella, tienes mi permiso para guardarte una copia! ;-)

Tabla de entidades en HTML y XML.

Esta tabla puede servir como recurso puntual, pero está claro que andar sustituyendo caracteres por códigos en un documento completo puede ser de lo más engorroso. Queda pues pendiente para próximo post una pequeña aplicación que nos lo haga de manera automática…




Creative Commons License
El blog de Albert Mata by Albert Mata is licensed under a Creative Commons Reconocimiento-Compartir bajo la misma licencia 2.5 España License.