Descrizione
ROXI è stata inizialmente sviluppata da Gabriele Lana per l’implementazione di un wiki strutturato, ora è stata resa disponibile (con licenza LGPL) come libreria indipendente per la manipolazione di documenti XML
Parti Sviluppate
- un parser xml
- scritto in C + relativi bindings
- non è validante
- non supporta DTD
- supporta xml:base, xml:id, xml:lang
- funzionamento misto pull/push (il primo del suo genere scritto in C)
- una rappresentazione ad oggetti del documento (DOM = Document Object Model)
- implementato in ruby
- ispirato ad XLinq di C# 3.0
- supporto per XPath 1.0
- parser scritto in C
- engine scritto in ruby
- supportate 100% delle funzionalità
- aggiunte delle estensioni allo standard
- supporto per XUpdate attraverso un DSL
- supportate 100% delle funzionalità
- supporto per XQuery attraverso un DSL
- supportate 80% delle funzionalità
- supporto iniziale per binding “class Ruby <-> DOM subtree”
La metodologia di sviluppo utilizzata è eXtreme Programming, tutto il codice è stato sviluppato in Test Driven Development, questo ha fatto in modo che attualmente ci siano più di 400 test automatici per la verifica della correttezza del codice finora sviluppato
Parti Da Sviluppare
- [Facile] validazione documenti con Schematron
- [Medio] validazione documenti con RelaxNG
- [Difficile] supporto per XSLT
- [Facile] supporto per manipolazione file RSS e/o Atom e/o OPML e/o …
- [Facile] supporto per estrazione/manipolazione microformats
- [Facile] supporto XInclude
- [Difficile] supporto per FOP
- [Medio] supporto per XLink
Altro
- [Documentazione] in realta’ i test parlano chiaro, e preferirei che le cose restassero cosi’, infatti mi piacerebbe realizzare un tool per la generazione automatica della documentazione a partire dai test (magari anche test realizzati ad hoc, tanto per semplificare le cose)
- [Deploy/Distribuzione] attualmente non gestisco in alcun modo la distribuzione del pacchetto, ci sono solo degli script Rake per build/test/clean del codice
- [Porting] dovrebbe essere verificata la portabilita’ verso sistemi operativi diversi da Linux (piattaforma nativa di sviluppo GNU/Linux/Gentoo)
Esempi
# creazione di un elemento
XElement.new('node')
# creazione di un elemento contenente del testo
XElement.new('node', XText.new('text'))
# i metodi di manipolazione del dom sono abbastanza "smart" da
# riconoscere i tipi di dati passati come parametri attuali e quindi
# comportarsi di conseguenza
XElement.new('node', 'text')
# creazione di un documento un po' piu' complesso
XDocument.new(
XElement.new('root',
XAttribute.new('attribute_1', 'value'),
XAttribute.new('attribute_2', 'value'),
XInstruction.new('php', 'phpinfo();')
XElement.new('child',
XNamespace.new('prefix', '/some/url'),
XAttribute.new('attribute', 'value'),
XElement.new('child',
XAttribute.new('attribute', 'value')
XElement.new('child',
XComment.new('some comment'),
)
)
),
XText.new('some text'),
)
)
# esempi di utilizzo di XPath
# il codice seguente calcola il prezzo totale di un ordine
total = XDocument.open(@order) do | doc |
doc.xpath('//item').inject(0) do | total, item |
price = item.child('price').to_n
quantity = item.child('quantity').to_n
total += price * quantity
end
end
# in maniera piu' compatta, sfruttando di piu' xpath
total = XDocument.open(@order) do | doc |
doc.xpath('//item').inject(0) do | total, item |
total += item.xpath('price * quantity')
end
end
# ancora piu' compatto, sfruttando delle estensioni non standard (mul)
total = XDocument.open(@order).xpath('sum(mul(//item/price, //item/quantity))')
# esempi di utilizzo di XUpdate (esempio originale e relativa traduzione)
# <xupdate:modifications version="1.0" xmlns:xupdate="http://www.xmldb.org/xupdate">
# <xupdate:insert-before select="/addresses/address[@id = 1]/name/last" >
# <xupdate:element name="middle">Lennox</xupdate:element>
# </xupdate:insert-before>
# </xupdate:modifications>
doc = XDocument.string(@xml).update do
select('/addresses/address[@id=1]/name/last') do | node |
node.insert_before(XElement.new('middle', 'Lennox'))
end
end
# <xupdate:modifications version="1.0" xmlns:xupdate="http://www.xmldb.org/xupdate">
# <xupdate:variable name="state" select="/addresses/address[@id = 1]/state"/>
# <xupdate:insert-after select="/addresses/address[@id = 1]/country">
# <xupdate:value-of select="$state"/>
# </xupdate:insert-after>
# </xupdate:modifications>
doc = XDocument.string(@xml).update do
select('/addresses/address[@id=1]') do | node |
node.child('country').insert_after(node.child('state').clone)
end
end
# <xupdate:modifications version="1.0" xmlns:xupdate="http://www.xmldb.org/xupdate">
# <xupdate:remove select="/addresses/address[@id = 1]/phone"/>
# </xupdate:modifications>
doc = XDocument.string(@xml).update do
remove('/addresses/address[@id=1]/phone')
end
# alcuni esempi di utilizzo di XQuery (esempio originale e traduzione)
# <ul> {
# for $x in doc("books.xml")/bookstore/book/title
# order by $x
# return <li>{$x}</li>
# }
# </ul>
element = XElement.new('ul',
XQuery.from(XDocument.open(@books).xpath('/bookstore/book/title')) do
order_by { | title | title.value }
select { | title | XElement.new('li', title) }
end
)
# supporto anche delle features non standard come la group_by (anche
# se un po' macchinosa), e la filter dove e' possibile manipolare
# tutto il result set attuale, in questo caso viene utilizzato per
# limitare il numero di risultati
XQuery.from(nodeset) do
order_by(:descending) { | selection |
selection.attribute('take').to_f
}
filter { | selections |
selections[1..15]
}
select { | selection |
puts selection.attribute('take').value
}
end
# in piu' con opportune estensioni di xpath (in questo caso le
# funzioni distinct-values e min) si riescono ad eseguire query
# di questo tipo
# <results>
# {
# let $doc := doc("prices.xml")
# for $t in distinct-values($doc//book/title)
# let $p := $doc//book[title = $t]/price
# return
# <minprice title="{ $t }">
# <price>{ min($p) }</price>
# </minprice>
# }
# </results>
doc = XDocument.open(@prices)
XElement.new('result',
XQuery.from(doc.xpath('distinct-values(//book/title)')) do
select do | title |
minprice = doc.xpath("min(//book[title = '#{title}']/price)")
XElement.new('minprice',
XAttribute.new('title', title),
XElement.new('price', minprice)
)
end
end
)
)
# esempio di Binding
# Definiamo per ogni attributo della classe News
# l'espressione XPath che una volta valutata costituirà
# il valore dell'attributo stesso
class News
extend XBind
xattribute :title => 'only(strings(*[@class = "title"]))'
xattribute :insert_date => 'only(dates(*[@class = "insert_date"]))'
xattribute :expire_date => 'only(dates(*[@class = "expire_date"]))'
xattribute :text => 'normalize-space(only(strings(*[@class = "text"])))'
end
# supponiamo di avere un xml di questo tipo
# <html>
# <head>
# <title>XPUG NEWS</title>
# </head>
# <body>
# <div class="news">
# <span class="title">First News</span>
# <span class="insert_date">20060404</span>
# <span class="expire_date">20060414</span>
# <div class="text">
# First News Text
# </div>
# </div>
# <div class="news">
# <span class="title">Second News</span>
# <span class="insert_date">20060403</span>
# <span class="expire_date">20060413</span>
# <div class="text">
# Second News Text
# </div>
# </div>
# <div class="news">
# <span class="title">Third News</span>
# <span class="insert_date">20060402</span>
# <span class="expire_date">20060412</span>
# <div class="text">
# Third News Text
# </div>
# </div>
# </body>
# </html>
# per ogni news presente nella pagina eseguiamo dei test
today = Date.today
XDocument.open('<file>.html').xpath('//*[@class = "news"]').each do | news_dom |
news = News.new(news_dom)
assert today <= news.expire_date, "'#{news.title}' should be expired at #{today}"
end
# news = News.new(news_dom)
# istanzia News
# valorizza gli attributi dell'oggetto valutando le espressioni xpath su news_dom
# crea i metodi di accesso agli attributi