From: Горшков Никита <wishmaster.nick@mail.ru..>
Newsgroups: email
Date: Mon, 7 Feb 2008 14:31:37 +0000 (UTC)
Subject: Руководство по программированию на С++ с использованием libxml++
Автор: Murray Cumming <murrayc@murrayc.com..>
Перевод: Горшков Никита <wishmaster.nick@mail.ru..> <ICQ: 278224209>
Готов выслушать замечания и предложения по поводу перевода.
Версия перевода от 22.02.2008г.
Адрес оригинала: http://libxmlplusplus.sourceforge.net/docs/manual/html/index.htmlВступление
Это руководство по программированию на языке С++ с использованием
библиотеки libxml (с примерами).
Содержание
libxml++
- Установка
- UTF-8 и Glib::ustring
- Компиляция и линковка
Парсеры
- Парсер DOM
- Парсер SAX
- TextReader парсер
libxml++
libxml++ это С++ API для популярного XML-парсера libxml, написанное на
С. libxml отличается высокой производительностью и соответствием
основным стандартам, но его С API очень сложный, даже для простых
проектов. libxml++ является простым С++-подобным API, позволяющим
создавать простые проекты с простым исходным кодом.
В отличие от других С++-парсеров, он не требует придерживаться
стандартных особенностей С++, таких как пространства имен,
STL-контейнеры или идентификация типа времени выполнения (runtime type
identification, прим. пер.), и не требует соответствия API-стандартам
предназначенным для Java. Поэтому libxml++ требует современного С++
компилятора подобного g++ 3.
В основном, libxml++ создавался для удовлетворения потребности в
надежном API и надежном ABI (ABI - Application Binary Interface, прим.
пер.) С++ XML-парсере, который смогли бы использовать в бинарном виде
как динамически подключаемую библиотеку к приложениям написанным на
языке С++, распределенным по разным адресным пространствам. Поэтому,
при обновлении libxml++ на более новую версию, все программы
использующие старую версию продолжают работать. libxml++ API
развивается постепенно за счет добавления нового кода в не содержащий
ошибок код API, а также из-за возможности параллельного использования
нескольких версий ABI одновременно. Это основные технологии и
принципы, на которых основан проект [12]GNOME , частью которого
является libxml++.
Установка
libxml++ содержится во многих Linux и *BSD дистрибутивах, но также
может быть установлен из исходных текстов под Linux и Windows. Для
компиляции подойдет любой современный компилятор, например g++, SUN
Forte, или MSVC++.
Для примера, установим libxml++ и его документацию на платформу
Debian, используя apt-get (также подойдет synaptic):
# apt-get install libxml++2.6-dev libxml++2.6-doc
Чтобы узнать, установили ли вы пакет libxml++ или что он должным
образом функционирует, используйте команду
pkg-config libxml++-2.6 --modversion.
Исходные тексты могут быть загружены с сайта
http://libxmlplusplus.sourceforge.net.
libxml++ поставляется под лицензией LGPL, которая разрешает
использовать его как динамически подключаемую библиотеку в открытом и
закрытом программном обеспечении. Лежащая в основе библиотека libxml
использует еще более богатую лицензию MIT.
UTF-8 и Glib::ustring
libxml++ API понимает строки в кодировке UTF-8 Unicode, которая может
поддерживать все известные языки и локали. UTF-8 был выбран, потому
что из всевозможных кодировок ему часто отдают предпочтение. UTF-8 это
мульти-байтовая кодировка, при этом, используемое значение некоторых
символов более 1 байта. Но для совместимости, строки "старого стиля"
представленные 7-ми битной кодировкой ASCII остаются без изменений при
кодировании в UTF-8, а UTF-8 строки не содержат нулевой байт, который
бы мешал оценивать число байтов. По этим причинам, вы можете
"спрятать" UTF-8 строки в объект std::string. К тому же, std::string
API может оперировать со строками на основе байтов, вместо символов.
Так как Стандарт С++ не имеет строкового класса, который бы мог
полностью оперировать с UTF-8, libxml++ использует класс Glib::ustring
из библиотеки glibmm. Glib::ustring содержит практически полностью
похожее на std::string API, но методы такие, как length() и operator[]
оперируют с целыми символами UTF-8, а не с одиночными байтами.
Возможно скрытое преобразование между std::string и Glib::ustring,
поэтому, вы везде сможете использовать std::string в любом случае
видимый через Glib::ustring в API, но только, если вы действительно не
заботитесь о различных локалях на любом другом языке, отличном от
английского. Однако, это очень маловероятно в нашем сегодняшнем
связанном мире!
glibmm также поддерживает полезный API для преобразования между
кодировками и локалями.
Компиляция и линковка
Для использования libxml++ в своем приложении, вы должны сообщить
компилятору, где искать хидеры и библиотеку libxml++. Для упрощения
процедуры сборки, libxml++ снабжена файлом pkg-config.pc. Для примера,
следующая команда включает нужные опции компилятора:
pkg-config libxml++-2.6 --cflags --libs
В том случае, если вы используете autoconf и automake, то наиболее
просто их использовать с макросом PKG_CHECK_MODULES в вашем
конфигурационном файле configure.ac. Для примера:
PKG_CHECK_MODULES(SOMEAPP, libxml++-2.6 >= 2.10.0)
AC_SUBST(SOMEAPP_CFLAGS)
AC_SUBST(SOMEAPP_LIBS)
Парсеры
Также как и дочерняя библиотека libxml, libxml++ поддерживает три
парсера, которые вам могут потребоваться - DOM, SAX и TextReader
парсер. Их особенности и принципы работы с ними будут объяснены ниже.
Все эти парсеры могут анализировать XML-документы непосредственно на
диске, строки или C++ std::istream. Не смотря на то, что libxml++ API
использует только Glib::ustring, а следовательно кодировку UTF-8,
libxml++ может разбирать документы в любых кодировках, конвертируя в
UTF-8 автоматически. Это преобразование не приводит к потерям
какой-либо информации, потому что UTF-8 распознает любые локали.
Помните, что в XML-документах, как правило, "пустое место" играет
роль, поэтому парсеры могут находить неожиданные узлы, где текст
содержит "пустоту" или перевод каретки. Парсер не может знать, нужны
ли вам эти узлы или нет, но ваше приложение может игнорировать их.
Парсер DOM
Парсер DOM анализирует весь документ полностью и сохраняет его
структуру в памяти. Доступен через
Parser::get_document(). При помощи методов Document::get_root_node() и
Node::get_children(), вы можете перемещаться по иерархии дерева XML
без ограничений, перепрыгивая назад или вперед по документу
основываясь на информации, с которой вы сталкиваетесь в данный момент.
Поэтому парсер DOM использует большее количество памяти, чем
остальные.
Вы должны использовать C++ RTTI (через dynamic_cast<>) для определения
типа узла и выполнения действий, которые возможны с данным типом узла.
В примере только Element имеет атрибуты. Здесь представлена
наследственная иерархия типов узлов:
* xmlpp::Node:
+ xmlpp::Attribute
+ xmlpp::ContentNode
o xmlpp::CdataNode
o xmlpp::CommentNode
o xmlpp::ProcessingInstructionNode
o xmlpp::TextNode
+ xmlpp::Element
+ xmlpp::EntityReference
Несмотря на это, вы сможете получать указатели на Узлы, которые всегда
наследуются от своих родительских Узлов. В таком случае, это будет
означать, что данный Узел существует и ваш указатель на него будет
действительным пока экземпляр Документа существует.
Некоторые методы к тому же могут создавать новый дочерний Узел.
Используя его или один из методов Document::write_*(), вы можете
использовать libxml++ для построения нового XML-документа.
Пример
Этот пример анализирует документ, изучает его элементы и проверяет их.
Все приведенные здесь примеры содержатся в поставке исходных текстов
libxml++.
Source Code: http://libxmlplusplus.sourceforge.net/examples/dom_parser
File: main.cc
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <libxml++/libxml++.h>
#include <iostream>
void print_indentation(unsigned int indentation)
{
for(unsigned int i = 0; i < indentation; ++i)
std::cout << " ";
}
void print_node(const xmlpp::Node* node, unsigned int indentation = 0)
{
std::cout << std::endl; //Separate nodes by an empty line.
const xmlpp::ContentNode* nodeContent = dynamic_cast<const xmlpp::ContentNode*>(node);
const xmlpp::TextNode* nodeText = dynamic_cast<const xmlpp::TextNode*>(node);
const xmlpp::CommentNode* nodeComment = dynamic_cast<const xmlpp::CommentNode*>(node);
if(nodeText && nodeText->is_white_space()) //Let's ignore the indenting - you don't always want to do this.
return;
Glib::ustring nodename = node->get_name();
if(!nodeText && !nodeComment && !nodename.empty()) //Let's not say "name: text".
{
print_indentation(indentation);
std::cout << "Node name = " << node->get_name() << std::endl;
std::cout << "Node name = " << nodename << std::endl;
}
else if(nodeText) //Let's say when it's text. - e.g. let's say what that white space is.
{
print_indentation(indentation);
std::cout << "Text Node" << std::endl;
}
//Treat the various node types differently:
if(nodeText)
{
print_indentation(indentation);
std::cout << "text = \"" << nodeText->get_content() << "\"" << std::endl;
}
else if(nodeComment)
{
print_indentation(indentation);
std::cout << "comment = " << nodeComment->get_content() << std::endl;
}
else if(nodeContent)
{
print_indentation(indentation);
std::cout << "content = " << nodeContent->get_content() << std::endl;
}
else if(const xmlpp::Element* nodeElement = dynamic_cast<const xmlpp::Element*>(node))
{
//A normal Element node:
//line() works only for ElementNodes.
print_indentation(indentation);
std::cout << " line = " << node->get_line() << std::endl;
//Print attributes:
const xmlpp::Element::AttributeList& attributes = nodeElement->get_attributes();
for(xmlpp::Element::AttributeList::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
{
const xmlpp::Attribute* attribute = *iter;
print_indentation(indentation);
std::cout << " Attribute " << attribute->get_name() << " = " << attribute->get_value() << std::endl;
}
const xmlpp::Attribute* attribute = nodeElement->get_attribute("title");
if(attribute)
{
std::cout << "title found: =" << attribute->get_value() << std::endl;
}
}
if(!nodeContent)
{
//Recurse through child nodes:
xmlpp::Node::NodeList list = node->get_children();
for(xmlpp::Node::NodeList::iterator iter = list.begin(); iter != list.end(); ++iter)
{
print_node(*iter, indentation + 2); //recursive
}
}
}
int main(int argc, char* argv[])
{
Glib::ustring filepath;
if(argc > 1 )
filepath = argv[1]; //Allow the user to specify a different XML file to parse.
else
filepath = "example.xml";
try
{
xmlpp::DomParser parser;
parser.set_validate();
parser.set_substitute_entities(); //We just want the text to be resolved/unescaped automatically.
parser.parse_file(filepath);
if(parser)
{
//Walk the tree:
const xmlpp::Node* pNode = parser.get_document()->get_root_node(); //deleted by DomParser.
print_node(pNode);
}
}
catch(const std::exception& ex)
{
std::cout << "Exception caught: " << ex.what() << std::endl;
}
return 0;
}
Парсер SAX
Парсер SAX представляет каждый узел XML-документа как
последовательность. Так, когда вы обрабатываете один узел, вы можете
получить информацию обо всех предыдущих узлах, но, с другой стороны, у
вас не будет никакой информации о последующих узлах. Парсер SAX
использует меньше памяти, чем парсер DOM и подходит для пакетной
обработки документов.
При использовании метода parse_chunk()вместо parse(), вы можете
разбирать XML-документ по частям перед его общей обработкой.
Как показано в примере, вы должны получить ваш родительский класс из
SaxParser и заменить некоторые из виртуальных методов. Эти методы
"указателя" (в оригинале "handler" прим. пер.) будут вызваны, когда
документ проанализирован.
Пример
Этот пример показывает, как методы указателя вызываются во время
анализа.
Source Code: http://libxmlplusplus.sourceforge.net/examples/sax_parser
File: myparser.h
#ifndef __LIBXMLPP_EXAMPLES_MYPARSER_H
#define __LIBXMLPP_EXAMPLES_MYPARSER_H
#include <libxml++/libxml++.h>
class MySaxParser : public xmlpp::SaxParser
{
public:
MySaxParser();
virtual ~MySaxParser();
protected:
//overrides:
virtual void on_start_document();
virtual void on_end_document();
virtual void on_start_element(const Glib::ustring& name,
const AttributeList& properties);
virtual void on_end_element(const Glib::ustring& name);
virtual void on_characters(const Glib::ustring& characters);
virtual void on_comment(const Glib::ustring& text);
virtual void on_warning(const Glib::ustring& text);
virtual void on_error(const Glib::ustring& text);
virtual void on_fatal_error(const Glib::ustring& text);
};
#endif //__LIBXMLPP_EXAMPLES_MYPARSER_H
File: main.cc
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <fstream>
#include <iostream>
#include "myparser.h"
int
main(int argc, char* argv[])
{
Glib::ustring filepath;
if(argc > 1 )
filepath = argv[1]; //Allow the user to specify a different XML file to parse.
else
filepath = "example.xml";
// Parse the entire document in one go:
try
{
MySaxParser parser;
parser.set_substitute_entities(true); //
parser.parse_file(filepath);
}
catch(const xmlpp::exception& ex)
{
std::cout << "libxml++ exception: " << ex.what() << std::endl;
}
// Demonstrate incremental parsing, sometimes useful for network connections:
{
//std::cout << "Incremental SAX Parser:" << std:endl;
std::ifstream is(filepath.c_str());
char buffer[64];
MySaxParser parser;
do {
is.read(buffer, 63);
Glib::ustring input(buffer, is.gcount());
parser.parse_chunk(input);
}
while(is);
parser.finish_chunk_parsing();
}
return 0;
}
File: myparser.cc
#include "myparser.h"
#include <iostream>
MySaxParser::MySaxParser()
: xmlpp::SaxParser()
{
}
MySaxParser::~MySaxParser()
{
}
void MySaxParser::on_start_document()
{
std::cout << "on_start_document()" << std::endl;
}
void MySaxParser::on_end_document()
{
std::cout << "on_end_document()" << std::endl;
}
void MySaxParser::on_start_element(const Glib::ustring& name,
const AttributeList& attributes)
{
std::cout << "node name=" << name << std::endl;
// Print attributes:
for(xmlpp::SaxParser::AttributeList::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter)
{
std::cout << " Attribute " << iter->name << " = " << iter->value << std::endl;
}
}
void MySaxParser::on_end_element(const Glib::ustring& name)
{
std::cout << "on_end_element()" << std::endl;
}
void MySaxParser::on_characters(const Glib::ustring& text)
{
std::cout << "on_characters(): " << text << std::endl;
}
void MySaxParser::on_comment(const Glib::ustring& text)
{
std::cout << "on_comment(): " << text << std::endl;
}
void MySaxParser::on_warning(const Glib::ustring& text)
{
std::cout << "on_warning(): " << text << std::endl;
}
void MySaxParser::on_error(const Glib::ustring& text)
{
std::cout << "on_error(): " << text << std::endl;
}
void MySaxParser::on_fatal_error(const Glib::ustring& text)
{
std::cout << "on_fatal_error(): " << text << std::endl;
}
TextReader парсер
Подобно парсеру SAX, парсер TextReader подходит для пакетной
обработки, но вместо использования указателей на определенные части
документа, он позволяет определять тип текущей ветви, обработать ветвь
соответствующим образом и пропустить в документе столько, сколько
необходимо. Не так, как при использовании парсера DOM, вы не сможете в
XML-документе вернуться назад. С другой стороны, у вас не будет
расточительной по времени обработки ветвей, которые вам не нужны, что
наблюдается при использовании парсера SAX.
Все методы находятся в одном объекте синтаксического анализатора, но
их результат зависит от данной ситуации. Например, используйте
read()для перемещения к следующей текстовой ветви, а move_to_element()
для навигации по дочерним ветвям. Эти методы возвращают false, если
доступных узлов больше нет. Тогда используйте один из методов
get_name() и get_value() для проверки элементов и их атрибутов.
Пример
Этот пример проверяет каждый узел по очереди, а затем перемещается на
следующий.
Source Code: http://libxmlplusplus.sourceforge.net/examples/textreader
File: main.cc
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <libxml++/libxml++.h>
#include <libxml++/parsers/textreader.h>
#include <iostream>
struct indent {
int depth_;
indent(int depth): depth_(depth) {};
};
std::ostream & operator<<(std::ostream & o, indent const & in)
{
for(int i = 0; i != in.depth_; ++i)
{
o << " ";
}
return o;
}
int
main(int argc, char* argv[])
{
try
{
xmlpp::TextReader reader("example.xml");
while(reader.read())
{
int depth = reader.get_depth();
std::cout << indent(depth) << "--- node ---" << std::endl;
std::cout << indent(depth) << "name: " << reader.get_name() << std::endl;
std::cout << indent(depth) << "depth: " << reader.get_depth() << std::endl;
if(reader.has_attributes())
{
std::cout << indent(depth) << "attributes: " << std::endl;
reader.move_to_first_attribute();
do
{
std::cout << indent(depth) << " " << reader.get_name() << ": " << reader.get_value() << std::endl;
} while(reader.move_to_next_attribute());
reader.move_to_element();
}
else
{
std::cout << indent(depth) << "no attributes" << std::endl;
}
if(reader.has_value())
std::cout << indent(depth) << "value: '" << reader.get_value() << "'" << std::endl;
else
std::cout << indent(depth) << "novalue" << std::endl;
}
}
catch(const std::exception& e)
{
std::cout << "Exception caught: " << e.what() << std::endl;
}
}
Огромное спасибо Гришкову Никите за данную статью. Пробежал по статье - очень доходчиво описаны как DOM, так и SAX технологии разбора XML документа. Проработую на неделе весь материал. Долго искал что-то похожее для Windows, но в MSDN нашел только COM-парсер (тот который MSXML), ИМХО, неудобный он какой-то.
Привет всем, использую в своей программе DomParser.
Столкнулся с такой проблемкой.
Создаю строчку и передаю её parser.parse_memory_raw(...)
Если строка содержит правильный XML документ все работает хорошо, или ошибка в документе, все перехватывается по try {}catch(const xmlpp::exception& ex) {
Но когда передею в строчке произвольный набор символом на этапе парсинга, не генерируется исключение, а программы завершает свою работу выдавая результат об ошибке парсинга в консоль. Как избежать этого, как сделать так чтоб программа не закрывалась а можно было в ней обработать результат и продолжыть выполнение кода дальше?
Блин а нельзя было что-нибудь новое написать?
Например показать как с разделами cdata работать!
Весь интернет блин кишит этими ксероксами которые врядли чтото кроме копипасты сделать могут
>Блин а нельзя было что-нибудь новое написать?
>Например показать как с разделами cdata работать!
>Весь интернет блин кишит этими ксероксами которые врядли чтото кроме копипасты сделать
>могут
:) Да я в свое время тоже маялся. В итоге перевел (как смог) эту статью.
Вообще ничего по-русски не было. Уж ежели не нравится - либо по-английски изучайте, либо переводите сами :)