Nous allons découvrir dans ce billet une petite librairie bien sympathique pour Ruby qui vous sera surement très utile. Après une brève description, nous verrons comment l’utiliser dans ses fonctionnalités de bases. Nous nous focaliserons essentiellement sur le côté XML.
Nokogiri en quelques mots
Cette librairie a été créée par Aaron Patterson et Mike Dalessio. C’est un parseur HTML/XML/XSLT pour Ruby qui se sert de XPATH et/ou de CSS3. Elle est basée sur le modèle Hpricot et elle utilise libXML2 (et libXSLT) pour chercher et parser (d’où sa rapidité).
Vous utilisez peut-être toujours la librairie pure Ruby REXML, ou bien vous avez le soucis de la rapidité et vous êtes déjà passé à Hpricot ? Allez, allez il est temps de changer. La transition n’est pas trop dure de Hpricot vers Nokogiri (je vous rappelle que c’est le même modèle, donc vous n’avez quasiment rien à faire). Et de REXML à Nokogiri ? Très peu de changements, la syntaxe est assez proche mais Nokogiri est tellement plus élégant ;-] (et fait tellement plus de choses, et va tellement plus vite, et est tellement so cute!). Tentez le changement, vous allez aimer !
C’est vraiment rapide ?
C’est tout simple, d’un côté on a les escargots asthmatiques, de l’autre les libellules supraluminiques :-]. Comment ça j’exagère ? On n’en est pas loin.
Regardez ces benchmarks réalisés par RubyInside (ils ne couvrent pas tout, mais pour les fonctions de bases c’est suffisant, pour info l’axe des y est en seconde) :

Et puis, n’hésitez pas à faire vos propres benchmarks, il n’y a que ça de mieux pour être convaincu !
Personnellement pour des codes que nous utilisons, pour le chargement d’un arbre XML d’un millier de noeuds à deux/trois étages, nous passons d’une dizaine de secondes (REXML) à moins d’une seconde (Nokogiri). La classe :p. Ne parlons même pas de la recherche ou de l’écriture dans un fichier.
Même pour un tout petit mini document (une cinquantaine de noeuds sur deux étages), vous allez me dire : « on ne va pas s’embêter pour ça… » (au passage, l’embêtement => sudo gem install nokogiri). Ok, mais c’est quand même plus rapide de deux dixièmes de secondes facilement (vous passez d’un centième de seconde à un dix millième hop !), et puis, il faut prendre les bonnes habitudes :-].
Bref aperçu
On va voir rapidement les fonctionnalités de base. Afin que vous puissiez tester rapidement et facilement ces codes, j’ai mis tout dans une même classe. Parfois c’est un peu acrobatique, mais le but c’est de montrer les fonctionnalités :p.
Voici la classe dans laquelle nous évoluerons :
require 'nokogiri'
class XMLNokogiri
attr_accessor :myDoc
def initialize(myFileName)
loadXML(myFileName)
end
end
L’équivalent en REXML pour comparer :
require 'rexml/document'
include REXML
class XMLREXML
attr_accessor :myDoc
def initialize(myFileName)
loadXML(myFileName)
end
end
Et voici le fichier XML que l’on prendra en exemple :
Charger un arbre XML en mémoire
Pour charger le document en mémoire avec Nokogiri :
def loadXML(myFileName)
begin
myFile = File.new(myFileName)
rescue
puts "Can't open the file. Please check the name: " + myFileName + ". Try it again: "
myFileName = gets.chomp
retry
end
@myDoc = Nokogiri::XML(myFile)
end
Avec REXML, c’était juste la syntaxe de la ligne qui crée l’arbre qui changeait :
@myDoc = Document.new(myFile)
Affichage basique des éléments
Globalement j’affiche l’arbre, mais le but est plutôt de montrer comment accéder aux différents éléments (de manière basique). Après dans cet exemple je les affiche, mais vous pouvez en faire ce que vous voulez.
def readXML()
# Get a node (or many)
for gecko in @myDoc.root.xpath("//gecko")
# Get an attribute
puts gecko['name']
# Get a text
puts "t" + gecko.xpath("./espece").text
puts "t" + gecko.xpath("./periode").text
puts "t" + gecko.xpath("./region").text
puts "t" + gecko.xpath("./taille").text
puts "t" + gecko.xpath("./couleur").text
puts "n"
end
end
Avec REXML, on pouvait avoir :
# Get a node (or many)
for gecko in @myDoc.root.elements
# Get an attribute
puts gecko.attributes['name']
# Get a text
puts "t" + gecko.elements["espece"].text
puts "t" + gecko.elements["periode"].text
puts "t" + gecko.elements["region"].text
puts "t" + gecko.elements["taille"].text
puts "t" + gecko.elements["couleur"].text
puts "n"
end
Chercher un noeud
Pour Nokogiri, je le fais avec XPATH, mais il y a d’autres moyens.
def searchNode(xpathExpr)
myNode = @myDoc.at(xpathExpr)
if(myNode == nil)
puts "Not found..."
else
puts myNode.to_xml
return myNode
end
end
Avec REXML, seule la ligne de recherche était un peu différente (attention la méthode to_xml ne marche pas pour REXML) :
myNode = @myDoc.elements.to_a(xpathExpr)
Ajouter un noeud
Je distingue deux cas, ajouter un noeud à la racine et ajouter un noeud n’importe où.
def insertAChildNode(docPosition, myNode)
docPosition.add_child(myNode)
end
def addARootNode(myNode)
@myDoc.root = myNode
end
Pour REXML, on utilisait :
# Insert a node in the XML doc
docPosition.add_element(myNode)
# Insert a root node in the XML doc
@myDoc.add_element(myNode)
Créer un nouveau noeud
Pour créer un nouveau noeud avec Nokogiri (avant de l’ajouter)
def createANewNode(name)
return Nokogiri::XML::Node.new(name, @myDoc)
end
Avec REXML, on faisait :
return Element.new(name)
Ajouter un texte et des attributs
Pour ajouter du texte ou un attribut avec Nokogiri :
def addAnAttribute(myNode, name, value)
myNode[name] = value
end
def addText(myNode, text)
myNode.content = text.to_s
end
Avec REXML :
# Add attribute
myNode.add_attribute(name, value)
# Add text
myNode.text = text.to_s
Créer un nouveau document
Avec Nokogiri :
def createANewDoc()
myNewDoc = Nokogiri::XML::Document.new
end
Avec REXML c’était à peu près pareil :
myNewDoc = Document.new
Sauvegarder l’arbre XML dans un fichier
Et pour finir, voici comment sauvegarder l’arbre dans un fichier avec Nokogiri :
def saveToFile(myfileName)
begin
myFile = File.new(myfileName)
rescue
end
myFile = File.open(myfileName, 'w')
@myDoc.write_xml_to(myFile, :indent => 4, :encoding => 'UTF-8')
myFile.close
end
Avec REXML, la ligne d’écriture était différente :
@myDoc.write(myFile, 4)
Utilisation
Maintenant vous pouvez utiliser ces fonctions de base (vous pouvez en faire de même avec celle en REXML). En vrac par exemple :
# Load the XML doc
myXMLNokogiri = XMLNokogiri.new("./Gekkonidae.xml")
# Print the XML doc
myXMLNokogiri.readXML()
# Get the node of the gecko named Green
myXMLNokogiri.searchNode('//gecko[@name = "Green"]')
# Add a new gecko node with an attribute name Color
myNewNode = myXMLNokogiri.createANewNode('gecko')
myXMLNokogiri.addAnAttribute(myNewNode, 'name', 'Color')
myXMLNokogiri.addARootNode(myNewNode)
# Add a node couleur to the node we have just added
myNewNode = myXMLNokogiri.createANewNode('couleur')
myXMLNokogiri.addText(myNewNode, 'Multicolor')
myNode = myXMLNokogiri.searchNode('//gecko[@name = "Color"]')
myXMLNokogiri.insertAChildNode(myNode, myNewNode)
# We create a new doc and save it at the place of the old one (yeah, it's just to test eh :p)
myXMLNokogiri.myDoc = myXMLNokogiri.createANewDoc()
# We create a new node with an attribute
myNewNode = myXMLNokogiri.createANewNode('gecko')
myXMLNokogiri.addAnAttribute(myNewNode, 'name', 'Geckogeek')
# We create a new node and add it to the gecko node we have created
myNewChildNode = myXMLNokogiri.createANewNode('espece')
myXMLNokogiri.addText(myNewChildNode, 'Geek')
myXMLNokogiri.insertAChildNode(myNewNode, myNewChildNode)
# We add the gecko node to our new tree
myXMLNokogiri.addARootNode(myNewNode)
# We save this tree in a file
myXMLNokogiri.saveToFile("./GekkonidaeGeek.xml")
Récapitulatif
Voici en un seul tenant la classe Nokogiri que nous venons d’écrire :
require 'nokogiri'
class XMLNokogiri
attr_accessor :myDoc
def initialize(myFileName)
loadXML(myFileName)
end
def loadXML(myFileName)
begin
myFile = File.new(myFileName)
rescue
puts "Can't open the file. Please check the name: " + myFileName + ". Try it again: "
myFileName = gets.chomp
retry
end
@myDoc = Nokogiri::XML(myFile)
end
def readXML()
# Get a node (or many)
for gecko in @myDoc.root.xpath("//gecko")
# Get an attribute
puts gecko['name']
# Get a text
puts "t" + gecko.xpath("./espece").text
puts "t" + gecko.xpath("./periode").text
puts "t" + gecko.xpath("./region").text
puts "t" + gecko.xpath("./taille").text
puts "t" + gecko.xpath("./couleur").text
puts "n"
end
end
def searchNode(xpathExpr)
myNode = @myDoc.at(xpathExpr)
if(myNode == nil)
puts "Not found..."
else
puts myNode.to_xml
return myNode
end
end
def insertAChildNode(docPosition, myNode)
docPosition.add_child(myNode)
end
def addARootNode(myNode)
@myDoc.root = myNode
end
def createANewNode(name)
return Nokogiri::XML::Node.new(name, @myDoc)
end
def addAnAttribute(myNode, name, value)
myNode[name] = value
end
def addText(myNode, text)
myNode.content = text.to_s
end
def createANewDoc()
myNewDoc = Nokogiri::XML::Document.new
end
def saveToFile(myfileName)
begin
myFile = File.new(myfileName)
rescue
end
myFile = File.open(myfileName, 'w')
@myDoc.write_xml_to(myFile, :indent => 4, :encoding => 'UTF-8')
myFile.close
end
end
Voilà vous avez les bases pour vous lancer dans cette librairie ! N’hésitez surtout pas à l’utiliser ;-] ça serait dommage de passer à côté. Vous pouvez aussi aller jeter un oeil sur le site officiel si ça vous dit.




