Un file XML, si sa, altro non è che un file di testo contenente una sequenza di stringhe formattate secondo precise specifiche. In questo articolo vedremo insieme come è possibile scrivere un file XML in modo automatico sfruttando alcune librerie a disposizione di Ruby.
L'articolo non propone immediatamente una soluzione basata completamente su Ruby ma segue un approccio più formativo, mostrando i vari livelli per arrivare a scrivere il nostro XML con una sintassi object oriented.
Chi desidera immediatamente scrivere un XML sfruttando completamente le librerie Ruby può saltare immediatamente al capitolo Scrivere un XML con Builder. Questo articolo presuppone una certa conoscenza del linguaggio XML. Per ulteriori approfondimenti su XML consiglio la lettura della Guida XML di Base.
Molti modi per scrivere un XML
Scrivere un documento XML è tutto sommato banale, sia che si utilizzi un linguaggio di programmazione sia che si scriva il codice direttamente a mano. Prendiamo il codice seguente, che rappresenta un documento XML valido.
<?xml version="1.0" encoding="UTF-8"?> <articles> <article id="2644">Rails e Subversion</article> <article id="2619">Capistrano: il deployment Rails diventa facile</article> <article id="2616">ActiveRecord - un ORM per Ruby</article> </articles>
Se copiamo il contenuto e lo incolliamo in un file, ecco che il risultato è un file XML valido.
In questo caso non abbiamo utilizzato alcun linguaggio di programmazione ma abbiamo raggiunto ugualmente l'obiettivo: scrivere un XML. Questa soluzione, tuttavia, è scomoda e tanto più laboriosa quanto lungo è il mio file XML. Inoltre, è del tutto inutile ai fini del nostro scopo: imparare a scrivere un XML con Ruby.
Scrivere un XML in una stringa
Procedendo per gradi, decidiamo che il primo passo è quello di sfruttare l'uso di Ruby, variabili e stringhe. Grazie alla sintassi Heredoc possiamo inglobare il nostro XML in una stringa e poi elaborarlo. Ad esempio, vediamo come salvarlo in un file.
xml = <<XML <?xml version="1.0" encoding="UTF-8"?> <articles> <article id="2644">Rails e Subversion</article> <article id="2619">Capistrano: il deployment Rails diventa facile</article> <article id="2616">ActiveRecord - un ORM per Ruby</article> </articles> XML File.open('file.xml', 'w+') { |f| f.write(xml.chomp) } # utilizzo chomp per rimuovere gli spazi ad inizio e fine della stringa # => file.xml è stato creato con all'interno il contenuto di XML
Eseguendo questo script, otterremo un file chiamato file.xml
con all'interno il nostro contenuto XML, disponibile nella variabile XML. Questa soluzione, sebbene contenga un'elaborazione Ruby, è poco più che un'esecuzione
automatica di un processo manuale. Può andare bene se il contenuto XML arriva in ingresso da qualche input, ad esempio un database, in alternativa richiede sempre e comunque qualcuno che scriva a mano la stringa stessa.
Questa soluzione ancora non ci convince. Proviamo a migliorarla.
Scrivere un XML appoggiandosi ad un Hash/Array
Osservando attentamente il nostro XML, potremmo descrivere il contenuto come un elenco di elementi articolo, ciascuno dei quali con specifiche proprietà. Gli elementi sono racchiusi all'interno di un elemento contenitore che è preceduto dalla dichiarazione XML.
Proviamo allora a convertire la nostra stringa "statica" in qualcosa di più flessibile. Come prima cosa abbiamo bisogno di estrarre dalla stringa l'elenco degli articoli, inserendoli all'interno di un array.
articles = [ { :id => 2644, :title => 'Rails e Subversion' }, { :id => 2619, :title => 'Capistrano: il deployment Rails diventa facile'}, { :id => 2616, :title => 'ActiveRecord - un ORM per Ruby' }, ]
Per semplicità, ogni elemento dell'array è un Hash anche se in Ruby, normalmente, si preferisce creare uno specifico oggetto invece di delegare il compito ad un Hash (array associativo) come avviene invece in altri linguaggi. A questo punto, mantenendo inalterato il funzionamento generale, adattiamo il nostro script per scrivere l'XML.
articles = [
{ :id => 2644, :title => 'Rails e Subversion' },
{ :id => 2619, :title => 'Capistrano: il deployment Rails diventa facile'},
{ :id => 2616, :title => 'ActiveRecord - un ORM per Ruby' },
]
articles_xml = articles.map do |article|
"<article id="#{article[:id]}">#{article[:title]}</article>"
end
xml = <<XML
<?xml version="1.0" encoding="UTF-8"?>
<articles>#{articles_xml.join('')}</articles>
XML
File.open('file.xml', 'w+') { |f| f.write(xml.chomp) }
# => file.xml è stato creato con all'interno il contenuto di XML
Questa soluzione, anche se apparentemente più complessa, rappresenta il primo esempio verso il traguardo: un metodo di sviluppo fondamentale che si basa sulla separazione tra i dati (i nostri articoli) e la loro rappresentazione (risultato XML).
Qualche riga più in su ho accennato alla creazione di un oggetto specifico. Vogliamo provare a sfruttare un nuovo tipo di dato per scrivere il codice in modo più elegante?
Come prima cosa creiamo il nostro oggetto Article
.
class Article attr_accessor :id, :title def initialize(id, title) self.id = id self.title = title end def to_xml "<article id="#{self.id}">#{self.title}</article>" end end
Ora riscriviamo il codice iniziale.
articles = [
Article.new(2644, 'Rails e Subversion'),
Article.new(2619, 'Capistrano: il deployment Rails diventa facile'),
Article.new(2616, 'ActiveRecord - un ORM per Ruby'),
]
xml = <<XML
<?xml version="1.0" encoding="UTF-8"?>
<articles>#{articles.map { |a| a.to_xml }.join('') }</articles>
XML
File.open('file.xml', 'w+') { |f| f.write(xml.chomp) }
# => file.xml è stato creato con all'interno il contenuto di XML
Grazie all'uso degli oggetti siamo riusciti a trasferire la logica di trasformazione del nostro oggetto all'interno della definizione dell'oggetto stesso. Il risultato è un codice più conciso ma soprattutto uno script più flessibile. Possiamo infatti aggiungere quanti metodi desideriamo al nostro oggetto e convertire lo stesso dato in innumerevoli altri formati, senza troppi sforzi.
A questo punto, potremmo andare avanti con mini-ottimizzazioni per giorni, ma non è certo il caso. Per ottenere un reale salto di qualità di serve qualcosa di più.
Scrivere un XML con Builder
Ruby ci piace. Ci piace così tanto che il nostro obiettivo è quello di astrarre il più possibile la logica di conversione sfruttando al 100% le potenzialità del linguaggio. A questo punto, il prossimo passo è quello di utilizzare la libreria Builder.
Builder non fa parte del package core di Ruby, tuttavia possiamo facilmente installarla come GEM.
gem install builder
Scrivere un XML con Builder è semplice ed elegante, poiché la sintassi è perfettamente in sintonia con il pensiero Ruby.
Nota: prima di lanciarci a capofitto nell'adattare il nostro script, dedichiamo una decina di minuti a leggere la documentazione della GEM.
Prendendo come base di partenza il nostro array di oggetti, ecco come potremmo modificare
lo script iniziale.
articles = [
Article.new(2644, 'Rails e Subversion'),
Article.new(2619, 'Capistrano: il deployment Rails diventa facile'),
Article.new(2616, 'ActiveRecord - un ORM per Ruby'),
]
xml = Builder::XmlMarkup.new
xml.instruct!
xml.articles do |builder|
articles.each do |item|
xml.article(item.title, :id => item.id)
end
end
File.open('file.xml', 'w+') { |f| f.write(xml) }
# => file.xml è stato creato con all'interno il contenuto di XML
Semplice, chiaro ed elegante... ma non solo! Builder offre innumerevoli vantaggi aggiuntivi. Ad esempio, Builder è perfettamente consapevole che la sintassi XML obbliga a convertire i caratteri <, >, &, ' e " in entità e gestisce la codifica in modo assolutamente automatico.
Quanto più complesso è il documento da gestire, tanto più è consigliabile utilizzare librerie specifiche per la manipolazione di dati invece di delegare a soluzioni più orientate al semplice scripting. Potrebbe essere utile sapere che, dietro le quinte, anche Rails sfrutta Builder per la generazione di file XML.
Il risultato finale
Quella che segue è la versione completa del codice finale per scrivere il nostro XML con Builder.
require 'rubygems'
gem 'builder'
require 'builder'
class Article
attr_accessor :id, :title
def initialize(id, title)
self.id = id
self.title = title
end
end
articles = [
Article.new(2644, 'Rails e Subversion'),
Article.new(2619, 'Capistrano: il deployment Rails diventa facile'),
Article.new(2616, 'ActiveRecord - un ORM per Ruby'),
]
xml = Builder::XmlMarkup.new
xml.instruct!
xml.articles do |builder|
articles.each do |item|
xml.article(item.title, :id => item.id)
end
end
File.open('file.xml', 'w+') { |f| f.write(xml) }
# => file.xml è stato creato con all'interno il contenuto di XML