Нажмите для просмотра прикрепленного файлаВ общем я немного заморочился на эту тему и написал класс, который назвал по-страшному - xmlstreamreaderhelper
Несмотря на то, что понять как он работает не просто я постараюсь все же объяснить суть. Все построено на полиморфизме, чтобы избежать дублирование кода. Есть 2 класса:
- AbstractTagImplementator
- AbstractTagProcessor
Класс AbstractTagProcessor наследует класс AbstractTagImplementator и определяет в себе ряд методов для работы с тэгами xml'я. В то время как класс AbstractTagImplementator определяет общий функционал парсера.
Если разбирать мой xml из прошлых постов, то его парсинг теперь выглядит так:
//определяем в заголовке набор классов, каждый из которых представление необходимых тегов
//.h
class TourML : public AbstractTagProcessor
{
public:
TourML(const QString &name = QString(), AbstractTagImplementator *parent = 0);
bool beforeEvent();
void afterEvent();
private:
QTime t;
};
class Country : public AbstractTagProcessor
{
public:
Country(const QString &name = QString(), AbstractTagImplementator *parent = 0);
bool beforeEvent();
};
class Packet : public AbstractTagProcessor
{
public:
Packet(const QString &name = QString(), AbstractTagImplementator *parent = 0);
bool beforeEvent();
};
В этих классах переопределяются так называемые эвенты базовых классов. Всего 3 эвента: beforeEvent(), event(), afterEvent(). В зависимости от сложности парсера переопределяются нужные. В простом случае нужен только beforeEvent(), а вот если нужно будет перебрать все существующие теги или обработать неизвестные, или просто хочется реализовать свой функционал иначе, то надо переопределять метод event().
//реализация классов тегов
//.cpp
TourML::TourML(const QString &name, AbstractTagImplementator *parent) :
AbstractTagProcessor(name, parent)
{
}
bool TourML::beforeEvent()
{
t.start();
moveToTag("TourML");
if (xml()->attributes().value("version") != "1.0") {
xml()->raiseError(QObject::tr("The file is not an TourML version 1.0 file."));
return true;
}
return false;
}
void TourML::afterEvent()
{
qDebug() << t.elapsed() << "ms.";
}
Country::Country(const QString &name, AbstractTagImplementator *parent) :
AbstractTagProcessor(name, parent)
{
}
bool Country::beforeEvent()
{
qDebug() << xml()->tokenString() << xml()->name() << name() << "Country: " << xml()->attributes().value(QLatin1String("nameLat")).toString();
return true; // interrupt parse
}
Packet::Packet(const QString &name, AbstractTagImplementator *parent) :
AbstractTagProcessor(name, parent)
{
}
bool Packet::beforeEvent()
{
QString packet;
moveToTag(QLatin1String("tour"));
packet = xml()->attributes().value(QLatin1String("name")).toString();
moveToTag(QLatin1String("spo"));
packet += " (" + xml()->attributes().value(QLatin1String("issue")).toString() + ')';
skipSubTree();
moveToTag(QLatin1String("priceQuantity"));
packet += " [" + xml()->readElementText() + ']';
qDebug() << packet;
return true; //parse next tag
}
//Подготовка и вызов парсера
//.cpp
QString fileName = "file.xml";
QFile file(fileName);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QMessageBox::warning(this, tr("Xml Parser"),
tr("Cannot read file %1:\n%2.")
.arg(fileName)
.arg(file.errorString()));
return;
}
reader = QSharedPointer<QXmlStreamReader>(new QXmlStreamReader(&file));
tourMLparser = QSharedPointer<TourML>(new TourML("TourML"));
TourML::setXmlReader(reader.data());
AbstractTagImplementator references("references", tourMLparser.data());
Country country("country", &references);
AbstractTagImplementator sources("sources", tourMLparser.data());
Packet packet("packet", &sources);
if (!tourMLparser->parse() && tourMLparser->xml()->hasError() && tourMLparser->xml()->error() != QXmlStreamReader::PrematureEndOfDocumentError) {
qDebug() << "Error when parsing data from socket.";
}
Т.е. открываем файл, создаем объект QXmlStreamReader, которому и передаем device (file). Затем создается родительский объект TourML, и по аналогии с QObject'ами тэги-классы выстраиваются в дерево. Потом на корневом теге вызывается метод parse(), который уже проходит по всему дереву детей. Если в процессе парсинга любого тэга xml прервется, при передаче по сокету например, то запускается локальный QEventLoop и ожидает новые данные с устройства. Если они приходят, то парсинг продолжается дальше. Я пробовал генерировать бесконечный поток xml'я по сокету, парсер работает не прерываясь.
Имена, которые передаются в конструкторы объектов - реальные названия тегов в xml'е чувствительные к регистру. Еще вот этот код прокомментирую:
AbstractTagImplementator references("references", tourMLparser.data());
В этом случае тэг "references" является всего-лишь "чекпоинтом", нам тег не нужен сам по себе, но мы должны указать короткую дорогу парсеру, чтобы он мог найти тег "country", поэтому нам нет необходимости наследоваться от класса AbstractTagImplementator и мы используем его базовый функционал, которого достаточно. С другой стороны мы могли бы отказаться от этого чекпоинта и парсер бы все равно нашел нужный тег, однако в разных ситуациях это может замедлить скорость парсинга, т.к. заставит парсер сравнивать имена ключей, которые мы могли бы отмести заранее. А в случае, когда у разных подветок имеются ключи с одинаковым названием это может привести к путанице и невозможности узнать какой из подветок принадлежит тег, чтобы соотнести его.
Как по мне, так это выглядит более правильно, чем мой первоначальный вариант:
Q_ASSERT(isStartElement() && name() == QLatin1String("TourML"));
while (!atEnd()) {
readNext();
if (isStartElement()) {
if (name() == QLatin1String("references")) {
while (!(isEndElement() && name() == QLatin1String("references"))) {
readNext();
if (isStartElement() && name() == QLatin1String("countries")) {
while (!(isEndElement() && name() == QLatin1String("countries"))) {
readNext();
if (isStartElement() && name() == QLatin1String("country"))
qDebug() << attributes().value(QLatin1String("nameLat"));
}
}
}
} else if (name() == QLatin1String("sources")) {
while (!(isEndElement() && name() == QLatin1String("sources"))) {
readNext();
if (isStartElement() && name() == QLatin1String("source")) {
while (!(isEndElement() && name() == QLatin1String("source"))) {
readNext();
if (isStartElement() && name() == QLatin1String("packets")) {
while (!(isEndElement() && name() == QLatin1String("packets"))) {
readNext();
if (isStartElement() && name() == QLatin1String("packet")) {
while (!(isEndElement() && name() == QLatin1String("packet"))) {
readNext();
if (isStartElement() && name() == QLatin1String("packetHeader")) {
while (!(isEndElement() && name() == QLatin1String("packetHeader"))) {
readNext();
if (isStartElement()) {
if (name() == QLatin1String("tour")) {
qDebug() << attributes().value(QLatin1String("name"));
} else if (name() == QLatin1String("spo")) {
qDebug() << attributes().value(QLatin1String("issue"));
} else if (name() == QLatin1String("spoInfo")) {
while (!(isEndElement() && name() == QLatin1String("spoInfo"))) {
readNext();
if (isStartElement() && name() == QLatin1String("priceQuantity"))
qDebug() << readElementText();
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
К тому же замеры скорости не показали каких либо отличительных различий. Классы на C++ я проектирую впервые поэтому был бы рад узнать где я сделал что-то не так и как этом можно улучшить.
Есть мысль написать кодо-генератор для парсера, где можно было бы загрузить дерево xml'я и проставляя галочки выбрать путь для парсера.
Выкладываю для критики и дополнений исходники.
Нажмите для просмотра прикрепленного файла