Fullmenu null

 

10 May 2018

Para este post vamos a necesitar los siguientes requisitos: una cuenta de Google y crear un Calendario de Google. Se recomienda crear un calendario específico para este ejercicio puesto que vamos a borrar y crear eventos en el mismo de forma genérica y si lo hacemos sobre el principal podríamos perder eventos de nuestro interés. Así mismo para poder acceder a las APIs de Google deberemos obtener unas credenciales que autoricen a la aplicación para acceder a nuestra cuenta.
En este post vamos ver cómo consumir un XML con los eventos gratuitos que se van a realizar en las bibliotecas municipales de Madrid usando el servicio de Datos Abiertos. Nuestro script se va a ejecutar de forma periódica, obteniendo del servicio remoto los eventos para los próximos 60 días, parseando el Xml y creando los eventos en el calendario con la información de interés (Título, descripción, fecha de inicio y fin, etc). Para más información consultar https://datos.madrid.es/portal/site/egob/

Xml

El endpoint que ofrece la web de Datos Abiertos de Madrid nos devuelve un array de elementos contenido tal como se muestra a continuación:

<contenido>
<tipo>Evento</tipo>
<atributos idioma="es">
<atributo nombre="ID-EVENTO">10701791</atributo>
<atributo nombre="TITULO">A la carta</atributo>
<atributo nombre="GRATUITO">1</atributo>
<atributo nombre="EVENTO-LARGA-DURACION">0</atributo>
<atributo nombre="FECHA-EVENTO">2018-05-19 00:00:00.0</atributo>
<atributo nombre="FECHA-FIN-EVENTO">2018-05-19 23:59:00.0</atributo>
<atributo nombre="HORA-EVENTO">12:00</atributo>
<atributo nombre="DESCRIPCION">
<![CDATA[ A la carta: Fundación Arte que alimenta (clásica). ]]>
</atributo>
<atributo nombre="CONTENT-URL">
http://www.madrid.es/sites/v/index.jsp?vgnextchannel=1ccd566813946010VgnVCM100000dc0ca8c0RCRD&vgnextoid=60d3e5e031b23610VgnVCM2000001f4a900aRCRD
</atributo>
<atributo nombre="TITULO-ACTIVIDAD">Conciertos en la Biblioteca Eugenio Trías</atributo>
<atributo nombre="CONTENT-URL-ACTIVIDAD">
http://www.madrid.es/sites/v/index.jsp?vgnextchannel=1ccd566813946010VgnVCM100000dc0ca8c0RCRD&vgnextoid=8c43e5e031b23610VgnVCM2000001f4a900aRCRD
</atributo>
<atributo nombre="LOCALIZACION">
<atributo nombre="CONTENT-URL-INSTALACION">
http://www.madrid.es/sites/v/index.jsp?vgnextchannel=9e4c43db40317010VgnVCM100000dc0ca8c0RCRD&vgnextoid=e791bed05ceed310VgnVCM1000000b205a0aRCRD
</atributo>
<atributo nombre="NOMBRE-INSTALACION">
Biblioteca Pública Municipal Eugenio Trías. Casa de Fieras de El Retiro
</atributo>
<atributo nombre="ID-INSTALACION">6893633</atributo>
<atributo nombre="COORDENADA-X">442345</atributo>
<atributo nombre="COORDENADA-Y">4474221</atributo>
<atributo nombre="LATITUD">40.4166091081099</atributo>
<atributo nombre="LONGITUD">-3.6795930368034804</atributo>
<atributo nombre="LOCALIDAD">MADRID</atributo>
<atributo nombre="PROVINCIA">MADRID</atributo>
<atributo nombre="DISTRITO">RETIRO</atributo>
</atributo>
<atributo nombre="TIPO">/contenido/actividades/Musica</atributo>
</atributos>
</contenido>

De todos estos datos nosotros vamos a usar sólo un pequeño subconjunto de ellos como son TITULO-ACTIVIDAD, FECHA-EVENTO y LOCALIZACION, teniendo este último la particularidad de que es a su vez un array de attributo

Para ello tendremos que ser capaces de buscar en los nodos aquellos que tengan el atributo nombre con el valor de interés

Consola Google

En primer lugar deberemos crear una aplicación en la consola de Google en https://console.developers.google.com

En esta aplicación habilitaremos al menos las APIs de "Google Calendar API"

Así mismo deberemos crear unas credenciales de "OAuth" obteniendo la posibilidad de descargarlas en un fichero JSON y que deberemos ubicar junto con el script.

Groogle

Para facilitar la parte técnica de autentificación y creación de servicios he creado un proyecto de código abierto, Groogle, disponible en https://puravida-software.gitlab.io/groogle/ el cual publica un artefacto en Bintray y que podremos usar en nuestros scripts simplemente incluyendo su repositorio.

Este proyecto se divide a su vez en otros subprojectos, como por ejemplo:

  • groogle-core, contiene la parte de autentificación principalmente

  • groogle-drive, para el manejo de ficheros y carpetas en Drive

  • groogle-sheet, para la gestión específica de hojas de cálculo Sheet

  • groogle-calendar, para la gestión específica de calendarios

En nuestro caso vamos a utilizar el último (el primero se autoincluye por dependencia transitiva)

@Grab(group='io.github.http-builder-ng', module='http-builder-ng-apache', version='1.0.3')
@Grab(group='ch.qos.logback', module='logback-classic', version='1.2.3')

@Grab(group = 'com.puravida.groogle', module = 'groogle-core', version = '1.5.0-alpha2')
@Grab(group = 'com.puravida.groogle', module = 'groogle-calendar', version = '1.5.0-alpha2')
@GrabConfig(systemClassLoader=true)

import com.google.api.services.calendar.CalendarScopes
import com.puravida.groogle.GroogleScript
import com.puravida.groogle.CalendarScript

import groovyx.net.http.*
import static groovyx.net.http.HttpBuilder.configure
import static groovyx.net.http.ContentTypes.JSON
import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson

Groogle nos permitirá realizar el login de nuestra aplicación y guardar la autorización en nuestro disco de tal forma que en ejecuciones posteriores no se requiera de autorizar de nuevo la aplicación (mientras no borremos el fichero con la autorización generada)

    login {
        applicationName 'groogle-example'
        withScopes CalendarScopes.CALENDAR
        usingCredentials new File('client_secret.json')
        asService false
    }

Eliminando antiguos

Para mantener el calendario "limpio" con sólo los eventos de interés vamos a realizar en primer lugar un borrado de todos los eventos que existen en el mismo

    String groogleCalendarId = "0v757vib7jf1hj0bjsq041vco8@group.calendar.google.com"
    withCalendar groogleCalendarId, {
        eachEvent{
            removeFromCalendar()
        }
    }

Consumiendo XML

Para realizar la petición al servicio remoto usaremos HttpBuilder-Ng el cual nos permite recuperar el contenido en formato Xml tal como se muestra a continuación:

XML = ["application/xml", "text/xml", "application/xhtml+xml", "application/atom+xml"]
xmlEncoder = NativeHandlers.Encoders.&xml

http = configure {
    request.uri = 'https://datos.madrid.es/egob/catalogo/206717-0-agenda-eventos-bibliotecas.xml'
    request.encoder XML, xmlEncoder
    request.contentType = 'application/xml'
}.get{

}

Una vez realizado el GET , el objeto http será un groovy.util.Node el cual nos permitirá navegar a través de sus nodos buscando la información de interés.

Business

Por último sólo resta ir navegando por los nodos recuperados extrayendo la información de interés y creando un evento en el calendario para cada una de ellas

    http.contenido.each{
        String titulo = it.atributos.atributo.find{ it.'@nombre'=='TITULO-ACTIVIDAD' }.text()
        String descripcion = it.atributos.atributo.find{ it.'@nombre'=='DESCRIPCION' }.text()
        String inicio  = it.atributos.atributo.find{ it.'@nombre'=='FECHA-EVENTO' }.text()
        String fin  = it.atributos.atributo.find{ it.'@nombre'=='FECHA-FIN-EVENTO' }.text()
        String hora  = it.atributos.atributo.find{ it.'@nombre'=='HORA-EVENTO' }.text()
        String where  = it.atributos.atributo.find{ it.'@nombre'=='LOCALIZACION' }?.atributo.find{it.'@nombre'=='NOMBRE-INSTALACION'}.text()

        Date dini = Date.parse('yyyy-MM-dd HH:mm:ss.S', inicio)
        Date dfin = Date.parse('yyyy-MM-dd HH:mm:ss.S', fin)

        if( descripcion.trim()=="" )
            return
        if( dini < fromDate )
            return

        createEvent groogleCalendarId, {
            it.event.summary = titulo
            it.event.description = "($hora) $descripcion"
            it.event.location=where
            from dini
            until dfin
        }
    }

Script
//tag::dependencies[]
@Grab(group='io.github.http-builder-ng', module='http-builder-ng-apache', version='1.0.3')
@Grab(group='ch.qos.logback', module='logback-classic', version='1.2.3')

@Grab(group = 'com.puravida.groogle', module = 'groogle-core', version = '1.5.0-alpha2')
@Grab(group = 'com.puravida.groogle', module = 'groogle-calendar', version = '1.5.0-alpha2')
@GrabConfig(systemClassLoader=true)

import com.google.api.services.calendar.CalendarScopes
import com.puravida.groogle.GroogleScript
import com.puravida.groogle.CalendarScript

import groovyx.net.http.*
import static groovyx.net.http.HttpBuilder.configure
import static groovyx.net.http.ContentTypes.JSON
import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson

//end::dependencies[]

//tag::http[]
XML = ["application/xml", "text/xml", "application/xhtml+xml", "application/atom+xml"]
xmlEncoder = NativeHandlers.Encoders.&xml

http = configure {
    request.uri = 'https://datos.madrid.es/egob/catalogo/206717-0-agenda-eventos-bibliotecas.xml'
    request.encoder XML, xmlEncoder
    request.contentType = 'application/xml'
}.get{

}
//end::http[]

Date fromDate = new Date() - 10

CalendarScript.instance.with {
    //tag::login[]
    login {
        applicationName 'groogle-example'
        withScopes CalendarScopes.CALENDAR
        usingCredentials new File('client_secret.json')
        asService false
    }
    //end::login[]

    //tag::prepare[]
    String groogleCalendarId = "0v757vib7jf1hj0bjsq041vco8@group.calendar.google.com"
    withCalendar groogleCalendarId, {
        eachEvent{
            removeFromCalendar()
        }
    }
    //end::prepare[]

    //tag::business[]
    http.contenido.each{
        String titulo = it.atributos.atributo.find{ it.'@nombre'=='TITULO-ACTIVIDAD' }.text()
        String descripcion = it.atributos.atributo.find{ it.'@nombre'=='DESCRIPCION' }.text()
        String inicio  = it.atributos.atributo.find{ it.'@nombre'=='FECHA-EVENTO' }.text()
        String fin  = it.atributos.atributo.find{ it.'@nombre'=='FECHA-FIN-EVENTO' }.text()
        String hora  = it.atributos.atributo.find{ it.'@nombre'=='HORA-EVENTO' }.text()
        String where  = it.atributos.atributo.find{ it.'@nombre'=='LOCALIZACION' }?.atributo.find{it.'@nombre'=='NOMBRE-INSTALACION'}.text()

        Date dini = Date.parse('yyyy-MM-dd HH:mm:ss.S', inicio)
        Date dfin = Date.parse('yyyy-MM-dd HH:mm:ss.S', fin)

        if( descripcion.trim()=="" )
            return
        if( dini < fromDate )
            return

        createEvent groogleCalendarId, {
            it.event.summary = titulo
            it.event.description = "($hora) $descripcion"
            it.event.location=where
            from dini
            until dfin
        }
    }
    //end::business[]
}