Leggere, interrogare e trasformare file XML da riga di comando
Intro
Il comune di Palermo ha pubblicato i dati pubblici e aperti sulle elezioni comunali dell’11 giugno 2017 in formato XML, con uno schema descritto in questo file.
Qui sotto ad esempio la struttura di uno degli oltre 200 file pubblicati.

L’XML è uno dei formati classici di pubblicazione di dati aperti, ma non è un formato per tutti. Molti utenti infatti non sono in grado di esaminarli.
Per la sua natura è facilmente leggibile da un calcolatore e ci sono varie modalità per farlo.
E con un’utility specializzata (XMLStarlet) e con un piccolo comando come questo di sotto, è possibile trasformare questo file XML in una “piatta” tabella con tutti i dati sui voti dei candidati di una lista al consiglio comunale, per ogni sezione elettorale (40 candidati per 600 sezioni, quindi 24000 record).
Qui sotto lo vedete in azione:

I dati sui risultati delle liste elettorali hanno questa struttura:
SV è la sezione elettorale con il suo numero identificativo (e altri attributi), che contiene al suo interno i dati su V0 che rappresenta la lista (in questo caso la 12, quella del “Movimento 5 stelle”), che contiene al suo interno V1, ovvero i dati sui candidati al consiglio comunale. Con questa struttura gerarchica SV>V0>V1.
Il comando di sopra nel dettaglio:
sel --net -t -m, abilito la selezione (sel) su un file remoto (--net), impostando un template (-t) per “mappare” gli elementi che corrispondono (“matchano”-m) alla seguente query XPATH;"//SV/V0/V1", i candidati al consiglio;"@NUMERO", l’attributo con il numero identificativo del candidato;-o ",", per inserire un separatore di testo;-v "@VOTIVALIDI_C1", l’attributo con i voti validi del candidato;-o ",", per inserire un separatore di testo;-v "../@NUMERO", mi muovo verso l’alto nella gerarchia dell’XMLdi un gradino con.., quindi vado inV0(la lista) e recupero l’identificativo numerico della lista;-o ",", per inserire un separatore di testo;-v "../../@NUMERO", mi muovo verso l’alto nella gerarchia dell’XMLdi due gradini con../.., quindi vado inSV(il seggio) e recupero l’identificativo numerico del seggio;-n, per inserire un’andata a capo per ogni risultato ottenuto;- http://…/SEZ_3_82053_L12.xml è l’URL del file XML.
In output nella shell avrò:
Ovvero
| numeroCandidato | voti | numeroLista | sezione |
|---|---|---|---|
| 1 | 5 | 12 | 1 |
| 2 | 9 | 12 | 1 |
| 3 | 2 | 12 | 1 |
| … | … | … | … |
Creare dei file CSV con i dati per tutte le liste
Ho pensato che possa essere molto interessante fare un esempio più ricco e completo e creare uno script bash per:
- scaricare tutti i file
XMLdelle 18 liste; - estrarre da ognuno l’anagrafica dei candidati consiglieri;
- estrarre da ognuno il numero di voti, per ogni sezione, di ogni candidato al consiglio;
- fare il join – unire – le info sul numero di voti, con l’anagrafica dei candidati consiglieri, e creare un file
CSVper ogni lista; - unire tutti i file
CSVe produrre anche un unico file con il numero di voti di ogni candidato, per ogni lista, per ogni sezione.
Requisiti
Il prodotto finale è uno script BASH, quindi bisogna avere a disposizione un sistema compatibile con questo linguaggio (lo sono essenzialmente tutti).
Richiede tre utility:
- l’immancabile cURL, per scaricare i file;
- XMLStarlet per interrogare i file
XMLe trasformarli in fileCSV; - csvkit per fare il join e il merge dei
CSVscaricati.
Richiede una conoscenza di base (e/o la volontà/possibità di farserla) su:
- XPATH, per estrarre i dati (per interrogare) i file XML;
- la linea di comando, perché è un po’ il campo di gioco di queste modalità di accesso e modifica di file;
- BASH, che è il linguaggio dello script finale;
- aprire i file XML del comune con un buon editor di testo, guardarli un po’ e comprenderne la struttura.
Lo script
Lo script per intero è più in basso. A seguire un esploso delle varie parti che lo compongono.
La prima cosa che viene eseguita nello script è il download dei file delle liste. Queste sono 18 ed è comodo scaricarle con un ciclo for ... loop che lo fa 18 volte per noi.
Poi da ognuno dei 18 file XML vengono estratti i dati anagrafici e i dati per sezione, sempre con un ciclo for.
A ogni file viene aggiunta anche un’intestazione di colonne.
I file di anagrafica hanno questa struttura:
| numeroCandidato | nomeCandidato | numeroLista | nomeLista |
|---|---|---|---|
| 1 | GELARDA IGOR DETTO GERARDA DETTO GERALDA | 12 | MOVIMENTO 5 STELLE |
| 2 | ARGIROFFI GIULIA | 12 | MOVIMENTO 5 STELLE |
| 3 | CAPARROTTA GIANCARLO DETTO CAPAROTTA | 12 | MOVIMENTO 5 STELLE |
Mentre quelli con i dati per sezione:
| numeroCandidato | voti | numeroLista | sezione |
|---|---|---|---|
| 1 | 5 | 12 | 1 |
| 2 | 9 | 12 | 1 |
| 3 | 2 | 12 | 1 |
Poi viene fatto il join tra anagrafica e dati per sezione:
Per ogni lista viene prodotto un file con nome lista_NumeroLista.csv, con questa struttura (ci sono delle colonne duplicate, che potrei rimuovere in fase di join):
| numeroCandidato | voti | numeroLista | sezione | numeroCandidato | nomeCandidato | numeroLista | nomeLista |
|---|---|---|---|---|---|---|---|
| 1 | 5 | 12 | 1 | 1 | GELARDA IGOR DETTO GERARDA DETTO GERALDA | 12 | MOVIMENTO 5 STELLE |
| 2 | 9 | 12 | 1 | 2 | ARGIROFFI GIULIA | 12 | MOVIMENTO 5 STELLE |
| 3 | 2 | 12 | 1 | 3 | CAPARROTTA GIANCARLO DETTO CAPAROTTA | 12 | MOVIMENTO 5 STELLE |
E infine viene creato anche un unico file CSV di insieme (scaricabile da qui), con i dati per tutti i consiglieri di tutte le liste, per ogni sezione (senza le colonne duplicate). Sono 638 candidati per 600 sezioni per un totale di 382800 record.
Quindi avrò in output 1 file CSV con i dati per ogni lista e quello soprastante, per totale di 19 file CSV (encoding UTF-8 e come separatore la ,).
Lo script di poche righe (al netto dei commenti) è quello di sotto, tutto realizzato con oggetti free e open-source.
Libro consigliato
Per entrare nel mondo divertentissimo ed efficiente della “riga di comando” mi sento di consigliare il bel “Data Science at the Command Line” . È un libro per tutti, di facile lettura e pieno di esempi utili.