ODT (OpenDocument Text) Report Manager


Il framework MasonSQL dispone di un generatore di report nei formati ODT e PDF.

I dati da includere nel report vengono estratti dal database mediante delle query.
Un report può essere assemblato con più porzioni (testo, tabelle, documenti, ...) usate una sola volta o ripetute in base al numero di righe restituite dalle query.
Un report è quindi definito da un elenco di porzioni, da assemblare con dei dati, anch'essi estratti nell'ordine di definizione, usando un template quale modello per la generazione del contenuto.

Con il formato PDF è inoltre possibile assemblare in un unico file "PDF" più report e file.

Porzione di report

Un report è suddiviso in porzioni (ad esempio intestazione, testata, dettaglio, piede).
Il report ha inoltre una intestazione di pagina ed un piè di pagina.

Una porzione di report può utilizzare vari tipi di modello, utilizzato per generare il blocco di oggetti ODT (testo, tabelle, immagini ...).

Una porzione di report può anche non definire un modello; utile nel caso si voglia definire una query di dati da utilizzare nelle porzioni successive.

Una porzione di report può anche non definire una query; in tal caso il modello verrà applicato una sola volta.

Una o più porzioni figlie possono fare riferimento ad una porzione padre.
La generazione delle porzioni figlie viene ripetuta per il numero di righe dei dati restituiti dalla query del padre.
Una porzione padre deve essere dichiarata prima delle porzioni figlie.

Durante il processo di generazione, i parametri e i dati risultato delle query precedentemente elaborate sono visibili nel modello e nella query della porzione successiva.
I dati delle query di porzioni figlie non sono visibili dalla porzione padre e tanto meno da porzioni allo stesso livello del padre.
Nelle porzioni figlie dello stesso padre le porzioni successive vedono i dati delle porzioni sorelle, elaborate in precedenza.
Ad ogni elaborazione di una riga di dati (risultato della query) di una porzione, saranno disponibili i dati della riga corrente e non delle righe precedenti.

Django Template Language

Nei documenti ODT e nelle query si utilizza DTL; un linguaggio specifico per la elaborarazione dei modelli (template processing), molto versatile e diffuso in vari ambienti tra cui anche Perl e Dojo Toolkit, entrambi usati in MasonSQL.

Il generatore dei report utilizza la libreria Perl DTL::Fast che implementa parte delle specifiche del linguaggio DTL.

Rispetto alla implementazione ufficiale ci sono alcune incompatibilità ed estensioni che citiamo solo per chi fa un uso sofisticato del linguaggio.

In DTL si utilizzano i caratteri {{ e }} per delimitare il testo da interpretare.
Ad esempio per sostituire una variabile var si scriverà nel modello o nella query {{ var }}.

Le variabili in DTL possono anche essere raggruppate gerarchicamente, usando hash e array.
Ad esempio per fare riferimento ad un elemento di una hash si scriverà {{ myhash.mykey }} dove myhash è il nome della hash e mykey è il nome della chiave.

Parametri

Il generatore di report accetta a riga di comando dei parametri che potranno essere utilizzati nei modelli e nelle query come variabili in DTL.

I parametri vengono inseriti in una hash di nome ARGV (ARGument Variables) che sarà visibile in tutti i modelli e query.

Ad esempio se si chiama il generatore con i seguenti parametri param1=100 MyPAR='my string data' esse saranno reperibili in DTL come {{ ARGV.param1 }} e {{ ARGV.MyPAR }}

Query

Ogni porzione di report può definire una query nel linguaggio SQL con la quale reperire i dati nel database.

Nella query possiamo inserire dei costrutti DTL che verranno elaborati prima di ogni esecuzione per estrarre la tabella dei dati.

Per ogni riga dei dati ricavati verrà generata la porzione usando il modello (se presente), rendendo visibili i dati delle colonne come variabili DTL.

I dati della riga saranno visibili in DTL come hash; ad esempio, se la porzione ha nome HEAD i dati della riga saranno del tipo {{ HEAD.var }}.

In caso di campi di tipo JSON il contenuto del campo verrà convertito in hash o array e sarà possibile fare riferimento ai dati sottostanti come ad esempio {{ HEAD.myhash.myfield }} {{ HEAD.myarray[5] }}

L'hash in DTL avrà un ulteriore valore ROW_NUMBER che esprime il numero di riga del recordset, contando da 1. Per esempio il tag {{ HEAD.ROW_NUMBER }} conterrà il numero di riga corrente.

La hash così definita sarà anche visibile nelle elaborazioni successive di query e modelli di porzioni allo stesso livello o superiore.

Immagini

Sarà possibile impaginare immagini. Le immagini possono essere caricate nella tabella SQL in formato bytea.

Un'immagine presente in una porzione sarà sostituita solo quando il suo nome contiene un tag DTL. Il formato del nome sarà: #<porzione_nome>.<immagine_nome_del_campo>. Le immagini senza tag DTL verranno ignorate.

Le immagini dei modelli verranno sostituite con le immagini appropriate dal database.

Il formato dei nomi delle immagini aggiunge al report, saranno: <porzione_nome>_<immagine_nome>_<numero_di_riga>.<suffix>

Il tipo di immagine MIME verrà rilevato e aggiunto gli attributi di immagine.
Il suffisso dell'immagine verrà determinato dal timo MIME dell'immagine.
La dimensione del frame rimane invariata dopo il rendering.

Configuratore integrato in MasonSQL

Nel menu [Config] è disponibile il form [ODT Reports] con il quale è possibile definire i report fornendo i modelli usati per l'assemblaggio e definendo le porzioni con le relative query di estrazione dei dati da stampare.

Nel form sono presenti due pulsanti (Test ODT e Test PDF) con i quali vengono generati e poi scaricati i report nei formati ODT o PDF, utilizzando i parametri indicati nel campo Test parameters.
Per la sintassi dei parametri, fare riferimento a OdtReport#Parametri .
La generazione nel formato ODT non include eventuali porzioni di tipo PDF (file e report allegati) che verranno ignorati.

I file dei modelli vengono allegati alla tabella dei report (public.odt_reports) e si possono caricare dal form. Ogni report ha un archivio separato dei file, eventualmente organizzato in cartelle sottostanti.
Se si salvano i file in cartelle, il riferimento al nome del file dovrà essere completo del persorso.

Definizione dei termini

termine descrizione
modello generico documento contenente le indicazioni di struttura o di grafica in cui si inseriscono le informazioni mediante riferimenti a dati esterni
modello di stampa file nel formato ODT utilizzato come base per la costruzione del documento
sezione parte di un documento ODT contrassegnata da un nome
porzione porzione del report, generata utilizzando una sezione, una tabella o un documento ODT
report documento nel formato ODT
DTL Django Template Language; usato per inserire nei modelli e nelle query i dati
dati I dati possono essere forniti al generatore di report come parametri (a riga di comando) o come risultato delle query
query istruzione nel formato SQL per estrarre i dati dal database. La query può contenere costrutti nel formato DTL

Dettagli dell'implementazione

Per archiviare le definizioni e i modelli dei report si utilizzano due tabelle:
nome tabella descrizione
public.odt_reports elenco dei report
public.odt_report_portions elenco delle porzioni dei report

Le tabelle sono editabili usando il configuratore integrato in MasonSQL.

Tabelle

Tabella public.odt_reports

nome campo descrizione
id chiave primaria
name nome del report
description descrizione breve del report
comment commento/descrizione lunga del report
cmd_parameters campo usato dal configuratore per passare i parametri al generatore per le stampe di prova.
Il formato è del tipo "param1=number param2='my string' "

Tabella public.odt_report_portions

nome campo descrizione
id chiave primaria
ord ordine di elaborazione
id_odt_report riferimento alla chiave primaria del report
name nome della porzione
id_father chiave primaria della porzione padre
type tipo di porzione
file_name nome del file del modello o del file dove è presente l'oggetto usato come modello
(indicare anche il percorso se il file è stato salvato in una cartella dell'archivio del report)
obj_ref nome di riferimento dell'oggetto usato come modello
query query SQL

Tipologie di porzioni di report

In base alla compilazione della tabella public.odt_report_portions si determinano le porzioni da elaborare in base al seguente schema che stabilisce i valori da attribuire ai campi type, file_name e obj_ref:

type descrizione della porzione file_name obj_refSorted ascending
base_report modello di documento da utilizzare come base nome del file ODT  
odt_file file in formato ODT nome del file ODT  
pdf_file file in formato PDF, da allegare nome del file PDF  
pdf_report report da allegare, in formato PDF   nome del report
odt_section sezione odt nome del file ODT da cui copiare la sezione nome della sezione
odt_table tabella ODT nome del file ODT da cui copiare la sezione nome della tabella

I campi file_name e obj_ref possono contenere costrutti DTL, così e possibile rendere parametrici le sorgenti dalle quali caricare modelli e allegati.

La query della porzione viene valutata per prima; poi si valutano i campi file_name e obj_ref per ogni riga restituita dalla query. In tal modo sarà possibile cambiare il modello, riga per riga.

Porzione di tipo base_report

Indica un modello di documento ODT da utilizzare come base per la generazione del report.
L'eventuale contenuto del file viene ignorato.
Il documento di base può contenere dei costrutti DTL, utile per elaborare l'intestazione di pagina e il piè di pagina ma anche eventuali altri oggetti del documento di base.
Questa porzione deve essere definita prima delle porzioni di tipo ODT (odt_section, odt_table e odt_file) in quanto è utilizzata come contenitore per esse.
La porzione deve essere omessa se il report contiene solo porzioni di tipo PDF (pdf_file e pdf_report) .
Se si inglobano oggetti provenienti da altri documenti ODT gli stili applicati saranno quelli definiti nel documento di base, qui definito.

Porzione di tipo odt_section

La porzione corrisponde ad una sezione ODT presente nel file, identificata con il suo nome.
Se il campo odt_file non è definito, la sezione verrà letta dal file del modello base_report.
Gli stili applicati saranno quelli definiti nel documento di base, e non quelli definiti nel file, eventualmente indicato.

Porzione di tipo odt_table

La porzione corrisponde ad una tabella ODT presente nel file, identificata con il suo nome.
Se il campo odt_file non è definito, la sezione verrà letta dal file del modello base_report.
Gli stili applicati saranno quelli definiti nel documento di base, e non quelli definiti nel file, eventualmente indicato.

Porzione di tipo odt_file

La porzione è costituita dal contenuto di un file ODT.
L'interpretazione DTL verrà applicata al contenuto.
Gli stili applicati saranno quelli definiti nel documento di base, e non quelli definiti nel file indicato.

Porzione di tipo pdf_file

Viene allegato il file in formato PDF senza alcuna trasformazione.

Porzione di tipo pdf_report

Il report da allegare (in formato ODT) viene generato fornendo come parametri per il generatore DTL, i campi risultato della query della porzione (solo i dati della prima riga).

Il report generato viene poi convertito nel formato PDF ed allegato.

Bug Libreoffice automatic RSID marks

Purtroppo i documenti ODT generati con Libreoffice soffrono di un bug che provoca la proliferazione di elementi di tipo SPAN anche se non includono differenti attributi di formattazione, rispetto al testo adiacente.

Il problema è qui documentato:

Il seguente esempio di codice XML mostra il problema, che impedisce al parser di interpretare correttamente i costrutti DTL:
<style:style style:name="T5" style:family="text"><style:text-properties officeooo:rsid="00260f63"/></style:style>
. . . .
<text:p text:style-name="P9">{{ <text:span text:style-name="T5">Ordini</text:span>.stato_ordine }}</text:p>

Prima di applicare il parser DTL è quindi necessario verificare che non ci siano elementi di tipo text:span con style contenente soltanto proprietà officeooo:rsid che andranno eliminate prima di procedere con il parsing.

Esempio

Utilizziamo le relazioni dell'applicazione d'esempio realizzata nella guida AddingChildFrame che utilizza le tabelle definite in NewTableWithEmptyMqlFile per comporre un report che stamperà l'elenco degli impiegati con la lista degli ordini, con i relativi prezzi e commissioni.

Le tabelle interessate sono demo.employees, demo.orders e demo.commissions

Se il report viene richiamato con il parametro ID_EMPLOYEE verrà stampato un solo impiegato con quella chiave primaria.

Dobbiamo quindi definire due query:
  • una per elencare gli impiegati:
SELECT
  id,
  name,
  surname,
FROM demo.employees
WHERE {% if not defined ARGV.ID_EMPLOYEE %}true{% endif %} or id = '{{ ARGV.ID_EMPLOYEE }}'::integer;

Limitiamo l'elenco nel caso sia definita la variabile ARGV.ID_EMPLOYEE (variabile fornita a riga di comando) al solo impiegato che ha il campo id uguale alla variabile.

  • e un'altra per elencare gli ordini effettuati da un impiegato:
SELECT
  price,
  type,
  percentage as perc
FROM demo.orders
INNER JOIN demo.commissions ON commissions.id = orders.commission
WHERE orders.employee = {{ EB.id }};

Limitiamo l'elenco utilizzando la variabile EB.id che corrisponde al campo id (chiave primaria dell'impiegato) della prima query, in quanto chiameremo con il nome EB (Employee body) la porzione del report corrispondente alla prima query.

Ipotizzando di aver definito un report di esempio nella tabella public.odt_reports con questi valori:
id name description comment cmd_parameters
23 demoreport example of report no comment ID_EMPLOYEE=3

e di aver caricato un file di nome my_templates.odt con i modelli, la tabella delle porzioni assumerà questi valori:
id ord id_odt_report name id_father type file_name obj_ref query
18 1 23     base_report my_templates.odt    
19 2 23     odt_section   employees_header{% defined ARGV.ID_EMPLOYEE %}_SINGLE{% endif %}  
14 3 23 EB   odt_section   employees_body SELECT id, name, surname,
FROM demo.employees
WHERE {% if not defined ARGV.ID_EMPLOYEE %}true{% endif %} or id = '{{ ARGV.ID_EMPLOYEE }}'::integer;
21 4 23   14 odt_section   orders_header  
22 5 23 OB 14 odt_section   orders_body SELECT price, type, percentage as perc
FROM demo.orders
INNER JOIN demo.commissions ON commissions.id = orders.commission
WHERE orders.employee = {{ EB.id }};
23 6 23   14 odt_section   orders_footer  
24 7 23     odt_section   employees_footer  

Le sezioni nel file ODT potrebbero essere così compilate:

Sezione employees_header:
=================================================
       List orders divided per employee
=================================================

Sezione employees_header_SINGLE:
=================================================
           Orders of an employee
=================================================

Sezione employees_body
  Employee {{ EB.name }} {{ EB.surname }}

Sezione orders_header
+---------------+--------------+----------------+
|     price     | % commission |     type       |

Sezione orders_body
+---------------+--------------+----------------+
| {% sprintf "%13.2f" OB.price %} | {% sprintf "%12.2f" OB.perc %} | {% sprintf "%13s" OB.type %}  |

Sezione orders_footer
+---------------+--------------+----------------+
                                            [{% sprintf '%3i' EB.id %}]

Sezione employees_footer

=================================================

Per ottenere un report simile al seguente, nell'ipotesi di chiamare il generatore con il parametro ID_EMPLOYEE=3:
=================================================
           Orders of an employee
=================================================

  Employee Ted Danson
+---------------+--------------+----------------+
|     price     | % commission |     type       |
+---------------+--------------+----------------+
|       12.50   |      12%     |        Breads  |
+---------------+--------------+----------------+
|      230.00   |      20%     |        Drinks  |
+---------------+--------------+----------------+
|      345.33   |      10%     |      Prepared  |
+---------------+--------------+----------------+
                                            [  3]

=================================================
Versione pagina: r26 - 14 Mar 2024, GuidoBrugnara
Questo sito utilizza FoswikiCopyright (©) Leader.IT - Italy P.I. IT01434390223 Informativa privacy & uso dei cookies