REXML
14/12/2018
Сегодня мой друг спросил: “Есть ли возможность парсить XML в Ruby из коробки?”. Несмотря на огромную популярность и де-факто стандарт Nokogiri, в закромах стандартной библиотеки Ruby таки есть классы для парсинга XML – REXML. Давайте пощупаем их за вымя и в будущем лишний раз не будем ставить “пол мира” для того, чтобы обработать несколько конфигурационных XML файлов. Для начала пройдемся по дереву элементов и выведем в STDOUT строкое представление каждого из узлов используя поиск в глубину:
require 'rexml/document'
xml = <<-XML
<a>
<b one="1">X</b>
<b two="2">
<c>Y</c>
</b>
<b three="3">Z</b>
</a>
XML
doc = REXML::Document.new(xml)
# DFS
traverse = -> (node) do
p node
return if node.elements.empty?
node.elements.each { |node| traverse.call(node) }
nil
end
traverse.call(doc.root)
#=> <a> ... </>
#=> <b one='1'> ... </>
#=> <b two='2'> ... </>
#=> <c> ... </>
#=> <b three='3'> ... </>
А теперь пройдемся по самому элементу. Возьмем, например, элемент <b>
с аттрибутом two
через XPath и посмотрим, что возвращают разные методы на нем:
require 'rexml/xpath'
node = REXML::XPath.first(doc.root, '/*/b[@two]')
#=> <b two='2'> ... </>
node.attributes
#=> { "two" => "2" }
node.elements
#=> #<REXML::Elements:0x00007fb3df8a19d0 @element=<b two='2'> ... </>>
node.elements.count
#=> 1
node.elements.first
#=> <c> ... </> # Любопытно, результат отображается не строковым литералом
node.elements.first.text
#=> "Y"
node.text('c') # text работает с XPath
#=> "Y"
node.node_type
#=> :element
node.get_text('c').node_type
#=> :text
Так же есть еще куча классов для манипулирования деревом элементов, написания всевозможных парсеров, валидации документа. Попробуем на закуску имплементировать простенький стримовый парсер:
require 'rexml/parsers/sax2parser'
parser = REXML::Parsers::SAX2Parser.new(xml)
parser.listen(:start_element) do |uri, localname, qname, attributes|
puts "START: #{localname}"
end
parser.listen(:end_element) do |uri, localname, qname|
puts "END: #{localname}"
end
parser.parse
#=> START: a
#=> START: b
#=> END: b
#=> START: b
#=> START: c
#=> END: c
#=> END: b
#=> START: b
#=> END: b
#=> END: a
REXML присутствует с давних времен (1.8.7), следовательно это хороший выбор для тех, кто поддерживает старые версии Ruby.