samedi 29 mars 2014

Les bases NoSQL pour stocker du XML

Pendant de nombreuses années le XML tenait le haut du pavé dans le domaine de format de fichier: pour les données , les configurations etc. Le XML était partout:

Source : https://delta-xi.net/gfx/xml-landscape.gif

Depuis, on revient à un usage plus raisonnable du XML. Et des formats comme le JSON, le YAML ont  maintenant trouvés leur place.

Comment stocker efficacement des fichiers XML ?.

La solution habituelle.

La solution basique consiste à extraire les informations pertinentes du XML pour les sérialiser dans une base de données. Le format XML se prête mal a des opérations de recherche.
Cette solution à ses limites surtout pour des enregistrements XML de taille variable. L'exemple qui vient à l'esprit est celui de la récente réforme des échanges bancaires: le SEPA.
D'un format fixe de 240 caractères , les nouvelles normes ont évoluées sur des articles XML de taille variable. Le volume est plus important en raison de l'utilisation du XML, du détails des opérations mais d'une taille non prédictible.

 Des solutions à base de NoSQL.


Le principe de ces solutions est de stocker les fichiers XML échangés dans  une base de donnée Nosql. Le choix de cette base se portera sur un dispositif orienté document comme MongoDB.
source:
http://blogs.the451group.com/information_management/2011/04/15/nosql-newsql-and-beyond/

Les informations seront stockées sous deux formes:
Sous la forme d'origine en XML et sous un format JSON avec une succession de  clé / valeur. Les opérations de recherche se feront sur la partie JSON.

Ci-dessous un exemple de programme en Ruby utilisant la librairie nokogiri pour parser le XML:

## cas primaire
require 'json'
def cas_general (filename, owner, node)
ch ={}
ch[:node] =[]
ch[:owner] = owner
owner =nil
ch_ent = {}
owner ||= node.attr('name')
owner ||= node.attr('cobolrecordname')
node.keys.each do |t|
ch_ent[t.to_sym] = ch[t.to_sym] = node.attr(t) if !node.attr(t).empty?
end
ch_ent[:name] = ch[:name] = node.attr('cobolrecordname') if !ch[:name]
if node.children.size > 0 then
ch_ent[:nature] = ch[:nature] = 'groupe'
node.children.each do |n|
ch[:node] << cas_general(filename,owner,n)
end
else
ch_ent[:nature] =ch[:nature] = 'simple'
ch.delete(:node)
end
ch_ent[:filename] = filename
puts filename
ch_ent[:size] =ch_ent[:size].to_i if ch_ent.has_key?(:size)
ch_ent[:decimal] =ch_ent[:decimal].to_i if ch_ent.has_key?(:decimal)
ch_ent[:occurs] =ch_ent[:occurs].to_i if ch_ent.has_key?(:occurs)
ch_ent.keys.each do |t|
ch_ent[t] = false if ch_ent[t] == 'false'
ch_ent[t] = true if ch_ent[t] == 'true'
end
ts =ch_ent.to_json
puts ts
return ch
end
###
require 'nokogiri'
Dir.glob("copybooks/*.xml").each do |filename|
f= File.open(filename)
doc = Nokogiri::Slop (f)
f.close
copy = doc.document.html.body.children.children[0]
### le premier niveau est traite particuliairement
ch = {}
ch[:node]=[]
ch[:node] << cas_general(filename, 'root',copy)
end
view raw XML2json hosted with ❤ by GitHub
Le fichier XML est exploré récursivement  puis injecté dans une base NoSQL mongoDB par ce script:

require 'mongo'
require 'json'
include Mongo
mongo_client = MongoClient.new("localhost",27017)
filecontent = ARGV.pop
content = File.readlines(filecontent)
db = mongo_client.db("pocpaye")
entites = db.collection("entites")
content.each do |mt|
puts mt.inspect
#item = JSON.pretty_generate(mt)
# puts item
ts =JSON.parse(mt)
entites.insert(ts)
puts ts
end
view raw injecteur hosted with ❤ by GitHub

Le fichier XML sera injecté par ailleurs.

Ainsi l'information sera disponible sous deux formes avec une des formes autorisant des recherches multicriteres. Le stockage dans un système sans schéma est particulièrement adapté à des informations hétérogènes.