Voi siete qui: Inizio Programmare in Scala

Lavorare con XML in Scala

XML è diventato da tempo la lingua franca nella comunicazione tra macchine attraverso la rete. La combinazione di leggibilità, standardizzazione e strumenti offerta dal formato ha reso inevitabile la diffusione di XML tra gli sviluppatori. Eppure, scrivere codice per manipolare documenti XML è un lavoro ingrato e spiacevole nella maggior parte dei linguaggi di programmazione. Scala migliora questa situazione.

Come nel caso degli attori, che abbiamo visto nel capitolo 9, Scala implementa una parte del supporto per XML sotto forma di libreria, a cui affianca un certo supporto nella propria sintassi. Il programmatore ha la sensazione che il supporto XML in Scala sia una parte completamente naturale del linguaggio. Alcuni comodi operatori addolciscono con una cucchiaiata di zucchero sintattico il compito di immergersi nelle profondità di documenti dalla struttura complessa, e il pattern matching edulcora ulteriormente il processo. Generare documenti XML è altrettanto piacevole.

Scala vi consente di inserire codice XML in linea, una comodità inusuale nei linguaggi di programmazione. Potete usare codice XML ovunque possiate usare una stringa, o quasi. Questa caratteristica rende più immediato l’impiego di modelli e di dati di configurazione, e vi permette di fare esperimenti con XML senza nemmeno aprire un file.

Ora esamineremo come si lavora con XML in Scala. Per prima cosa, analizzeremo come leggere un documento XML e navigare attraverso la sua struttura. Infine, produrremo documenti XML in maniera programmatica e mostreremo come usare il codice XML in linea.

Leggere XML

Cominceremo con i concetti di base: come trasformare una stringa che contiene codice XML in una struttura dati con cui poter lavorare.

// esempi/cap-10/reading/from-string-script.scala

import scala.xml._

val someXMLInAString = """
<sandwich>
  <bread>integrale</bread>
  <meat>salame</meat>
  <condiments>
    <condiment expired="true">maionese</condiment>
    <condiment expired="false">mostarda</condiment>
  </condiments>
</sandwich>
"""

val someXML = XML.loadString(someXMLInAString)
assert(someXML.isInstanceOf[scala.xml.Elem])

Tutto perfetto. Abbiamo trasformato la stringa in un’istanza di NodeSeq, il tipo usato da Scala per memorizzare una sequenza di nodi XML. Se il nostro documento XML si fosse trovato su disco, avremmo potuto usare il metodo loadFile dello stesso package.

Dato che abbiamo fornito noi stessi i dati XML, possiamo saltare l’invocazione di XML.loadString e assegnare semplicemente un frammento di markup a una variabile val o var.

// esempi/cap-10/reading/inline-script.scala

import scala.xml._

val someXML =
<sandwich>
  <bread>integrale</bread>
  <meat>salame</meat>
  <condiments>
    <condiment expired="true">maionese</condiment>
    <condiment expired="false">mostarda</condiment>
  </condiments>
</sandwich>

assert(someXML.isInstanceOf[scala.xml.Elem])

Esplorare XML

Se riportiamo l’esempio precedente nell’interprete, possiamo esplorare il nostro tramezzino usando alcuni comodi strumenti offerti da NodeSeq.

scala> someXML \ "bread"
res2: scala.xml.NodeSeq = <bread>integrale</bread>

Quella barra inversa — ciò che la documentazione chiama funzione di proiezione — dice “trovami gli elementi chiamati bread”. Quando usiamo una funzione di proiezione, otterremo sempre come risultato un’istanza di NodeSeq. Se siamo interessati soltanto a quello che si trova tra i tag, possiamo usare il metodo text.

scala> (someXML \ "bread").text
res3: String = integrale

L’espressione someXML \ "bread" text, senza parentesi e senza il punto prima della invocazione di text, è ancora valida. Otterrete sempre lo stesso risultato, ma la sintassi è più difficile da leggere. Le parentesi chiariscono le vostre intenzioni.

Abbiamo ispezionato solo lo strato più esterno del nostro tramezzino. Proviamo a ottenere la sequenza dei condimenti.

scala> someXML \ "condiment"
res4: scala.xml.NodeSeq =

Cosa c’è che non va? La funzione \ non discende negli elementi figli di una struttura XML. Per fare questo, dovete usare la funzione sorella \\ (due barre inverse).

scala> someXML \\ "condiment"
res5: scala.xml.NodeSeq = <condiment expired="true">maionese</condiment>
  <condiment expired="false">mostarda</condiment>

Molto meglio. (Abbiamo diviso la singola riga di uscita in due righe, per adattare il testo alla pagina.) Siamo entrati nella struttura e abbiamo estratto i due elementi <condiment>. Sembra che uno dei condimenti sia andato a male, però. Possiamo scoprire se un qualsiasi condimento è scaduto estraendo il suo attributo expired. Tutto quello che ci vuole è il simbolo @ prima del nome dell’attributo.

scala> (someXML \\ "condiment")(0) \ "@expired"
res6: scala.xml.NodeSeq = true

Abbiamo usato (0) per prendere il primo dei due condimenti restituiti da (someXML \\ "condiment").

Usare XML con i cicli e il pattern matching

Il frammento di codice precedente ha estratto il valore dell’attributo expired (true in questo caso), ma non ci ha detto qual è il condimento scaduto. Se stessimo maneggiando un tramezzino XML arbitrario, come faremmo a identificare i condimenti scaduti? Possiamo usare un ciclo per attraversare i dati XML.

// esempi/cap-10/reading/for-loop-script.scala

for (condiment <- (someXML \\ "condiment")) {
  if ((condiment \ "@expired").text == "true")
    println("la " + condiment.text + " è scaduta!")
}

Dato che NodeSeq eredita gli stessi attributi ormai noti di cui è fornita la maggior parte delle collezioni in Scala, uno strumento come il ciclo for può essere applicato direttamente. Nell’esempio appena visto, abbiamo estratto i nodi <condiment>, abbiamo effettuato un ciclo su ognuno di essi e abbiamo controllato se il loro attributo expired era uguale alla stringa "true". Abbiamo dovuto specificare che volevamo il testo di un certo condimento usando il campo text di condiment, altrimenti avremmo ottenuto una rappresentazione sotto forma di stringa dell’intera riga di codice XML.

Per operare sulle strutture XML possiamo anche usare il pattern matching. I casi del pattern matching possono essere scritti in termini di letterali XML; le espressioni tra parentesi graffe ({}) effettuano l’escape della sintassi standard del pattern matching in Scala. Per cercare una corrispondenza con tutti i nodi XML in una porzione di pattern matching di cui viene effettuato l’escape, usate un trattino basso seguito da un asterisco (_*). Per legare la corrispondenza trovata a una variabile, usate un prefisso contenente il nome della variabile e il simbolo @.

Mettiamo tutto insieme in un singolo esempio. Includeremo di nuovo il documento XML originale in modo che possiate seguire da vicino le operazioni di pattern matching sul codice XML.

// esempi/cap-10/reading/pattern-matching-script.scala

import scala.xml._

val someXML =
<sandwich>
  <bread>integrale</bread>
  <meat>salame</meat>
  <condiments>
    <condiment expired="true">maionese</condiment>
    <condiment expired="false">mostarda</condiment>
  </condiments>
</sandwich>

someXML match {
  case <sandwich>{ingredients @ _*}</sandwich> => {
    for (cond @ <condiments>{_*}</condiments> <- ingredients)
      println("condimenti: " + cond.text)
  }
}

Qui abbiamo legato il contenuto della nostra struttura <sandwich> (cioè, quello che si trova tra il tag di apertura e il tag di chiusura) a una variabile chiamata ingredients. Poi, man mano che iteriamo attraverso gli ingredienti in un ciclo for, assegniamo gli elementi racchiusi tra i tag <condiments> a una variabile temporanea di nome cond. Ogni valore assunto da cond viene successivamente stampato.

Gli stessi strumenti di Scala che ci permettono di manipolare strutture dati complesse con facilità sono prontamente disponibili anche per elaborare dati XML. La libreria XML di Scala è un’alternativa leggibile a XSLT che rende la lettura e l’analisi di XML un gioco da ragazzi, e vi fornisce anche alcuni strumenti altrettanto potenti per scrivere codice XML, come vedremo nella prossima sezione.

Scrivere XML

Mentre alcuni linguaggi assemblano documenti XML attraverso complessi meccanismi di serializzazione di oggetti, il supporto di Scala per i letterali XML rende molto più semplice la creazione di questi documenti. Essenzialmente, quando avete bisogno di codice XML vi basta scrivere codice XML. Per interpolare variabili ed espressioni, effettuate l’escape della sintassi Scala con le parentesi graffe, come abbiamo fatto nel pattern matching dell’esempio precedente.

scala> var name = "Bob"
name: java.lang.String = Bob

scala> val bobXML =
     | <person>
     |   <name>{name}</name>
     | </person>
bobXML: scala.xml.Elem =
<person>
  <name>Bob</name>
</person>

Come potete vedere, la variabile name è stata sostituita quando abbiamo costruito il documento XML assegnato a bobXML. Quella valutazione avviene solo una volta; se name venisse ridefinito in seguito, l’elemento <name> di bobXML conterrebbe ancora la stringa "Bob".

Un esempio reale

Per fare un esempio più completo, supponiamo di stare progettando l’incarnazione più recente e più amata del classico programma “ciao mondo”: un sistema di pubblicazione per un blog. Cominceremo con una classe che rappresenta un articolo di un blog in un formato compatibile con Atom.

// esempi/cap-10/writing/post.scala

import java.text.SimpleDateFormat
import java.util.Date

class Post(val title: String, val body: String, val updated: Date) {
  lazy val dashedDate = {
    val dashed = new SimpleDateFormat("yy-MM-dd")
    dashed.format(updated)
  }

  lazy val atomDate = {
    val rfc3339 = new SimpleDateFormat("yyyy-MM-dd'T'h:m:ss'-05:00'")
    rfc3339.format(updated)
  }

  lazy val slug = title.toLowerCase.replaceAll("\\W", "-")
  lazy val atomId  = "tag:example.com," + dashedDate + ":/" + slug
}

Oltre agli ovvi attributi title e body, nella nostra classe Post abbiamo definito diversi valori calcolati in maniera ritardata. Questi attributi ci torneranno comodi quando trasformeremo i nostri articoli in un feed Atom, il formato standard per la raccolta e l’aggregazione dei blog dai server web. Atom è un formato XML, e questa è un’applicazione perfetta per mostrare come generare documenti XML con Scala.

Definiremo una classe AtomFeed che prende una sequenza di oggetti Post come unico argomento.

// esempi/cap-10/writing/atom-feed.scala

import scala.xml.XML

class AtomFeed(posts: Seq[Post]) {
  val feed =
  <feed xmlns="http://www.w3.org/2005/Atom">
    <title>Il mio blog</title>
    <subtitle>Un sottotitolo fantasioso.</subtitle>
    <link href="http://example.com/"/>
    <link href="http://example.com/atom.xml" rel="self"/>
    <updated>{posts(0).atomDate}</updated>
    <author>
      <name>Mario Rossi</name>
      <uri>http://example.com/about.html</uri>
    </author>
    <id>http://example.com/</id>
    {for (post <- posts) yield
    <entry>
      <title>{post.title}</title>
      <link href={"http://example.com/" + post.slug + ".html"} rel="alternate"/>
      <id>{post.atomId}</id>
      <updated>{post.atomDate}</updated>
      <content type="html">{post.body}</content>
      <author>
        <name>Mario Rossi</name>
        <uri>http://example.com/about.html</uri>
      </author>
    </entry>
    }
  </feed>

  def write = XML.saveFull(Config.atomPath, feed, "UTF-8", true, null)
}

In questo esempio abbiamo pesantemente sfruttato la possibilità di effettuare l’escape delle espressioni Scala. Ovunque ci sia bisogno di un frammento di informazione dinamica — per esempio, la data del primo articolo nella sequenza, formattata secondo lo standard Atom — abbiamo semplicemente scritto normale codice Scala effettuandone l’escape. Nell’ultima metà dell’elemento <feed>, abbiamo usato un’espressione for per produrre blocchi successivi di codice XML formattato dinamicamente.

Il metodo write di AtomFeed mostra l’uso del metodo saveFull fornito dalla libreria scala.xml. Il metodo saveFull scrive un documento XML su disco, utilizzando opzionalmente un altro schema di codifica e una diversa dichiarazione di tipo di documento. In alternativa, il metodo save, contenuto nello stesso package, farà uso di una qualsiasi variante di java.io.Writer nel caso aveste bisogno di un buffer, di una pipe, &c.

Scrivere documenti XML in Scala è un’operazione banale: costruite il documento di cui avete bisogno sfruttando il codice XML in linea, ricorrete all’interpolazione ovunque sia necessario sostituire il contenuto dinamico, e usate i metodi di convenienza illustrati per scrivere i vostri documenti completi su disco o su altri canali di uscita.

Riepilogo, e poi?

XML è usato da applicazioni software di ogni tipo, eppure pochi linguaggi facilitano il compito di lavorare con XML. Abbiamo visto che Scala velocizza lo sviluppo con XML semplificando la lettura e la scrittura di codice XML.

Nel prossimo capitolo, vedrete il ricco supporto offerto da Scala per creare i vostri linguaggi domain-specific (DSL).

© 2008–9 O’Reilly Media
© 2009–10 Giulio Piancastelli per la traduzione italiana