Александр Неткачев
19.05.2004
Изучая новую технологию всегда хочется применить её для решения текущих задач. А одна из основных задач современного программиста - это составление программ, которые собирают данные из нескольких систем, обрабатывают их и выдают результат. Это напоминает сборку модели из готовых деталей конструктора, где роль крупных блоков играют, например, базы данных, а в качестве соединительных деталей используется простой и гибкий язык программирования.
Основными источниками данных в современном программировании являются текстовые файлы, базы данных и файлы в XML формате. А обрабатывать и соединять их друг с другом попробуем с помощью скриптового языка программирования Ruby.
Обработка текстовых файлов не представляет особой сложности, поскольку возможности Ruby в области поддержки регулярных выражений делают разбор любого текстового файла не очень сложной задачей. А вот обработку XML документов можно рассмотреть подробнее.
Для обработки XML существует как стандартное решение в виде rexml библиотеки, которая входит в Ruby 1.8, так и альтернативные варианты, которые в большинстве случаев представляют собой обертки (wrappers) вокруг С библиотек libxml2 и производных от неё.
Поиск расширений Ruby для обработки XML документов () приводит к множеству библиотек. Но отбросив все alpha, unstable, experimental и тому подобное получаем совсем небольшой список:
Я попробую сравнить найденные библиотеки между собой в решении простой задачи.
Программа 1. Генерация XML документа (generateXml.rb)
puts '<nodes>'
1000.times {|i| puts '<node sum="1" avg="' + i.to_s + '">Node sample text</node>'}
puts '</nodes>'
Задача
В сгенерированном программой 1 XML документе пройтись по всем узлам, посчитать сумму атрибутов sum и найти среднее значение атрибута avg.
Удобный API компенсируется недостатком производительности. Дело в том, что библиотека rexml написана на самом Ruby, производительность которого относительно C/C++ не велика. Также можно отметить, что XSL трансформацию библиотека rexml не поддерживает.
Программа 2. Решение задачи (parseXml_2.rb)
require "rexml/document"
include REXML
xmlStr = ''
ARGF.each {|line| xmlStr << line}
doc = Document.new xmlStr
sum = avgSum = count = 0
doc.elements.each('/nodes/node') { |e|
count += 1
sum += e.attributes['sum'].to_i
avgSum += e.attributes['avg'].to_i
}
puts "count(node): #{count}, sum(sum): #{sum}, avg(avg): #{avgSum/count}"
Определение времени выполнения и результат
$ time ruby generateXml.rb | ruby parseXml_2.rb count(node): 1000, sum(sum): 1000, avg(avg): 499 real 0m6.035s user 0m5.450s sys 0m0.590s
Итого - 6 секунд длился разбор документа на моем компьютере.
model name : Celeron (Mendocino) cpu MHz : 534.552 cache size : 128 KB bogomips : 1064.96
libgdome-ruby-0.3.tar.bz2 представляет собой оболочку вокруг Gdome2 библиотеки. Gdome2 - это реализация W3C DOM Level2 на C. Поэтому для установки libgdome-ruby надо сначала установить Gdome2 библиотеку.
Тут и пригодится Portage от Gentoo Linux:
$ emerge dev-libs/gdome2
Для ручной установки замечу, что Gdome2 зависит от библиотек libxml2 (.
Далее переходим к установке Ruby расширения libgdome-ruby:
$ tar -xjf libgdome-ruby-0.3.tar.bz2 $ cd libgdome-ruby-0.3 $ ruby extconf.rb $ make $ make install
Программа 3. Решение задачи с использованием Gdome2 (parseXml_3.rb)
require "gdome"
xmlStr = ''
ARGF.each {|line| xmlStr << line}
domImpl = Dom::implementation
doc = domImpl.createDocFromMemory(xmlStr, 0)
sum = avgSum = count = 0
children = doc.documentElement.childNodes
(0...children.length).each{ |i|
el = children.item(i)
if (el.kind_of?(Dom::Element))
count += 1
sum += el.getAttribute('sum').to_i
avgSum += el.getAttribute('avg').to_i
end
}
puts "count(node): #{count}, sum(sum): #{sum}, avg(avg): #{avgSum/count}"
Можно задать резонный вопрос: почему я не использовал XPath, для выборки узлов /Nodes/Node. Отвечаю - в библиотеке libgdome-ruby XPath не реализовали, хотя в самой Gnome2 XPath присутствует в полной мере. Отметим, что XSL трансформация не реализована в Gdom2 и, как следствие, в libgdome-ruby тоже.
Время выполнения задачи с использованием gdome значительно лучше, чем с rexml:
$ time ruby generateXml.rb | ruby parseXml_3.rb count(node): 1000, sum(sum): 1000, avg(avg): 499 real 0m0.334s user 0m0.300s sys 0m0.030s
Заявленая автором оболочки поддержка XPath и простые и напоминающие rexml интерфейсы весьма привлекательны. Библиотека зависит от libm (математические функции), libz (zlib), libiconv и, естественно, от libxml2. Как правило, все эти библиотеки в современных дистрибутивах есть, поэтому переходим без лишних слов к установке и реализации нашей задачи:
Устанавливаем скачаный файл libxml-0.3.4.tar.gz:
$ tar -xzf libxml-0.3.4.tar.gz $ cd libxml-0.3.4 $ ruby extconf.rb $ make && make install
Программа 4. Решение задачи с использованием libxml (parseXml_4.rb)
require 'xml/libxml'
xmlStr = ''
ARGF.each {|line| xmlStr << line}
xp = XML::Parser.new()
xp.string = xmlStr
doc = xp.parse
sum = avgSum = count = 0
doc.find('/nodes/node').each { |e|
count += 1
sum += e['sum'].to_i
avgSum += e['avg'].to_i
}
puts "count(node): #{count}, sum(sum): #{sum}, avg(avg): #{avgSum/count}"
Традиционный замер времени выполнения:
$ time ruby generateXml.rb | ruby parseXml_4.rb count(node): 1000, sum(sum): 1000, avg(avg): 499 real 0m0.210s user 0m0.180s sys 0m0.030s
Можно сказать только одно: Кубок победителю :-) Ruby libxml extention показал лучший результат по эффективности и удобству интерфейсов.
Для установки libxslt требуется, что бы libxml уже было установлено и header файлы находились в директории ../libxml относительно директории с libxslt.
$ ln -s libxml-0.3.4 libxml $ tar -xzf libxslt-0.3.4.tar.gz $ cd libxslt-0.3.4 $ ruby extconf.rb $ make && make install
Для примера предположим, что нам надо вывести список файлов в директории. Довольно простая задача, но при этом требуется разделить данные от представления. Это может понадобится, например, если дизайн представления будет менятся.
Примерная реализация: получаем список файлов в директории, строим из списка файлов XML и трансформируем его в HTML при помощи XSL.
XSL файл для трансформации (filesToHtml.xsl):
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<html><body><ul>
<xsl:for-each select="/files/file">
<li><xsl:value-of select="."/></li>
</xsl:for-each>
</ul></body></html>
</xsl:template>
</xsl:stylesheet>
Ruby скрипт, осуществляющий трансформацию (buildFileList.rb):
require 'xml/libxml'
require 'xml/libxslt'
xslt = XML::XSLT.file('filesToHtml.xsl')
xp = XML::Parser.new
# замечатальный пример компакности Ruby - выполнение shell комманды,
# проход по строкам её результата и составление XML в одной строке :-)
xp.string = `ls`.inject('<files>') { |xml, file|
xml << '<file>' << file.chomp << '</file>' } + '</files>'
xslt.doc = xp.parse
s = xslt.parse
s.apply
s.print
Проверяем производительность:
$ time ruby buildFileList.rb ... результат трансформации пропущен ... real 0m0.064s user 0m0.030s sys 0m0.030s
Резюмируя, можно сказать, что Ruby на данный момент обладает достаточными средствами для обработки XML документов и выполнения XSL трансформаций. Он соединяет лучшее, что было сделано в программировании - удобный синтаксис языка вместе с использованием существующих библиотек. Полученное в результате решение отличается простотой и легкостью в изучении, что делает его эффективным инструментом для программистов.
Продолжение следует...
Вы легко можете узнать о выходе продолжения, если подключитесь к сайта.
В продолжении:
Другие статьи по этой теме:
Статья взята с сайта .
[ опубликовано 30/12/2004 ]
Александр Неткачев - Обработка XML+XSL на Ruby