Cómo crear tu propio Servidor VPN con OpenVPN

OpenVPN es una solución multiplataforma que ha simplificado la configuración de VPNs en contra de soluciones más antiguas y difíciles de configurar como IPSec, haciéndolo más accesible para el público con menos experiencia en este tipo de tecnologías, permitiéndonos así crear nuestro propio servidor VPN.

Servidor

Lo primero que tenemos que hacer para crear nuestro propio servidor VPN es, obviamente, conseguir un servidor dónde realizar la instalación. Existen múltiples opciones en la nube y la mayoría de ellas, además, cuentan con imágenes para poder preinstalar OpenVPN en la creación de los servidores. Entre las posibles opciones que podemos utilizar encontramos los siguientes:

  • ScaleWay C1: 4 CPU, 2GB RAM, 50GB SDD, 1 IP, 200 Mbit/s ancho de banda -> 2.99€ al mes.
  • DigitalOcean: 1 CPU, 512 MB RAM, 20 GB SDD, 1 IP, 1 TB ancho de banda -> 5$ al mes.
  • Google Cloud Platform: Facturación por minutos de uso.
  • Amazon EC2: Facturación por minutos de uso.

También podríamos realizar la instalación en cualquier máquina Ubuntu con acceso a internet, como un ordenador antiguo o una Raspberry Pi. En estos casos deberemos realizar además tareas de redireccionamiento de tráfico en nuestra red local para que todo funcione correctamente.

Instalación

Para instalar OpenVPN en Ubuntu podemos utilizar apt-get mediante el siguiente comando:

sudo apt-get install openvpn easy-rsa

Después de esto, tenemos que extraer el fichero de ejemplo de configuración en la carpeta /etc/openvpn para incorporarlo así a nuestra configuración. Esto se puede hacer con un solo comando:

gunzip -c /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz > /etc/openvpn/server.conf

Una vez extraído editamos el fichero server.conf y realizamos algunos cambios.

sudo nano /etc/openvpn/server.conf

Las modificaciones más importantes a realizar son descomentar algunas líneas eliminando el caracter ‘;’. Para que el servidor VPN pase el tráfico web de los clientes a su destino, debemos descomentar la siguiente línea:

push "redirect-gateway def1 bypass-dhcp"

Es importante utilizar el servidor OpenDNS con los clientes conectados para la resolución de DNS. Esto puede ayudar a evitar que las solicitudes de DNS se escapen fuera de la conexión VPN. Sin embargo, es importante especificar también estos servidores DNS en los dispositivos de los clientes.. Para lograrlo necesitamos descomentar las siguientes líneas:

push "dhcp-option DNS 208.67.222.222"
push "dhcp-option DNS 208.67.220.220"

Para evitar que OpenVPN se ejecute en modo root descomentaremos las siguientes líneas que especifica el usuario nobody como el usuario por defecto a utilizar. Nobody es un usuario sin los privilegios por defecto de un usuario normal.

user nobody
group nogroup

El siguiente paso es configurar el reenvío de paquetes con sysctl. Esto le indicará al núcleo del servidor que reenvíe el tráfico de los dispositivos cliente hacia Internet. De lo contrario, el tráfico se detendrá en el servidor. Para lograrlo debemos ejecutar este comando:

echo 1 > /proc/sys/net/ipv4/ip_forward

Para hacer que este cambio sea permanente debemos descomentar la siguiente línea en el fichero /etc/sysctl.conf.

net.ipv4.ip_forward=1

Cortafuegos

De forma predeterminada, las reglas de reenvío de Linux eliminan todos los paquetes que intentan acceder a otra interfaz, por lo que necesitamos configurar un cortafuegos que permita el tráfico a través del puerto VPN 1194 y enviar por defecto las peticiones realizadas a Internet por los clientes. Para evitar el uso de IPTables vamos a usar ufw con los siguientes comandos para permitir conexiones SSH y 1194/udp:

ufw allow ssh
ufw allow 1194/udp

Después de eso, tenemos que modificar el archivo de configuración ufw para establecer la política de forward por defecto a ACCEPT. Este archivo es /etc/default/ufw y necesitamos modificar la línea:

DEFAULT_FORWARD_POLICY="ACCEPT"

Finalmente, debemos añadir unas cuantas reglas en el fichero /etc/ufw/before.rules para que OpenVPN funcione correctamente. Estas reglas son:

# START OPENVPN RULES
# NAT table rules
*nat
:POSTROUTING ACCEPT [0:0]
# Allow traffic from OpenVPN client to eth0
-A POSTROUTING -s 10.8.0.0/8 -o eth0 -j MASQUERADE
COMMIT
# END OPENVPN RULES

El resultado del fichero debe parecerse al de la Captura 1:

Captura 1: /etc/ufw/before.rules

Con los cambios realizados en ufw, podemos activarlo finalmente:

ufw enable

Generación de Certificados

El siguiente paso es configurar y generar los certificados que OpenVPN utilizará para cifrar el tráfico. Para este propósito vamos a utilizar los scripts de Easy RSA en nuestra carpeta de servidor.

Copiamos los scripts de generación de Easy RSA con el siguiente comando:

cp -r /usr/share/easy-rsa/ /etc/openvpn/

Creamos una carpeta para almacenar las claves:

mkdir /etc/openvpn/easy-rsa/keys

Ahora debemos modificar algunas variables en el fichero /etc/openvpn/easy-rsa/vars como la carpeta de EASY_RSA el valor del KEY_SIZE como podemos ver en la Captura 2:

Captura 2: /etc/openvpn/easy-rsa/vars

Antes de crear cualquier clave necesitamos configurar nuestro servidor como CA (Certificate Authority) para firmar nuestras claves con los siguientes comandos:

cd /etc/openpvn/easy-rsa
source ./vars # Carga el fichero vars editado previamente
./clean-all # Borra cualquier configuración y claves anteriores. Si ya hemos generado claves anteriormente, podemos saltarnos este comando.
./build-ca # Genera CA

Como podemos ver en la Captura 3 el sistema nos pedirá información sobre el certificado como el país, la provincia, el nombre, etc.

Captura 3: Creando Autoridad de Certificación

Con el siguiente comando generamos las claves del servidor para un servidor con nombre ‘server’ como podemos ver en la Captura 4.

./build-key-server server
Captura 4: Generando Clave para el Servidor

Ahora que tenemos las claves en la carpeta/etc/openvpn/easy-rsa/keys/ las copiamos en la carpeta /etc/openvpn:

cp /etc/openvpn/easy-rsa/keys/{server.crt,server.key,ca.crt} /etc/openvpn

Generamos además los parámetros Diffie-Hellman. Esto puede tardar varios minutos.

openssl dhparam -out /etc/openvpn/dh2048.pem 2048
Captura 5: Generando Diffie-Hellman

Finalmente, podemos iniciar nuestro servidor OpenVPN con el siguiente comando:

service openvpn start

Claves de Cliente

Una vez generadas las claves del servidor, podemos generar claves para cada uno de los diferentes clientes que queremos conectar a nuestra VPN. No existe ninguna relación entre la clave y el propio dispositivo por lo que podríamos generar una clave de cliente única y usarla en todos los dispositivos, eso sí, sólo un cliente podría conectarse a la VPN al mismo tiempo.

Para crear credenciales de autenticación independientes para los clientes (Captura 6) necesitamos usar el script build-key de la carpeta Easy RSA:

./build-key client1
Captura 6: Creando Clave para Client 1

Este comando va a generar las siguientes claves:

  • /etc/openvpn/easy-rsa/keys/client1.crt
  • /etc/openvpn/easy-rsa/keys/client1.key

Ahora que tenemos las claves, podemos proceder con la creación del archivo de configuración. Para ello partimos de uno de los archivos predeterminados de Easy RSA copiándolo en nuestra carpeta:

cp /usr/share/doc/openvpn/examples/sample-config-files/client.conf /etc/openvpn/easy-rsa/keys/client.ovpn

El siguiente paso requiere descargar a nuestro equipo local las llaves del cliente, los archivos de client.ovpn y ca.cart. Podemos hacer eso con SCP:

scp root@[SERVER-IP]:/etc/openvpn/easy-rsa/keys/client1.key .
scp root@[SERVER-IP]:/etc/openvpn/easy-rsa/keys/client1.crt .
scp root@[SERVER-IP]:/etc/openvpn/easy-rsa/keys/client.ovpn .
scp root@[SERVER-IP]:/etc/openvpn/easy-rsa/keys/ca.crt .

Una vez que hayamos descargado los archivos en nuestro ordenador, podemos crear un perfil OpenVPN unificado mediante la edición del archivo de plantilla client.ovpn e incluir la Autoridad de Certificación de los servidores, el certificado de clientes y su clave. También necesitamos hacer algunas modificaciones en el archivo:

  • Descomentar user nobody y group nogroup
  • Cambiar la línea remote my-server-1 1194 line con remote [SERVER-IP] 1194
  • Comentar las líneas de la Captura 7.
Captura 7: Fichero client.ovpn

Para combinar los archivos individuales en el único perfil unificado, el contenido de los archivos ca.crt, client1.crt y client1.key se pegan directamente en el perfil de .ovpn utilizando una sintaxis básica similar a XML. El código XML al final del archivo debe tener este formato:

<ca>
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
</ca>
<cert>
Certificate:
...
-----END CERTIFICATE-----
...
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
</key>

Clientes OpenVPN

Para poder usar el archivo .opvn de configuración y conectarnos remotamente a nuestro servidor VPN podemos hacer uso de diferentes clientes VPN disponibles.

OpenVPN Connect ofrece versiones para PC, dispositivos Android e iOS. En nuestro caso vamos a utilizar otro cliente VPN para Mac OSX, llamado Tunnelblick. Una vez instalado solo necesitamos abrir nuestro archivo de perfil .ovpn y Tunnelblick configurará automáticamente la conexión VPN para que podamos utilizarla como podemos ver en Captura 8.

Captura 8: Tunnelblick Conectado

Podemos asegurarnos de que estamos conectados a la VPN usando cualquier servicio web para obtener nuestra IP pública como podemos ver en Captura 9 que detecta que todo está bien configurado.

Captura 9: Comprobando mi IP
1

XSLT en Python: lxml

En el artículo Transformaciones con XSLT vimos una introducción al lenguaje de transformaciones, sin embargo, no explicamos como utilizar debidamente esta tecnología. En este artículo explicaremos como realizar transformaciones XSLT en Python haciendo uso de la biblioteca lxml y veremos como montar un servidor web con Flask para hacer uso de nuestro procesador XSLT desde cualquier lugar.

Introducción

Tanto los documentos XML como las hojas de estilo en XSLT son completamente independientes de la plataforma que se vaya a utilizar para realizar las transformaciones. Podemos enlazar una hoja de estilos XSLT a un documento XML de forma permanente añadiendo un elemento <?xml-stylesheet ..?> justo después de la declaración XML como podemos ver en el Fragmento de Código 1. De esta forma, al abrir el documento con un navegador web, éste aplicará las transformaciones pertinentes de forma automática y podremos comprobar el resultado.

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="hojaEstilos.xslt"?>
<Coleccion>
	<documento>
		<titulo>El Titulo</titulo>
		<autor>Miguel S. Mendoza</autor>
		<fecha>1984</fecha>
	</documento>
	<documento>
		<titulo>El Otro Titulo</titulo>
		<autor>Marta S. Román</autor>
		<fecha>2016</fecha>
	</documento>
</Coleccion>

Sin embargo, si lo que queremos es generar documentos independientes es necesario utilizar un procesador XSLT que se encargue de aplicar al documento XML las reglas de transformación incluidas en la hoja de estilo XSLT y genere un documento final. La transformación se realiza de la siguiente forma:

  1. El procesador analiza el documento y construye el árbol del documento.
  2. El procesador recorre todos los nodos desde el nodo raíz, aplicando a cada nodo una plantilla, sustituyendo el nodo por el resultado.
  3. Cuando el procesador ha recorrido todos los nodos, se ha terminado la transformación.

Afortunadamente existen procesadores XSLT para casi todos los lenguajes de programación, como Perl, C, Java, etc. En nuestro caso hemos optado por implementar el procesador haciendo uso del lenguaje Python y su librería libxml.

Python

Python es un lenguaje muy potente y fácil de aprender. Maneja eficientes estructuras de datos de alto nivel y cuenta con un enfoque simple pero efectivo de programación orientada a objetos. Posee una sintaxis muy elegante y tipado dinámico, que junto con su carácter interpretado lo convierte en un lenguaje ideal para desarrollo rápido de aplicaciones en innumerables áreas. Python se encuentra en un gran número de aplicaciones web y de escritorio, por lo que es un lenguaje de aprendizaje casi obligatorio para un desarrollador actual, ya que permite resolver problemas de una forma muy eficiente en un corto espacio de tiempo. Además, cuenta con un repositorio de paquetes que permiten extender la funcionalidad de las aplicaciones de una forma sencilla, muy similar a como se hace con NodeJS.

El gestor de paquetes recomendado para Python se denomina pip, y se utiliza de un modo similar a npm o apt-get. Con él, podemos instalar el paquete lxml que nos permitirá procesar documentos XML y HTML de forma sencilla.

pip install lxml

En el Fragmento de Código 2 podemos ver cómo aplicar una plantilla XLST a un documento XML como el que vimos en el Fragmento de Código 1.

import lxml.etree as ET

def transforma_documento(XML):
  dom = ET.fromstring(XML)
  transform = ET.XSLT(ET.parse('hojaEstilos.xslt'))
  nuevodom = transform(dom)
  resultado = ET.tostring(nuevodom, pretty_print=True)
  return resultado

Como podemos observar, aunque no hayamos visto el lenguaje Python hasta ahora, simplemente leyendo el código entendemos perfectamente lo que hace. Inicialmente se lee el fichero de entrada que se pasa como parámetro al método transforma_documento. Seguidamente se utiliza el contenido del fichero para obtener el árbol de nodos del documento XML y almacenarlos en la variable dom. A continuación se obtiene un objeto de transformación leyendo el fichero de hoja de estilos “hojaEstilos.xslt” en el que se han definido las transformaciones, cuyo contenido podemos ver en el Fragmento de Código 3. Se aplican las transformaciones en el árbol de nodos y se obtiene un nuevo árbol de documento que podemos convertir en una cadena de texto para devolverla como resultado para, si quisiéramos, escribirla en un nuevo fichero obteniendo finalmente nuestro fichero transformado.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:template match="/">
    <html>
      <body>
        <h1>Los Documentos</h1>
        <table>
          <tr>
            <th>Titulo</th>
            <th>Autor</th>
          </tr>
          <xsl:for-each select="Coleccion/documento">
            <tr>
              <td><xsl:value-of select="titulo" /></td>
              <xsl:choose>
                <xsl:when test="fecha > 2010">
                  <td style="color: grey">
                    <xsl:value-of select="autor" />
                  </td>
                </xsl:when>
                <xsl:otherwise>
                  <td style="color: green">
                    <xsl:value-of select="autor" />
                  </td>
                </xsl:otherwise>
              </xsl:choose>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Así, si abriéramos el fichero XML en un navegador obtendríamos un resultado parecido al que podemos observar en la siguiente captura.

Transformación XSLT de documento XML a contenido HTML

Flask

Python puede instalarse en cualquier sistema operativo actual. Sin embargo, si queremos utilizar código en este lenguaje desde un entorno web se hace necesario utilizar una infraestructura apropiada que permita desarrollar aplicaciones web o servicios sin necesidad de manejar detalles de bajo nivel como protocolos o manejo de procesos e hilos.

Entre las numerosas infraestructuras que podríamos utilizar nos hemos decantado por Flask, una infraestructura web minimalista que nos permite realizar peticiones a nuestro código de transformación de forma sencilla mediante el desarrollo de una API REST. En el Fragmento de Código 4 podemos ver un ejemplo sencillo de cómo podríamos utilizar nuestro código de transformación visto en el Fragmento de Código 2 utilizándolo como una biblioteca denominada publisher, que se corresponde con el nombre del fichero en el que hemos almacenado nuestras funciones transformadoras.

import publisher
from flask import Flask, render_template, request
app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route("/transform", methods=['POST'])
def transform():
	file = request.files['uploadfile']
	XML = file.read()
	resultDocument = publisher.transforma_documento(XML)
	return resultDocument

if __name__ == "__main__":
	app.run()

Para poder gestionar correctamente el servidor web, utilizamos las funciones render_template y request, con el objetivo de devolver ficheros estáticos, y de manejar las peticiones respectivamente. Como podemos observar es un código relativamente sencillo que nos permitirá obtener un documento transformado a partir de un documento que podemos enviar mediante un formulario HTML como el del Fragmento de Código 4.

<form action="/transform" method="post">
    <input type="file" name="uploadfile" />
    <input type="submit" value="Transformar" />
</form>

Podéis encontrar el código fuente utilizado en este artículo en GitHub.

Conclusión

Utilizar Flask nos permitirá, entre otras cosas, poder mejorar la modularidad de un sistema en diferentes máquinas en caso de que fuera necesario, es decir, podríamos perfectamente instalar nuestro servicio de transformaciones en un servidor, y la aplicación web que utilice éste en otro distinto. Sin embargo, si quisiéramos utilizar tanto Flask como NodeJS en una misma máquina tendremos que utilizar aplicaciones intermedias como Virtual Environment  para mantener la aplicación Flask ejecutándose como servicio, uWSGI como interfaz web entre servidores web y el servicio creado con Virtual Environment y NGINX como servidor web para controlar todas las peticiones entre la aplicación web y el servicio de transformaciones en Python.

1

Transformaciones con XSLT

XSLT es un lenguaje que permite transformar documentos XML en cualquier otro tipo documento. Este lenguaje busca dar solución al problema de expresar información estructurada de la forma más abstracta y reutilizable posible. Como veremos más adelante, este lenguaje resulta ideal para innumerables propósitos, ya que permite realizar transformaciones con XSLT de documentos de publicaciones con contenido básico en cualquier tipo de formato de presentación, como un micro sitio con contenido adaptativo a dispositivos móviles, un PDF, o un libro en formato EPUB listo para distribuirse a través de comercios de libros electrónicos.

Comenzaremos explicando los conceptos básicos relacionados con el lenguaje para posteriormente describir la forma en la que lo vamos a utilizar, así como el tipo de procesador XSLT que implementaremos.

Elementos en XSLT

XSLT se nutre de dos fuentes principales para funcionar: El documento XML a transformar y la plantilla XSLT de definición de las transformaciones. Estos dos documentos son utilizados por el procesador XSLT para generar un nuevo documento. La Figura 1 muestra la relación entre estos tres elementos.

Documentos en XSLT
Figura 1: Documentos en XSLT

Tanto el documento XML como la hoja de estilos deben ser documentos XML válidos y bien formados, lo que significa que una hoja de estilos se puede utilizar para transformar otra hoja de estilos.

Documentos XML

Un documento XML contiene elementos estructurados en forma de árbol definidos por medio de etiquetas rodeadas de corchetes angulares y barras (<,> y /). Para que un documento XML esté bien formado se deben seguir unas reglas básicas de estructuración de contenido. Estas reglas son las siguientes:

  • Todo documento XML debe estar contenido en un único elemento. Es decir, debe existir un elemento raíz que contenga a todos los demás elementos del documento.
  • Todos los elementos deben estar anidados. Si se abre un elemento dentro de otro elemento, el cierre del primer elemento debe aparecer antes del cierre del elemento en el que está contenido, es decir, un elemento no puede contener el cierre de un elemento si no contiene además su apertura.
  • Todos los valores de los atributos deben encontrarse entre comillas.
  • Las etiquetas XML son sensibles a mayúsculas y minúsculas. Esto es, las etiquetas, “<etiqueta></etiqueta>” y “<Etiqueta></Etiqueta>” son elementos distintos, por lo que “<etiqueta></Etiqueta>” sería un elemento mal formado.
  • Todos los elementos de cierre son obligatorios. Si creamos un elemento <img> que contiene otros elementos o contenido de texto, debe incluirse además su etiqueta de cierre </img>.
  • Si un elemento está vacío puede introducirse la marca de cierre en la etiqueta de apertura. Es decir, <etiqueta edad=”12”></etiqueta> y <etiqueta edad=”12”/> son elementos idénticos.

Para que además de bien formado, un documento XML sea válido, debe cumplir con esquemas predefinidos que establecen otras restricciones al contenido. Estos esquemas son las Definiciones de Tipo de Documento y los Esquemas XML, que vienen a ser metalenguajes utilizados para definir las características de un vocabulario XML (Doug Tidwell, XSLT.: O’Reilly, 2008). Con estos esquemas podemos definir, por ejemplo, las etiquetas válidas en un documento, así como los atributos que pueden contener. Estos esquemas XML se definen utilizando XML, por tanto, podríamos definir XML como un metalenguaje.

Por tanto, podemos tener un documento bien formado en XML, pero que no sea válido por no cumplir un esquema definido, pero no podemos tener un documento válido que no esté bien formado. En el Fragmento de Código 1 podemos ver un ejemplo de documento XML válido.

<?xml version="1.0" encoding="UTF-8"?>
<Coleccion>
	<documento>
		<titulo>El Titulo</titulo>
		<autor>Miguel S. Mendoza</autor>
		<fecha>2008</fecha>
	</documento>
	<documento>
		<titulo>El Otro Titulo</titulo>
		<autor>Catalina S. Román</autor>
		<fecha>2012</fecha>
	</documento>
</Coleccion>

Cabe señalar que aunque existen similitudes entre XML y HTML, no son exactamente lo mismo. Como hemos señalado anteriormente, XML se trata de un metalenguaje que permite definir estructuras XML que definen documentos, mientras que HTML se trata de un lenguaje propiamente dicho, ya que está definido mediante un DTD o esquema XML. Esto provoca que en HTML podamos encontrar estructuras que, aunque se muestran perfectamente en un navegador, rompen las reglas de documentos bien formados definidas anteriormente. Véanse por ejemplo las etiquetas <br>.

En principio, un documento HTML debe “limpiarse” antes de ser utilizado en una transformación XSLT para asegurarse de que es un documento válido. Por suerte podemos realizar este tipo de tareas de una forma sencilla como veremos más adelante.

Hojas de Estilo XSLT

Las hojas de estilo escritas en lenguaje XSLT se basan en patrones o reglas que identifican los elementos del documento a transformar y muestran cómo deben presentarse esos elementos en el documento final.

El elemento raíz de una plantilla XSLT es <xsl:stylesheet> o <xsl:transform> que acostumbra a ir acompañado de los atributos xmlns que define el entorno de nombres de etiquetas que se van a utilizar, y el atributo version, que define la versión del esquema en el que vamos a basar las reglas de transformación.

Dentro de una hoja de estilos podemos encontrar otros elementos como:

  • <xsl:output>: Permite definir algunos parámetros del formato de salida, como la codificación, el lenguaje del documento a transformar, la indentación y demás atributos.
  • <xsl:variable>: Elemento que va a poder ser accesible en todo el documento de una forma más sencilla sin tener que definirlos cada vez que se vayan a utilizar.
  • <xsl:template>: elementos principales de una hoja de estilos ya que definen las transformaciones propiamente dichas. Poseen un atributo denominado match cuyo valor define la expresión que identificará a los elementos del documento XML que se deben transformar con este elemento.

Dentro de un elemento template se encuentra el contenido de la plantilla que define cómo convertir el contenido de la etiqueta XML del documento original. Para definir esta plantilla se pueden utilizar otros elementos de hoja de estilos que permiten realizar diferentes funciones. Entre esos elementos destacan los siguientes:

  • <xsl:value-of>: Establece que en el punto en el que se sitúa este elemento debe aparecer el valor del elemento que se corresponda con una expresión concreta que se establece con el atributo select.
  • <xsl:for-each>: Permite definir un bucle que se repetirá para todos los elementos que cumplan con una condición establecida en el atributo select del mismo.
  • <xsl:choose>: Utilizado en conjunción de los elementos <xsl:when> y <xsl:otherwise> se utiliza para crear estructuras condicionales que nos permitirá establecer un resultado u otro dependiendo de las reglas establecidas en el atributo test del elemento <xsl:when>

Partiendo del documento XML del Fragmento de Código 1, en el Fragmento de Código 2 podemos ver un ejemplo de hoja de estilos XSLT que transformará el documento XML en código HTML.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:template match="/">
    <html>
      <body>
        <h1>Los Documentos</h1>
        <table>
          <tr>
            <th>Titulo</th>
            <th>Autor</th>
          </tr>
          <xsl:for-each select="Coleccion/documento">
            <tr>
              <td><xsl:value-of select="titulo" /></td>
              <xsl:choose>
                <xsl:when test="fecha > 2010">
                  <td style="color: grey">
                    <xsl:value-of select="autor" />
                  </td>
                </xsl:when>
                <xsl:otherwise>
                  <td style="color: green">
                    <xsl:value-of select="autor" />
                  </td>
                </xsl:otherwise>
              </xsl:choose>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Como consecuencia de aplicar la transformación obtendremos el resultado en código HTML que podemos ver en el Fragmento de Código 3.

<html>
  <body>
    <h1>Los Documentos</h1>
    <table>
      <tr>
        <th>Titulo</th>
        <th>Cuerpo</th>
      </tr>
      <tr>
        <td>El Titulo</td>
        <td style="color: green">Miguel S. Mendoza</td>
      </tr>
      <tr>
        <td>El Otro Titulo</td>
        <td style="color: grey">Catalina S. Román</td>
      </tr>
    </table>
  </body>
</html>

Esto es solo un pequeño ejemplo de las posibilidades que nos brindan las transformaciones en XSLT ya que existen muchos más elementos que se pueden utilizar dentro de una hoja de estilos XSLT para definir transformaciones.

XPath

Como hemos podido observar hasta ahora, tanto los documentos como las plantillas son documentos XML. Sin embargo, para poder navegar y encontrar elementos XML concretos dentro de un documento debemos utilizar otro lenguaje que nos permita identificar elementos que cumplan ciertos criterios de forma sencilla. En XSLT, el lenguaje que permite realizar estas acciones se denomina XPath y lo hemos podido ver en ejemplos anteriores en los atributos match, test, y select de los elementos <xsl:template>, <xsl:when> y <xsl:value-of> respectivamente.

En XPath todo objeto XML es tratado como un nodo. A su vez, se distinguen siete tipos de nodos:

  • Elemento Raíz o Documento: Elemento principal de un documento XML dentro del cual se definen el resto de elementos.
  • Elementos: Objetos XML encerrados entre corchetes angulares. Por ejemplo: <autor></autor>.
  • Atributo: Propiedad de un elemento cuyo valor se encuentra entrecomillado: Por ejemplo: edad=”23”.
  • Texto: Cualquier cadena de texto que se encuentre entrecomillada en un atributo, o dentro de un elemento. Tomando como ejemplo el elemento <autor>Miguel</autor>, el nodo texto sería el valor “Miguel”.
  • Comentario: Cadena de texto situada dentro de un elemento entre las cadenas de caracteres “<!–» y “–>”. Por ejemplo: <!– Esto es un comentario –>.

Para relacionar nodos, XPath define cinco parentescos entre ellos:

  • Padre: Nodo en el que está contenido un nodo concreto. Todo elemento o atributo posee un nodo padre.
  • Hijo: Nodos contenidos en un nodo concreto.
  • Hermano: Nodos que poseen el mismo padre.
  • Ancestro: Nodo padre de un nodo, así como el nodo padre del nodo padre, etc., es decir, todos los elementos padre desde un nodo concreto hasta el raíz.
  • Descendientes: Nodos hijo de un nodo, así como los hijos de cada nodo hijo, etc., es decir, todos los elementos hijo desde un nodo concreto.

XPath define una sintaxis para definir partes en un documento XML. Utiliza expresiones parecidas a las rutas para navegar entre los elementos y contiene una biblioteca de funciones que se utilizan para distinguir elementos.

Las expresiones más comunes en XPath podrían resumirse en las siguientes:

  • nombrenodo: Selecciona todos los nodos con el nombre “nombrenodo”.
  • /: Selecciona nodos desde el nodo raíz.
  • //: Selecciona nodos en el documento desde el nodo actual que cumplan con la selección independientemente de dónde se encuentren.
  • .: Selecciona el nodo actual.
  • ..: Selecciona el nodo padre.
  • @: Selecciona atributos.

Con estas expresiones se pueden construir rutas de navegación como las siguientes:

  • documento: Selecciona todos los nodos con el nombre “documento”.
  • /Coleccion: Selecciona el nodo raíz “Coleccion”.
  • Coleccion/documento: Selecciona todos los elementos “documento” hijos de “Colección”
  • //documento: Selecciona todos los elementos “documento” que se encuentren en el documento sin importar su lugar.
  • Coleccion//documento: Selecciona todos los elementos “documento” descendientes del elemento “Coleccion”.
  • //@edad: Selecciona todos los atributos “edad” del documento.

Además de seleccionar nodos en relación a su posición en el documento, podemos seleccionar nodos concretos dependiendo de ciertas características. Para encontrar nodos que contentan ciertos valores o cumplan ciertas propiedades utilizaremos los denominados “predicados”. Los predicados se incluyen en las expresiones XPath entre corchetes. A continuación podemos ver algunos ejemplos:

  • /Coleccion/documento[1]: Selecciona el primer elemento documento hijo del elemento raíz Coleccion.
  • /Coleccion/documento[last()-1]: Selecciona el penúltimo elemento documento hijo del elemento raíz Coleccion.
  • /Colección/documento[fecha > 2012]/titulo: Selecciona los elementos titulo hijos de los elementos documento, a su vez hijos del elemento raíz Coleccion cuyo valor del elemento fecha sea superior a 2012.

Se pueden anidar expresiones haciendo uso del operador “|”. Por ejemplo:

  • //titulo | //autor : Selecciona todos los nodos titulo Y autor del documento.

Por otro lado, se pueden utilizar comodines en las expresiones XPath, lo que nos permite seleccionar nodos dependiendo de expresiones múltiples. Los comodines más habituales son:

  • *: Selecciona cualquier nodo elemento.
  • @*: Selecciona cualquier nodo atributo.
  • node(): Selecciona cualquier nodo de cualquier tipo.

Finalmente, cabe destacar que tanto XSLT como XPath cuentan con una biblioteca de funciones que les permite seleccionar elementos de una forma mucho más eficiente. Podemos encontrar un listado de las mismas en la propia especificación.

Conclusión

En definitiva, XSLT es un lenguaje extremadamente potente que nos permite realizar transformaciones de una forma sencilla y rápida. Aunque la curva de aprendizaje pueda ser un poco complicada, una vez se han realizado unas cuantas transformaciones y se han estudiado correctamente las capacidades de selección de nodos con XPath las posibilidades se abren a cada paso.

1

Clean HPub Specification 1.0

En el siguiente artículo se detallan las características que debe poseer un paquete CHPub (Clean HPub) para ser transformado en cualquier sistema de publicación. Basado en la especificación de HPub de Alessandro Morandi para Baker Framework pretende simplificar el mismo y delegar la tarea del diseño y la forma de presentación en el medio de transformación.

Especificación

  1. Una publicación CHPub es un conjunto de ficheros empaquetados en un fichero ZIP con el formato: [NOMBRE].zip
  2. Una publicación está formada por diferentes apartados. Cada apartado constará de un fichero HTML nombrado con el formato [NOMBRE]_A[XX].html siendo NOMBRE el nombre de la publicación, y XX un número entre 00 y 99.
  3. Los recursos adicionales de cada apartado como imágenes, vídeos, etc. serán localizados en un directorio de nombre [NOMBRE]_A[XX], y referenciados en su correspondiente fichero de apartado de forma relativa.
    • Ej.: <img src="CHPub_A02/img/img01.jpg" alt="Lorem"/>
  4. Incluye un fichero denominado «book.json» con metadatos sobre la publicación como se describe en el apartado book.json.
  5. El código HTML que se utilizará en los ficheros deberá ser lo más sencillo posible, evitando utilizar CSS y limitando el uso de etiquetas más allá de las que se especifican en el correspondiente apartado etiquetas.
  6. Se permite el uso de etiquetas personalizadas relacionadas con extensiones definidas en el apartado extensiones.
  7. Incluyendo una imagen en la raíz de la publicación con el nombre cover.jpg se podrá utilizar en las portadas de las publicaciones que se generarán. Se recomienda una resolución de 1920×1080.

Estructura

├── CHPub
│   ├── cover.png
│   ├── book.json
│   ├── CHPub_A1.html
│   ├── CHPub_A1
│   │   ├── image
│   │   │   ├── cover.png
│   │   │   ├── image01.png
│   │   ├── video
│   │   │   ├── video1.mp4
│   │   │   ├── video2.mp4
│   │   ├── audio
│   │   │   ├── audio1.mp3
│   ├── CHPub_A2.html
│   ├── CHPub_A2
│   │   ├── image
│   │   │   ├── cover.png
│   │   │   ├── image01.png
│   │   │   ├── image02.png

book.json

El fichero book.json se situará en la raíz de la publicación y deberá contener un objeto JSON bien formado. Ejemplo de fichero book.json:

{    
    "title": "Lorem Ipsum",
    "author": ["Miguel S. Mendoza", "Marta S. Román"],
    "creator": ["Gabriel S. Román"],
    "editor":"NetRunners",
    "date": "2016-04-29",
    "url": "http://www.netrunners.es/",
    "description":"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
    "keywords":"specification, hpub, publication",
    "contents": [
        "CHPub_A01",
        "CHPub_A02",
        "CHPub_A03",
        "CHPub_A04"
    ]
}

Parámetros

  • title (string): Título de la publicación.
  • author (array): Autor/es de la publicación. Persona/s generadoras de los contenidos independientemente del formato. Un string con el nombre de cada autor diferente.
  • creator (array): Creador/es de la publicación. Persona/s creadoras de la publicación en CHPub. Un string con el nombre de cada creador diferente.
  • editor (string): Nombre del editor de la publicación.
  • date (string): Fecha de la publicación en formato YYYY-MM-DD.
  • url (string): URL de la publicación o de la editorial.
  • description (string): Descripción de la publicación.
  • keywords (string): Palabras clave relacionadas con la publicación.
  • contents (array): Lista de todos los ficheros de apartados de la publicación en el orden de visualización.

Etiquetas

A diferencia de la especificación original de HPub, en Clean HPub se pretenden crear publicaciones con el menor diseño posible, centradas en el contenido. De esta forma el diseño dependerá exclusivamente del sistema de transformación que reciba la publicación como entrada y genere una publicación en otro formato, ya sea PDF, EPub, etc. La regla es simple: Se permite cualquier etiqueta del estándar HTML4 siempre y cuando no se utilicen el atributo style. Más adelante, en el apartado extensiones, se detallarán etiquetas adicionales relacionadas con los componentes extra que se podrán utilizar.

Relación de etiquetas HTML válidas

Para limitar el diseño se especifican a continuación las etiquetas HTML que se permitirán por defecto en lo ficheros HTML de cada apartado para una publicación de libro normal.

  • html: Etiqueta contenedora de las etiquetas head y body.
  • head: En ella se incluirán algunos metadatos permitidos para cada apartado. Por el momento las etiquetas permitidas son:
    • title: Título del apartado.
  • body: Etiqueta contenedora del resto de etiquetas del apartado.
  • h1: Título del apartado. Solo debe existir uno.
  • hX: Títulos de subapartados siendo X el nivel de ordenación. Se debe respetar la estructura de niveles de subarpartados:
    • No se debe introducir un h3 si antes no se ha introducido un h2.
    • Todo h3 se considera subapartado del h2 inmediatamente anterior, del mismo modo que un h4 lo hará para un h3, y así sucesivamente.
  • p: Párrafos de contenido.
  • blockquote: Se utiliza para hacer referencia a una cita externa.
  • strong: Texto en negrita.
  • u: Texto subrayado.
  • i: Texto en cursiva.
  • a: Hiperenlace. Los siguientes atributos son obligatorios:
    • href: Ruta del enlace.
    • title: Descripción del enlace.
  • ul: Lista no numerada. Para cada elemento de la lista se utilizará la etiqueta li.
  • ol: Lista numerada. Para cada elemento de la lista se utilizará la etiqueta li.
  • img: Imágenes. Los siguientes atributos son obligatorios:
    • src: Ruta relativa a la imagen. Las imágenes deberán encontrarse en la carpeta del apartado.
    • alt: Descripción de la imagen.

Extensiones

Además de las etiquetas HTML básicas descritas anteriormente, y para aumentar la versatilidad de las publicaciones, se han creado etiquetas específicas para diferentes tipos de material. Algunas de ellas están relacionadas con extensiones externas HTML5 que cambiarán dependiendo de la transformación a realizar.

Etiquetas

  • gallery: Esta etiqueta se utilizará para englobar a un conjunto de etiquetas img y mostrarlas como una galería de imágenes.

    <gallery>
      <img src="CHPub_A1/images/imagen01.jpg" alt="Caption 01" />
      <img src="CHPub_A1/images/imagen02.jpg" alt="Caption 02" />
      <img src="CHPub_A1/images/imagen03.jpg" alt="Caption 03" />
    </gallery>
  • tooltip: Permite mostrar un pequeño tooltip de ayuda sobre un texto concreto. Útil para notas de autor o pequeñas aclaraciones.

    <p>Proin <tooltip title="Tooltip Text.">convallis cursus</tooltip> massa.</p>
  • video: Incluye un vídeo en la publicación. El vídeo podrá ser un fichero en la carpeta del apartado o un identificador de vídeo externo como Vimeo o Youtube. En los siguientes ejemplos los comentarios no son necesarios, solo se añaden para que se identifique el identificador de cada vídeo externo:

    <video src="CHPub_A01/video/vide01.mp4">Video description.</video>
    <-- https://vimeo.com/119787432 -->
    <video type="vimeo" src="119787432">Video description.</video>
    <-- https://www.youtube.com/watch?v=bYE67_Wfizw -->
    <video type="youtube" src="bYE67_Wfizw">Video description.</video>
  • videogallery: Genera una galería con los vídeos que se introduzcan entre la etiqueta de apertura y cierre:

    <videogallery>
      <source title="Video Title" src="CHPub_A01/video/vide01.mp4">Video description.</source>
      <source title="Video Title" type="vimeo" src="119787432">Video description.</source>
      <source title="Video Title" type="youtube" src="bYE67_Wfizw">Video description.</source>
    <videogallery>
  • audio: Etiqueta de audio que hará referencia a un fichero en la carpeta del apartado.

    <audio src="CHPub_A1/audio/audio01.mp3">Audio Description 01.</audio>
  • columns: Estructura el contenido en dos o más columnas. Cada columna se representa con la etiqueta column.

    <columns>
      <column>
        <p>Ut dictum lectus in turpis lacinia, et blandit metus ornare.</p>
      </column>
      <column>
        <p>Nullam at tortor nec nibh interdum laoreet.</p>
      </column>
    </columns>

Ejemplo de Apartado

<html>
    <head>
        <title>Lorem Ipsum</title>
    </head>
    <body>
        <h1>Neque porro quisquam est qui dolorem</h1>
        <p>Lorem ipsum dolor sit <strong>amet</strong>, consectetur adipiscing elit.</p>
        <ul>
            <li>Proin</li>
            <li>luctus</li>
            <li>libero</li>
        </ul>
        <img src="CHPub_A1/images/imagen01.jpg" alt="Image Title">
        <p>In hac habitasse platea <u>dictumst</u>. Interdum et malesuada fames ac ante ipsum
            primis in <i>faucibus</i>.</p>
        <video type="vimeo" src="119787432">Video Decription 01.</video>
        <h2>Ipsum quia dolor sit amet</h2>
        <p>Pellentesque hendrerit leo quis mi 
            <a href="http://www.netrunners.es" title="Link Description">lobortis tincidunt</a>. 
            Proin quis felis eget orci <tooltip title="Tooltip Text.">convallis cursus</tooltip> 
            ac eget massa.</p>
        <ol>
            <li>odio</li>
            <li>lectus</li>
        </ol>
        <video type="youtube" src="bYE67_Wfizw">Video Decription 02.</video>
        <h3>Integer orci leo</h3>
        <blockquote>Donec congue fermentum viverra.</blockquote>
        <columns>
            <column>
            <p>Ut dictum lectus in turpis lacinia, et blandit metus ornare.</p>
            </column>
            <column>
            <p>Nullam at tortor nec nibh interdum laoreet.</p>
            </column>
        </columns>
        <video src="CHPub_A1/video/video01.mp4">Video Decription 03.</video>
        <h2>Dolor sit amet</h2>
        <p>Donec sollicitudin diam neque, non lobortis dolor blandit in.</p>
        <h3>In hac habitasse platea dictumst</h3>
        <p>Morbi nunc tortor, pellentesque quis augue vel, bibendum venenatis tellus.</p>
        <audio src="CHPub_A1/audio/audio01.mp3">Audio Description 01.</audio>
        <p>Mauris eget augue vitae ipsum hendrerit finibus convallis et urna.</p>
        <gallery>
            <img src="CHPub_A1/images/imagen02.jpg" alt="Caption 01">
            <img src="CHPub_A1/images/imagen03.jpg" alt="Caption 02">
            <img src="CHPub_A1/images/imagen04.jpg" alt="Caption 03">
        </gallery>
    </body>
</html>

Meta

1