REXML — это процессор XML, написанный целиком на Ruby в полном соответствии со стандартом XML 1.0. Он не проверяет достоверность документа (соответствие схеме) и удовлетворяет всем тестам OASIS (Organization for the Advancement of Structured Information Standards - организация по внедрению стандартов структурирования информации) для таких процессоров.
Библиотека REXML предлагает несколько API. Сделано это, конечно, для того, чтобы обеспечить большую гибкость, а не внести путаницу. Два классических API — интерфейсы на базе DOM (объектной модели документа) и SAX (потоковый интерфейс). В первом случае весь документ считывается в память и хранится в древовидной форме. Во втором разбор осуществляется по мере чтения документа. Этот способ не требует загрузки документа в память и потому применяется, когда документ слишком велик, а память ограничена.
Во всех примерах мы будем использовать один и тот же XML-файл (см. листинг 15.1), представляющий часть описания личной библиотеки.
<library shelf='Recent Acquisitions'>
<section name='Ruby'>
<book isbn='0672328844'>
<title>The Ruby Way</title>
<author>Hal Fulton</author>
<description>Second edition. The book you are now reading.
Ain't recursion grand? </description>
</book>
</section>
<section name='Space'>
<book isbn='0684835509'>
<title>The Case for Mars</title>
<author>Robert Zubrin</author>
<description>Pushing toward a second home for the human
race. </description>
</book>
<book isbn='074325631X'>
<title>First Man: The Life of Neil A. Armstrong</title>
<author>James R. Hansen</author>
<description>Definitive biography of the first man on
the moon. </description>
</book>
</section>
</library>
15.1.1. Древовидное представление
Сначала покажем, как работать с ХМL-документом, представленным в виде дерева. Для начала затребуем библиотеку rexml/document
; обычно для удобства мы включаем также директиву include rexml
, чтобы импортировать все необходимое в пространство имен верхнего уровня. В листинге 15.2 продемонстрировано несколько полезных приемов.
require 'rexml/document'
include REXML
input = File.new('books.xml')
doc = Document.new(input)
root = doc.root
puts root.attributes['shelf'] # Недавние приобретения
doc.elements.each('library/section') { |e| puts e.attributes['name'] }
# Выводится:
# Ruby
# Space
doc.elements.each('*/section/book') { |e| puts e.attributes['isbn'] }
# Выводится:
# 0672328844
# 0321445619
# 0684835509
# 074325631X
sec2 = root.elements[2]
author = sec2.elements[1].elements['author'].text # Robert Zubrin
Обратите внимание: атрибуты представляются в виде хэша. Обращаться к элементам можно либо по пути, либо по номеру. В последнем случае учтите, что согласно спецификации XML индексация элементов начинается с 1, а не с 0, как в Ruby.
15.1.2. Потоковый разбор
А теперь попробуем разобрать тот же самый файл в потоковом стиле (на практике это вряд ли понадобилось бы, потому что размер файла невелик). У этого подхода несколько вариантов, в листинге 15.3 показан один из них. Идея в том, чтобы определить класс слушателя, методы которого анализатор будет вызывать для обработки событий.
require 'rexml/document'
require 'rexml/streamlistener'
include REXML
class MyListener
include REXML::StreamListener
def tag_start(*args)
puts 'tag_start: #{args.map {|x| x.inspect}.join(', ')}'
end
def text(data)
return if data =~ /^w*$/ # Ничего, кроме пропусков.
abbrev = data[0..40] + (data.length > 40 ? '...' : '')
puts ' text : #{abbrev.inspect}'
end
end