crossplatform.ru

Здравствуйте, гость ( Вход | Регистрация )

> QXmlStreamReader и большое количество уровней
SABROG
  опции профиля:
сообщение 13.7.2009, 14:50
Сообщение #1


Профессионал
*****

Группа: Участник
Сообщений: 1207
Регистрация: 8.12.2008
Из: Russia, Moscow
Пользователь №: 446

Спасибо сказали: 229 раз(а)




Репутация:   34  


Решил попробовать класс QXmlStreamReader. Изучив пример streambookmarks я реализовал свой парсер. И вот что мне не понравилось. Можно написать простенький код, который будет сравнивать каждое название ключа на токен isStartElement с нужным нам. Недостаток такого метода - большое количество операций сравнений и падение скорости. Зато очень просто, когда ключи имеют уникальные имена, не нужно контролировать вложенность, родительские отношения. Второй вариант (правильный). На каждый уровень дерева выделяется отдельный метод с while(!atEnd()). Подобное выделение требует создание нового метода для каждого такого уровня. Если глубина дерева в xml'e большая, то можно себе представить какое огромное количество методов получится! Например у меня глубина дерева такая:

tourml/sources/source/packets/packet/packetheader/spo/dates/date


Т.е. 9 методов (на каждый уровень) и в каждом по циклу while(). При этом меня совершенно не интересуют промежуточные уровни, только последние.

Теперь сижу и думаю может сделать какие-нибудь указатели на методы, которые будут вызываться по очереди на одном while. Скажем QQueue/QHash с указателем на метод и строкой сравнения.
---
Только вот на самом деле там даже больше 9и методов получится, т.к. в xml'е еще другие блоки есть, такой например:

tourml/references/tourtypes/tourtype


Сообщение отредактировал SABROG - 13.7.2009, 14:59
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
 
Начать новую тему
Ответов
SABROG
  опции профиля:
сообщение 29.8.2009, 14:40
Сообщение #2


Профессионал
*****

Группа: Участник
Сообщений: 1207
Регистрация: 8.12.2008
Из: Russia, Moscow
Пользователь №: 446

Спасибо сказали: 229 раз(а)




Репутация:   34  


Прикрепленный файл  xmlstreamreaderhelper.zip ( 2.43 килобайт ) Кол-во скачиваний: 306


В общем я немного заморочился на эту тему и написал класс, который назвал по-страшному - 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'я и проставляя галочки выбрать путь для парсера.

Выкладываю для критики и дополнений исходники.

Прикрепленный файл  xmlstreamreaderhelper.zip ( 2.43 килобайт ) Кол-во скачиваний: 306
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение

Сообщений в этой теме
- SABROG   QXmlStreamReader и большое количество уровней   13.7.2009, 14:50
- - ViGOur   Код в студию, можно занятся оптимизаторством и уни...   13.7.2009, 15:56
|- - SABROG   Цитата(ViGOur @ 13.7.2009, 16:56) Код в с...   13.7.2009, 17:45
- - ViGOur   Жесть! А почему нельзя сделать рекурсию с соз...   13.7.2009, 19:29
- - SABROG   Цитата(ViGOur @ 13.7.2009, 20:29) А почем...   13.7.2009, 20:28
- - SABROG   Да уж, что-то не очень получается. Если делать рек...   14.7.2009, 14:25
- - SABROG   В общем посмотрел я десяток примеров использования...   14.7.2009, 22:40
- - ViGOur   Ты немного не понял, рекурсия c примерно таким усл...   15.7.2009, 10:32
- - SABROG   Да оно всё это понятно, я тоже об этом говорю. Сам...   15.7.2009, 11:14
- - ViGOur   Так в том то и дело, что в твоем случае как я поня...   15.7.2009, 12:24
- - Tonal   То, что тут пытаетесь изобразить, называется конеч...   15.7.2009, 12:38
- - SABROG   Как я и писал выше затык будет в заполнении таблиц...   15.7.2009, 13:16
- - SABROG   Такой вопрос насчет QXmlStreamReader::PrematureEnd...   15.7.2009, 21:35
- - SABROG   Нашел забавную статью про StAX, пример рассматрива...   22.7.2009, 15:33
- - SABROG   Сегодня заметил статью в блоге на QtSoftware и о т...   24.7.2009, 13:27
- - Litkevich Yuriy   Цитата(SABROG @ 24.7.2009, 17:27) я немно...   24.7.2009, 13:39
- - SABROG   В общем я немного заморочился на эту тему и напи...   29.8.2009, 14:40


Быстрый ответОтветить в данную темуНачать новую тему
Теги
Нет тегов для показа


1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0




RSS Текстовая версия Сейчас: 1.12.2022, 16:34