Un ejemplo de XML. Contenidos del fichero fellowship.xml
:
<?xml version="1.0" encoding="UTF-8" ?> <document> <title>The Fellowship of the Ring</title> <author>J. R. R. Tolken</author> <published>1954</published> <chapter> <title>A Long Expected Party</title> <content>When Mr. Bilbo Bagins from Bag End ...</content> </chapter> <chapter> <title>A Shadow Of The Past</title> <content>The talk did not die down ...</content> </chapter> <!-- etc --> </document>
Véanse:
rexml/document
:
#!/usr/bin/env ruby require 'rexml/document' filename = ARGV.shift || 'fellowship.xml' File.open(filename) do |f| doc = REXML::Document.new(f) author = REXML::XPath.first(doc, '/document/author') print "Author: " puts author.text # J. R. R. Tolken puts "Chapters:" REXML::XPath.each(doc, '/document/chapter/title') do |title| puts " "+title.text # A Long Expected Party end # A Shadow Of The Past end
Ejecución:
$ ruby find_author.rb Author: J. R. R. Tolken Chapters: A Long Expected Party A Shadow Of The Past
#!/usr/bin/env ruby require 'rexml/document' filename = ARGV.shift || 'fellowship.xml' File.open(filename) do |f| doc = REXML::Document.new(f) REXML::XPath.each(doc, '/document/author') do |author| author.text = 'J.R.R. Tolkien' end puts doc end
Aunque REXML
es fácil de usar, nos gustaría disponer de un poco mas de expresividad de manera
que podamos expresar un conjunto de acciones
que se ejecuten cuando se produce cierto matching.
El siguiente fragmento de código muestra una primera versión de nuestro DSL:
if $0 == __FILE__ ripper = XmlRipper.new do |r| puts "Compiling ..." r.on_path '/document/title' do |t| puts "Title: "+t.text end r.on_path '/document/author' do |a| puts "Author: "+a.text end r.action { puts "Chapters: " } r.on_path '/document/chapter/title' do |ct| puts " "+ct.text end end filename = ARGV.shift || 'fellowship.xml' ripper.run filename # Compiling ... # Title: The Fellowship of the Ring # Author: J. R. R. Tolken # Chapters: # A Long Expected Party # A Shadow Of The Past end
require 'rexml/document' class XmlRipper def initialize() @before_action = proc { } # do nothing @path_action = [] # Array: to preserve the order @after_action = proc { } yield self if block_given? end def on_path(path, &block) @path_action << [ path, block ] end def action(&block) @path_action << [ '', block ] end def before(&block) @before_action = block end def after(&block) @after_action = block end def run_path_actions(doc) @path_action.each do |path_action| path, action = path_action REXML::XPath.each(doc, path) do |element| action[element] end end end def run(file_name) File.open(file_name) do |f| doc = REXML::Document.new(f) @before_action[doc] run_path_actions(doc) @after_action[doc] end end end
Queremos evitar esas cacofonías de uso del objeto r
en la versión anterior (sección 15.12.2) y poder usarlo así:
[~/chapter8ReflectionandMetaprogramming/DSL/xmlripper]$ cat sample.xr puts "Compiling ..." on_path '/document/title' do |t| puts "Title: "+t.text end on_path '/document/author' do |a| a.text = "J.R.R. Tolkien" puts "Author: "+a.text end action { puts "Chapters: " } on_path '/document/chapter/title' do |ct| puts " "+ct.text end
Casi lo único que hay que cambiar en la versión anterior es el método
initialize
:
def initialize( path = nil, &block ) @before_action = proc { } # do nothing @path_action = [] # Array: to preserve the order @after_action = proc { } if path then instance_eval( File.read( path ), path, 1 ) else instance_eval( &block ) if block_given? end end
Si queremos que nuestro DSL pueda ser llamado especificando el contexto para que el bloque se ejecute podemos usar esta versión ligeramente mas complicada:
def initialize( path = nil, &block ) @before_action = proc { } # do nothing @path_action = [] # Array: to preserve the order @after_action = proc { } if path then instance_eval( File.read( path ) , path, 1) elsif block_given? if block.arity < 1 instance_eval( &block ) else block.call(self) end end end
En la misma forma en que trabajan rake
y rspec
podemos crear un ejecutable
que recibe como entrada un fichero escrito en el DSL XRipper
y ejecuta las acciones
asociadas:
[~/DSL/xmlripper]$ cat xripper #!/usr/bin/env ruby require 'xml_ripper_v2' scriptname = ARGV.shift xmlfile = ARGV.shift if scriptname and xmlfile and File.exists? scriptname and File.exists? xmlfile XmlRipper.new(scriptname).run(xmlfile) else puts "Usage:\n\t#$0 xripper_script file.xml" end
Ahora lo único que hay que hacer es ejecutar nuestro intérprete xripper
sobre un fichero
escrito en el DSL:
[~/chapter8ReflectionandMetaprogramming/DSL/xmlripper]$ cat sample.xr puts "Compiling ..." on_path '/document/title' do |t| puts "Title: "+t.text end on_path '/document/author' do |a| a.text = "J.R.R. Tolkien" puts "Author: "+a.text end action { puts "Chapters: " } on_path '/document/chapter/title' do |ct| puts " "+ct.text end
Rakefile
:
[~/DSL/xmlripper]$ cat Rakefile task :default => :run desc "run xripper " task :run do sh "ruby -I. xripper sample.xr fellowship.xml" endLa ejecución produce esta salida:
[~/chapter8ReflectionandMetaprogramming/DSL/xmlripper]$ rake ruby -I. xripper sample.xr fellowship.xml Compiling ... Title: The Fellowship of the Ring Author: J.R.R. Tolkien Chapters: A Long Expected Party A Shadow Of The Past
Casiano Rodriguez León 2015-01-07