Ruby + XML = ROXI

17 March, 2007 (14:36) | programming, roxi, ruby, xml

L’altro giorno mi stavo facendo un giro e trovo questo pezzo di codice ruby

01: require 'net/http'
02: require 'rexml/document'
03:
04: # Web search for "madonna"
05: query = 'appid=YahooDemo&query=madonna&results=2'
06: url = "http://api.search.yahoo.com/WebSearchService/V1/webSearch?#{query}"
07:
08: # get the XML data as a string
09: xml_data = Net::HTTP.get_response(URI.parse(url)).body
10:
11: # extract event information
12: doc = REXML::Document.new(xml_data)
13: titles = []
14: links = []
15: doc.elements.each('ResultSet/Result/Title') do |ele|
16:    titles << ele.text
17: end
18: doc.elements.each('ResultSet/Result/Url') do |ele|
19:    links << ele.text
20: end
21:
22: # print all events
23: titles.each_with_index do |title, idx|
24:    print "#{title} => #{links[idx]}\n"
25: end

E’ un esempio su come utilizzare Ruby per interrogare le API di Yahoo! e come successivamente elaborare il risultato per ricavare i dati di interesse, in particolare quest’ultimo obiettivo viene raggiunto utilizzando la libreria REXML (la libreria inclusa in Ruby per l’elaborazione di documenti XML), e mi sono ricordato del perchè ne ho scritta una mia… ma andiamo con ordine

L’anno scorso sono stato incaricato da un’associazione culturale che ha sedi in tutto il mondo di realizzare un’applicazione per permettere ad ogni sede di poter gestire autonomamente (ma senza perdere l’uniformità fra sedi) sia il proprio sito (sottodominio) internet sia la propria intranet. Ero felicissimo in quanto avrei potuto realizzare dei sogni/progetti che tenevo nel cassetto da un po’ di tempo:

  • Studiare (bene) Ruby
  • Realizzare un wiki strutturato
  • Rilasciare un lavoro, per il quale sarei stato pagato, sotto licenza GPL (non essendo un’associazione a fini di lucro ero riuscito a convincerli)

Purtroppo il sogno è durato poco e il progetto è stato sospeso per questioni politiche, ma parte del lavoro è stata salvata, fra cui una libreria per l’elaborazione di documenti XML scritta in Ruby e in C che ho chiamato ROXI

Gabriele, Gabriele, Gabriele, ma che agilista sei? Che fine hanno fatto le tue prediche su NIH, YAGNI e KISS? Avevi già una libreria in Ruby per fare tutto quello che ti serviva, perchè ne hai scritta una tu?

Sarei un bugiardo se dicessi che non mi sono divertito a scrivere ROXI, ma vi assicuro che no ho avuto proprio bisogno, perchè REXML è:

  • Leeeeenta
  • Non sfrutta a pieno la potenza espressiva di Ruby (diciamo che l’ho trovata poco idiomatica)
  • Non implementa gli standard che dice di implementare

Sugli altri punti avrei potuto anche sorvolare, ma l’ultimo è stato davvero troppo penalizzante, sopratutto per quanto riguarda l’implementazione di XPath che è veramente lontana dallo standard 1.0, non parliamo del 2.0. Mi sono detto “poco male, è un progetto open source, posso aggiustarlo e poi riconsegnarlo alla comunità”, poi ho iniziato a studiarmi i sorgenti e ho trovato questo (src/rexml/parsers/xpathparser.rb)

01: require 'rexml/namespace'
02: require 'rexml/xmltokens'
03:
04: module REXML
05:   module Parsers
06:     # You don't want to use this class.  Really.  Use XPath, which is a wrapper
07:     # for this class.  Believe me.  You don't want to poke around in here.
08:     # There is strange, dark magic at work in this code.  Beware.  Go back!  Go
09:     # back while you still can!
10:
11:     class XPathParser
12:       # ... more more and more flatten code
13:     end
14:   end
15: end

Al che ho desistito e ho iniziato a scrivermi una libreria che risolvesse anche gli altri due problemi che avevo riscontrato

Cosa c’entra il codice che hai mostrato all’inizio?

C’entra per il semplice fatto che è sbagliato! Non tanto per il risultato che in questo caso può essere giusto, ma è sbagliato per lo standard XML/XPath, infatti se date un occhio all’XML restituito dall’url

1: <?xml version="1.0" encoding="UTF-8"?>
2: <ResultSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:yahoo:srch">
3:         <Result>
4:                 <Title>Madonna</Title>
5:                 <Url>http://www.madonna.com</Url>
6:                 ...
7:         </Result>
8:         ...
9: </ResultSet>

Tutti gli elementi per i quali non è indicato il namespace sono da considerarsi nel namespace di default (urn:yahoo:srch), quindi le query XPath fatte con REXML alla linea 15 e 18 non dovrebbero restituire niente! Per questo esempietto non è un grosso problema, ma provate ad immaginarvi cosa significherebbe scoprire tutte queste “piccole” diversità dallo standard sulla pelle di un’applicazione da decine di migliaia di linee di codice :-)

Lo stesso esempio scritto utilizzando ROXI e volendo aderire allo standard XPath

01: require 'roxi/xpath'
02: include ROXI
03:
04: query = 'appid=YahooDemo&query=madonna&results=2'
05: url = "http://api.search.yahoo.com/WebSearchService/V1/webSearch?#{query}"
06:
07: context = XPath::Context.new
08: context.namespaces['yh'] = 'urn:yahoo:srch'
09:
10: XDocument.uri(url).xpath('/yh:ResultSet/yh:Result[yh:Title][yh:Url]', context).each do | result |
11:   puts "#{result.child('Title').value} => #{result.child('Url').value}"
12: end

Se invece possiamo concederci qualche libertà possiamo utilizzare un’estensione dello standard stesso per ottenere una versione più compatta

1: query = '//{urn:yahoo:srch}:Result[{urn:yahoo:srch}:Title][{urn:yahoo:srch}:Url]'
2:
3: XDocument.uri(url).xpath(query).each do | result |
4:   puts "#{result.child('Title').value} => #{result.child('Url').value}"
5: end

Ovvero invece che specificare prima il contesto di ricerca (label => namespace url) è possibile specificare l’url direttamente nella query (utile in caso l’url non sia troppo complessa)

Vi racconto tutto questo perchè mi sono venuti i rimorsi di coscienza: sono stato pagato, ho ottenuto una libreria che ha il suo perchè, ma l’ho tenuta a fare la muffa sul mio HD. Poco tempo fa Chiaroscuro mi ha fatto ricordare di ROXI e in cuor mio ho deciso di rimetterci mano per rilasciarla alla comunità, ma non prima di qualche piccolo ritocco (ovviamente in ordine di priorità)

  • Riscrivere il parser XML in Ruby (attualmente è un parser pull scritto in C)
  • Riscrivere il parser XPath in Ruby (attualmente e’ un parser scritto in C generato con Bison)
  • Implementare il metodo di validazione schematron
  • Completare il parco delle estensioni alle funzioni XPath standard

La riscrittura dei parser è imperativa. Ad oggi se uno vuole velocità può utilizzare libxml e vivere felice, il mio obiettivo invece è quello di realizzare un’alternativa a REXML, che sia aderente agli standard W3C e che sia scritta solo in Ruby, in vista anche dell’uscita di YARV

Se qualcuno volesse dare una mano o solo vedere il codice mi mandi una mail (info at gabrielelana dot it)

Ho creato un progetto su GoogleCode

Write a comment