Tras ver la gran aceptación que a tenido el tema de los reportes utilizando plantillas ODT y la librería JODReports y viendo la escasa documentación y ejemplos que se pueden encontrar en la red me dispongo a mostraros algunos ejemplos de utilización de esta herramienta así como algunas recomendaciones y posibles problemas que he tenido a lo largo de algunos proyectos con estos informes y la mejor forma que he encontrado de solucionarlo.
Este tutorial se divide en 2: en la primera mostraré la parte en java (básicamente coger la plantilla, enchufarle los datos y obtener el fichero final) y en la segunda, a mi entender, la mejor forma de construir una plantilla.
En el post anterior
JODReports - Mini How to se describe la utilización básica de esta librería. Aquí repasaremos dichos ejemplos y ampliaremos con recomendaciones que por razones de desconocimiento no se mencionaron en el anterior.
Para sustituir las variables en nuestra plantilla lo primero que debemos hacer es abrir nuestra plantilla, para ello utilizamos la siguiente línea:
DocumentTemplateFactory documentTemplateFactory = new DocumentTemplateFactory();
DocumentTemplate template = documentTemplateFactory.getTemplate(new File(ruta_fisica_plantilla/nombreFichero.odt));
Las variables a sustituir previamiente las hemos debido de cargar a un objeto Map
Map data = new HashMap();
data.put("nombre_variable", "valor_variable");
// añadir todas las variables a cambiar
Una vez tenemos la plantilla y las variables simplemente pasamos estas últimas a la plantilla y le indicamos el fichero de salida.
template.createDocument(data, new FileOutputStream(ruta_fisica_fichero_guardar//nombreFichero-final.odt));
Si queremos incluir imágenes en nuestra plantilla odt deberemos definir un ImageSource que también añadiremos a nuestro Map con las variables para que sea sustituida.
El objeto ImageSource se puede definir de las siguientes maneras:
ImageSource imagen = new RenderedImageSource(ImageIO.read(new File(ruta_fisica_imagen)));
ImageSource imagen = new FileImageSource(ruta_fisica_imagen);
ImageSource imagen = new ClasspathImageSource(ruta_fisica_imagen);
Bueno llegados a este punto ya tenemos la parte de java desarrollada ahora vamos a construir la plantilla odt.
Recomendación MUY IMPORTANTE: Hay que intentar lo más posible hacer la plantilla enteramente desde el OpenOffice, si habéis leído el
post anterior habréis observado que para introducir las sentencias de bucles o condicionales se recurre a abrir el fichero odt con winrar y editar el archivo
content.xml esto es un error pues TODO el código que hayamos tecleado fuera de la estructura del documento si tenemos que editar la plantilla desde el OpenOffice para hacer alguna modificación en el contenido estático, al guardar todo será eliminado ya que el documento es reconstruido cada vez que guardamos.
Ahora empezamos a construir la plantilla. Lo primero será preparar todo el contenido estático del documento (contenido y formato). Es importante que el documento esté bien construido (por ejemplo: nada de 5 saltos de linea para conseguir la separación apropiada entre 2 párrafos nos vamos a propiedades del párrafo y le decimos que tenga una separación concreta con el siguiente o si necesitamos una página nueva hacemos un salto de página nada de salto de lineas hasta que salte de página).
Una vez que tenemos el contenido estático empezamos a introducir el contenido dinámico, lo primero y más sencillo las variables. Para insertar una variable simplemente ${nombre_variable}. Esta variable a de estar en nuestro Map principal. Esta variable puede ser un objeto simple (int, char, Integer, String, etc.) o un objeto complejo o un Map en tales casos para acceder a una propiedad (en el objeto) se hará ${nombre_variable.propiedad} o un value del objeto con ${nombre_variable.clave}.
Si necesitamos, por ejemplo, un contador dentro del documento o una variable podemos hacerlo de la siguiente manera:
[#assign contador = 1]
Acabamos de crear una variable "contador" con valor = 1. Para mostrarla simplemente ${contador}. Si necesitamos incrementarla, por ejemplo, lo podemos hacer de la siguiente manera :
[#assign contador = contado + 1]
La sentencia condicional que tenemos disponible para mostrar u ocultar un bloque del documento es la siguiente.
[#if mi_variable = valor]
bloque de contenido...
[#else]
otro bloque...
[/#if]
¿Simple verdad? Ahora voy a explicar y a mostrar con un sencillo ejemplo un caso práctico de como utilizar esto correctamente, los posibles errores que podemos tener y como evitarlos o solucionarlos.
He aquí una captura de una plantilla con 3 párrafos (5 si contamos los 2 formados por la apertura y cierre del segundo if) y dos sentencias condicionales:
En el ejemplo mostrado en la imagen, la primera condición de no cumplirse al generar el odt resultante hará que aunque el fichero se cree de forma correcta al tratar de abrirlo con OpenOffice nos de un error en la estructura. ¿la razón? la explico a continuación. El código interno de la plantilla tras guardarla para estos párrafos sería el siguiente:
<text:p text:style-name="P1">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eu varius diam. Curabitur quis tellus turpis. Nunc non nibh massa. Vestibulum et [#if variableUno = 1] <text:span text:style-name="T1"> lorem sed arcu hendrerit[/#if]</text:span> suscipit in ut mauris. Maecenas est ipsum, feugiat at rhoncus eget, scelerisque quis felis. Vestibulum elementum pellentesque pharetra. Donec commodo volutpat nunc vel cursus.
</text:p>
<text:p text:style-name="P1">
[#if variableDos = 1]
</text:p>
<text:p text:style-name="P1">
Nullam lorem elit, tempor fermentum sodales nec, ultricies a leo. Fusce vestibulum nisl eget libero pulvinar in accumsan libero pulvinar. Sed placerat tortor in arcu laoreet id convallis turpis rhoncus. Suspendisse sit amet mollis ante.
</text:p>
<text:p text:style-name="P1">
[/#if]
</text:p>
<text:p text:style-name="P1">
Aliquam vel lorem ligula. Donec convallis, ligula id dictum imperdiet, leo diam congue quam, in eleifend est neque eu tellus.
</text:p>
El código anterior es el que veríamos si abriéramos el odt de la plantilla con winrar y editáramos el content.xml. En el se pueden apreciar perfectamente los 5 párrafos que componen este documento (<text:p></text:p>). Como es un ejemplo muy simple el estilo para los 5 párrafos es el mismo (text:style-name="P1"). Para aplicar un estilo dentro del párrafo en linea se utiliza <text:span></text:span>.
Una vez aclarado esto podemos observar que para dar el formato de negrita en el documento se ha utilizado esta etiqueta span. ¿cuál es el problema con la primera sentencia? Si se cumple no hay problema, ahora si la condición no se cumple al generar el odt resultante se eliminará todo aquello dentro del [#if][/if] por lo que el código del párrafo generado será el siguiente:
<text:p text:style-name="P1">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eu varius diam. Curabitur quis tellus turpis. Nunc non nibh massa. Vestibulum et </text:span> suscipit in ut mauris. Maecenas est ipsum, feugiat at rhoncus eget, scelerisque quis felis. Vestibulum elementum pellentesque pharetra. Donec commodo volutpat nunc vel cursus.
</text:p>
Como se pude observar queda por ahí perdida una etiqueta de cierre del span que daba la formato al a la porción de texto dentro del párrafo. Esto va a producir un error al abrir el documento con OpenOffice pues la estructura de este no es correcta. Para evitar estos errores simplemente debemos de estar atentos al construir nuestra plantilla. Si el cierre del if [/#if] no hubiera estado en negrita significaría que esta fuera de ese span de formato y por lo tanto se eliminaría por completo todo el span y no se habría roto la estructura del documento. Y la plantilla sería correcta.
En la segunda sentencia no habría problemas de no cumplirse pues el código resultante sería correcto. Marco con un fondo más oscuro lo susceptible a desaparecer en el código si no se cumpliera la segunda condición.
La etiqueta de párrafo del sentencia [#if] en caso de no cumplirse la condición no se queda abierta ya que se cierra con la etiqueta de cierre del párrafo del cierre de la sentencia [/#if].
<text:p text:style-name="P1">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin eu varius diam. Curabitur quis tellus turpis. Nunc non nibh massa. Vestibulum et [#if variableUno = 1] <text:span text:style-name="T1"> lorem sed arcu hendrerit[/#if]</text:span> suscipit in ut mauris. Maecenas est ipsum, feugiat at rhoncus eget, scelerisque quis felis. Vestibulum elementum pellentesque pharetra. Donec commodo volutpat nunc vel cursus.
</text:p>
<text:p text:style-name="P1">
[#if variableDos = 1]
</text:p>
<text:p text:style-name="P1">
Nullam lorem elit, tempor fermentum sodales nec, ultricies a leo. Fusce vestibulum nisl eget libero pulvinar in accumsan libero pulvinar. Sed placerat tortor in arcu laoreet id convallis turpis rhoncus. Suspendisse sit amet mollis ante.
</text:p>
<text:p text:style-name="P1">
[/#if]
</text:p>
<text:p text:style-name="P1">
Aliquam vel lorem ligula. Donec convallis, ligula id dictum imperdiet, leo diam congue quam, in eleifend est neque eu tellus.
</text:p>
Resumiendo aunque no tengamos que editar el content.xml si que tenemos que construir la plantilla teniendo presente el código que se va a generar por debajo.
Para la iteración de objetos tipo List tenemos en JODReports la sentencia [#list nuestra_lista as item][/#list] y se utiliza de la siguiente manera:
Para iterar un List de Strings
[#list mi_lista_string as cadena]
${cadena}
[/#list]
Para iterar un list de objetos con sus propiedades
[#list mi_lista_object as item]
${item.propiedad}
[/#list]
A continuación vamos a ver unos ejemplos de iteración de listas, la lista estará formada por un listado de objetos Producto como el que se muestra a continuación.
public class Producto {
private int id;
private String nombre;
private int cantidad;
private float precioUnidad;
// Constructores
// ...
// Getters and Setters
// ...
}
Ahora vamos a ver 4 ejemplos básicos de iteración de listas, el porque de construir así la plantilla y el código que produce. Empezaremos viendo la captura de la plantilla con los 4 ejemplos.
Ejemplo 1
En el primer ejemplo vamos a iterar un list en línea con el texto (dentro de un párrafo). En este caso la única consideración a tener en cuenta es la misma que vimos en el primer ejemplo de la sentencia condicional, es decir, tenemos que intentar prevenir que el list pueda romper algún estilo. Veamos el código interno producido y la parte que se va a repetir con cada iteración (con fondo más oscuro).
<text:p text:style-name="Standard">Ejemplo 1:</text:p>
<text:p text:style-name="Standard">
A continuación se mencionan el nombre de todos los productos:[#list productos as item] ${item.nombre}, [/#list]. Las propiedades de cada producto se detallan en cada tabla de producto.
</text:p>
Ejemplo 2
En este segundo ejemplo vamos a mostrar todas las propiedades del elemento Producto en una tabla individual por cada elemento o iteración. En principio tampoco contempla ningún tipo de complicación ya que la iteración se va a realizar sobre una estructura (una tabla, podría ser un párrafo, etc) completa por lo que no tendremos problemas de que se rompa la estructura aunque no contenga elementos la lista ya que en ese caso la etiqueta que se abre antes de la definición del [#list] se cerraría con la etiqueta de cierre de la etiqueta que contiene el cierre del list [/#list] (al no tener elementos que iterar todo lo contenido entre [#list] y [/#list] se eliminará). Veamos el código interno producido y la parte que se va a repetir con cada iteración (con fondo más oscuro).
<text:p text:style-name="Standard">Ejemplo 2:</text:p>
<text:p text:style-name="Standard">
[#list productos as item]
</text:p>
<table:table table:name="Tabla1" table:style-name="Tabla1">
<table:table-column table:style-name="Tabla1.A"/>
<table:table-column table:style-name="Tabla1.B"/>
<table:table-row>
<table:table-cell table:style-name="Tabla1.A1" office:value-type="string">
<text:p text:style-name="P1">
Id
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla1.B1" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
${item.id}
</text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Tabla1.A2" office:value-type="string">
<text:p text:style-name="P1">
Nombre
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla1.B2" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
${item.nombre}
</text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Tabla1.A2" office:value-type="string">
<text:p text:style-name="P1">
Cantidad
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla1.B2" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
${item.cantidad}
</text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Tabla1.A2" office:value-type="string">
<text:p text:style-name="P1">
Precio
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla1.B2" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
${item.precioUnidad}/ Unidad
</text:p>
</table:table-cell>
</table:table-row>
</table:table>
<text:p text:style-name="Standard">
[/#list]
</text:p>
Ejemplo 3
En este tercer ejemplo vamos a iterar los elementos Producto del list dentro de una tabla
sin cabecera de tal forma que cada elemento este en una fila y cada propiedad en una columna. La definición del list se hace en la primera columna de la tabla que tendrá los bordes superior, izquierda e inferior sin pintar u ocultos para que una vez generado el informe no se aprecie esta columna. En la segunda columna y en adelante vamos colocando las propiedades del objeto como se muestra. El cierre del list se hace en la primera columna de la segunda fila, ocultaremos también los bordes oportunos para hacer desaparecer esta fila al generar el informe. Esta forma especial de construir el list dentro de la tabla hará que las iteraciones hagan crecer la tabla sin romper su estructura tengamos 0, 1 o n elementos en el list. Veamos el código interno producido y la parte que se va a repetir con cada iteración (con fondo más oscuro).
<text:p text:style-name="Standard">
Ejemplo 3:
</text:p>
<table:table table:name="Tabla2" table:style-name="Tabla2">
<table:table-column table:style-name="Tabla2.A"/>
<table:table-column table:style-name="Tabla2.B"/>
<table:table-column table:style-name="Tabla2.C"/>
<table:table-column table:style-name="Tabla2.D"/>
<table:table-column table:style-name="Tabla2.E"/>
<table:table-row>
<table:table-cell table:style-name="Tabla2.A1" office:value-type="string">
<text:p text:style-name="P2">
[#list productos as item]
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla2.B1" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
${item.id}
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla2.B1" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
${item.nombre}
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla2.B1" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
${item.cantidad}
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla2.E1" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
${item.precioUnidad} / Unidad
</text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Tabla2.A1" office:value-type="string">
<text:p text:style-name="P2">
[/#list]
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla2.A1" office:value-type="string">
<text:p text:style-name="P2"/>
</table:table-cell>
<table:table-cell table:style-name="Tabla2.A1" office:value-type="string">
<text:p text:style-name="P2"/>
</table:table-cell>
<table:table-cell table:style-name="Tabla2.A1" office:value-type="string">
<text:p text:style-name="P2"/>
</table:table-cell>
<table:table-cell table:style-name="Tabla2.A1" office:value-type="string">
<text:p text:style-name="P2"/>
</table:table-cell>
</table:table-row>
</table:table>
Ejemplo 4
El cuarto y último ejemplo de iterar listas es muy parecido al anterior se iteran las elementos rellenando una tabla solo que en este ejemplo la tabla lleva una
cabecera en la primera fila. Al igual que en el ejemplo anterior el list se abrirá y cerrará en una columna a la cual ocultaremos los bordes oportunos para que al generar el informe no se visualice solo que en este caso la columna no será la primera sino la última. En la última columna de la cabecera abrimos el list, en la siguiente fila iteramos las propiedades del objeto y en la última columna de esta fila cerramos el list. Esta forma especial de construir el list dentro de la tabla hará que las iteraciones hagan crecer la tabla sin romper su estructura tengamos 0, 1 o n elementos en el list. Veamos el código interno producido y la parte que se va a repetir con cada iteración (con fondo más oscuro).
<text:p text:style-name="Standard">
Ejemplo 4:
</text:p>
<table:table table:name="Tabla3" table:style-name="Tabla3">
<table:table-column table:style-name="Tabla3.A"/>
<table:table-column table:style-name="Tabla3.B"/>
<table:table-column table:style-name="Tabla3.C"/>
<table:table-column table:style-name="Tabla3.D"/>
<table:table-column table:style-name="Tabla3.E"/>
<table:table-row>
<table:table-cell table:style-name="Tabla3.A1" office:value-type="string">
<text:p text:style-name="P4">
ID:
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla3.A1" office:value-type="string">
<text:p text:style-name="P4">
NOMBRE:
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla3.A1" office:value-type="string">
<text:p text:style-name="P4">
CANTIDAD:
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla3.D1" office:value-type="string">
<text:p text:style-name="P4">
PRECIO / UNIDAD:
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla3.E1" office:value-type="string">
<text:p text:style-name="P2">
[#list productos as item]
</text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
<table:table-cell table:style-name="Tabla3.A2" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
${item.id}:
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla3.A2" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
${item.nombre}:
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla3.A2" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
${item.cantidad}:
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla3.D2" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
${item.precioUnidad} / Unidad:
</text:p>
</table:table-cell>
<table:table-cell table:style-name="Tabla3.E1" office:value-type="string">
<text:p text:style-name="Table_20_Contents">
[/#list]:
</text:p>
</table:table-cell>
</table:table-row>
</table:table>
Para incluir imágenes de forma dinámica en nuestra plantilla simplemente tenemos que insertar uma imagen abrir sus propiedades y en la pestaña "Opciones" indicamos para "Nombre"
jooscript.image(nombre_variable) tal y como se muestra en la imagen.
Y hasta aquí este post de JODReports y la construcción de plantillas desde OpenOffice. Espero que sirva si os estáis peleando con los informes. No olvidéis si os ha gustado y os ha servido de ayuda dejar un comentario y compartir. Cualquier duda no dudéis en preguntarla trataré de responder a la mayor brevedad posible.
Salu2