Ruby + XML = ROXI
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