![]() |
Здравствуйте, гость ( Вход | Регистрация )
![]() |
SABROG |
![]() ![]()
Сообщение
#1
|
![]() Профессионал ![]() ![]() ![]() ![]() ![]() Группа: Участник Сообщений: 1207 Регистрация: 8.12.2008 Из: Russia, Moscow Пользователь №: 446 Спасибо сказали: 229 раз(а) Репутация: ![]() ![]() ![]() |
Решил попробовать класс QXmlStreamReader. Изучив пример streambookmarks я реализовал свой парсер. И вот что мне не понравилось. Можно написать простенький код, который будет сравнивать каждое название ключа на токен isStartElement с нужным нам. Недостаток такого метода - большое количество операций сравнений и падение скорости. Зато очень просто, когда ключи имеют уникальные имена, не нужно контролировать вложенность, родительские отношения. Второй вариант (правильный). На каждый уровень дерева выделяется отдельный метод с while(!atEnd()). Подобное выделение требует создание нового метода для каждого такого уровня. Если глубина дерева в xml'e большая, то можно себе представить какое огромное количество методов получится! Например у меня глубина дерева такая:
Т.е. 9 методов (на каждый уровень) и в каждом по циклу while(). При этом меня совершенно не интересуют промежуточные уровни, только последние. Теперь сижу и думаю может сделать какие-нибудь указатели на методы, которые будут вызываться по очереди на одном while. Скажем QQueue/QHash с указателем на метод и строкой сравнения. --- Только вот на самом деле там даже больше 9и методов получится, т.к. в xml'е еще другие блоки есть, такой например:
Сообщение отредактировал SABROG - 13.7.2009, 14:59 |
|
|
![]() |
ViGOur |
![]()
Сообщение
#2
|
![]() Мастер ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модератор Сообщений: 3296 Регистрация: 9.10.2007 Из: Москва Пользователь №: 4 Спасибо сказали: 231 раз(а) Репутация: ![]() ![]() ![]() |
Код в студию, можно занятся оптимизаторством и универсальностью кода.
![]() |
|
|
SABROG |
![]()
Сообщение
#3
|
![]() Профессионал ![]() ![]() ![]() ![]() ![]() Группа: Участник Сообщений: 1207 Регистрация: 8.12.2008 Из: Russia, Moscow Пользователь №: 446 Спасибо сказали: 229 раз(а) Репутация: ![]() ![]() ![]() |
Код в студию, можно занятся оптимизаторством и универсальностью кода. ![]() Давайте попробуем, коль не боитесь ![]() Вот простой вариант:
Недостаток в том, что нет разбора кому какой ключ принадлежит. Т.е. если в xml'e будет 3 ветки и в каждой подветке будут ключи с одинаковыми именами, то я не смогу разобрать ключи "по кучкам". А есть такая махина:
Соответственно тут и так всё понятно. Плюс в том, что тут я точно знаю какой ветке что принадлежит. По скорости и первый и второй варианты почти одинаковы. XML файл на 5,5Мб парсится ~297ms. Сообщение отредактировал SABROG - 13.7.2009, 18:21 |
|
|
ViGOur |
![]()
Сообщение
#4
|
![]() Мастер ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модератор Сообщений: 3296 Регистрация: 9.10.2007 Из: Москва Пользователь №: 4 Спасибо сказали: 231 раз(а) Репутация: ![]() ![]() ![]() |
Жесть!
А почему нельзя сделать рекурсию с созданием дерева наследников? Меньше копи/паста одного и того же кода. |
|
|
SABROG |
![]()
Сообщение
#5
|
![]() Профессионал ![]() ![]() ![]() ![]() ![]() Группа: Участник Сообщений: 1207 Регистрация: 8.12.2008 Из: Russia, Moscow Пользователь №: 446 Спасибо сказали: 229 раз(а) Репутация: ![]() ![]() ![]() |
А почему нельзя сделать рекурсию с созданием дерева наследников? Я об этом думал пока ехал домой. Завтра попробую сделать рекурсию, но без дерева наследника. Попробую передавать в рекурсивную функцию нужные мне ключи и уже фильтровать их через if (name() == parName). |
|
|
SABROG |
![]()
Сообщение
#6
|
![]() Профессионал ![]() ![]() ![]() ![]() ![]() Группа: Участник Сообщений: 1207 Регистрация: 8.12.2008 Из: Russia, Moscow Пользователь №: 446 Спасибо сказали: 229 раз(а) Репутация: ![]() ![]() ![]() |
Да уж, что-то не очень получается. Если делать рекурсию без дерева наследников, то получается каждый ключ сравнивается со всем списком ключей снова, это замедляет парсинг. Есть мысль сделать дерево наследников таким образом: "tourml/sources/source/packets/packet/packetheader/spo/dates/date" + QStringList list = str.split("/");
Затем в рекурсии проходить по элементам списка и удалять из него пройденные ключи. Но этот вариант мне кажется каким-то странным, слабеньким вариантом XPath. --- Так, теперь я узнал, что есть разные варианты парсеров: DOM, SAX, StAX. Причем QXmlStreamReader это StAX парсер, который по сути вырос из Pull парсера. StAX и SAX совершенно разные вещи, первый не использует callback функции в отличие от второго. Сообщение отредактировал SABROG - 14.7.2009, 21:25 |
|
|
SABROG |
![]()
Сообщение
#7
|
![]() Профессионал ![]() ![]() ![]() ![]() ![]() Группа: Участник Сообщений: 1207 Регистрация: 8.12.2008 Из: Russia, Moscow Пользователь №: 446 Спасибо сказали: 229 раз(а) Репутация: ![]() ![]() ![]() |
В общем посмотрел я десяток примеров использования StAX парсера на Java и пришел к выводу, что это, в принципе, нормальный код. В основном, как я и говорил люди выносят циклы while() в отдельные методы и жестко вызывают метод nextTag() (которого кстати нет в Qt, а он пропускает комментарии всё остальное, что не является tag'ом), который устанавливает курсор на следующий элемент, подразумевая тема самым, что если сначала шел Tag = "Name", то за ним обязательно должен идти Tag = "Surname". Т.е. надеются на жесткий порядок следования tag'ов, чтобы уменьшить количество итераций через while(). Тем не менее почти везде такие здоровые портянки. Нигде не обнаружил рекурсии или какой-то специальной оптимизации.
Сообщение отредактировал SABROG - 14.7.2009, 23:06 |
|
|
ViGOur |
![]()
Сообщение
#8
|
![]() Мастер ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модератор Сообщений: 3296 Регистрация: 9.10.2007 Из: Москва Пользователь №: 4 Спасибо сказали: 231 раз(а) Репутация: ![]() ![]() ![]() |
Ты немного не понял, рекурсия c примерно таким условием:
общая идею думаю должна быть понятна. ![]() если не понятно, постараюсь описать подробней. Сообщение отредактировал ViGOur - 15.7.2009, 10:37 |
|
|
SABROG |
![]()
Сообщение
#9
|
![]() Профессионал ![]() ![]() ![]() ![]() ![]() Группа: Участник Сообщений: 1207 Регистрация: 8.12.2008 Из: Russia, Moscow Пользователь №: 446 Спасибо сказали: 229 раз(а) Репутация: ![]() ![]() ![]() |
Да оно всё это понятно, я тоже об этом говорю. Сам подход странный с точки зрения стиля программирования. Это получается некий гибрид Pull и Push парсера. Просто в таком случае тогда проще взять обычный SAX2 (push) парсер - QXmlReader. Но он не поддерживает потоковые данные и не умеет писать xml'и. Да и заполнять надо как-то этот вектор хитро. Например на одну ветку может приходится несколько нужных подветок, тогда придется дублировать имена основной ветки по количеству необходимых дочерних узлов. Или отказываться от вектора и сделать через дерево указателей. Опять же, такое дерево должно отражать некий путь парсинга. Насколько будет удобно изменять этот путь, подстраиваясь под новые форматы xml'ей? А мне надо в итоге написать 4 xml парсера. В принципе как я говорил выше, можно сделать некий аналог XPath'а, распарсить строчки с нужными путями в дерево. Правда в итоге получится новый оверхед. Тут выбор стоит, либо красивый и компактный код, но в жертву приносится скорость парсинга. Либо увеличение размера приложения из-за копи-пастинга и отвратительный код с точки зрения стиля программирования, но шустрый парсер.
|
|
|
ViGOur |
![]()
Сообщение
#10
|
![]() Мастер ![]() ![]() ![]() ![]() ![]() ![]() Группа: Модератор Сообщений: 3296 Регистрация: 9.10.2007 Из: Москва Пользователь №: 4 Спасибо сказали: 231 раз(а) Репутация: ![]() ![]() ![]() |
Так в том то и дело, что в твоем случае как я понял идут уникальные имена подветок и соответственно не нужно отслеживать, а если нужно наследовать, то достаточно ввести еще один параметр в массив:
и все так же с вектором, а так как родитель в вектор попадет раньше дочки, то и указатель на родителя будет и при изменении расположения элементов и их родителей никаких проблем с изменением и никакой путаницы.Вплоть до того, что ты сам программно сможешь менять значения, положения элементов и их родителей, а потом сохранять. Только в структуру нужно будет еще несколько аргументов ввести. ![]() |
|
|
Tonal |
![]()
Сообщение
#11
|
![]() Активный участник ![]() ![]() ![]() Группа: Участник Сообщений: 452 Регистрация: 6.12.2007 Из: Новосибирск Пользователь №: 34 Спасибо сказали: 69 раз(а) Репутация: ![]() ![]() ![]() |
То, что тут пытаетесь изобразить, называется конечный автомат.
![]() Т. е. нужно написать таблицу состаяний и таблицу переходов. Таблица переходов - это двумерный массив индексирующийся состоянием и сигналом. В твоём случае сигнал - это имя тега. Таблица состояний может просто состаять из указателей на функции обработки. Тогда код разбора будет примерно такой:
Заполнение таблиц и точные типы по вкусу. ![]() Сообщение отредактировал Tonal - 15.7.2009, 12:42 |
|
|
SABROG |
![]()
Сообщение
#12
|
![]() Профессионал ![]() ![]() ![]() ![]() ![]() Группа: Участник Сообщений: 1207 Регистрация: 8.12.2008 Из: Russia, Moscow Пользователь №: 446 Спасибо сказали: 229 раз(а) Репутация: ![]() ![]() ![]() |
Как я и писал выше затык будет в заполнении таблицы. Мы всё-таки дерево парсим, где может быть не один ребенок у одной ветки. Сложно представить как управлять таким автоматом. Я переделал так, мне кажется это золотая середина:
Раскрывающийся текст
Кстати как я не изгалялся над алгоритмом время парсинга почему-то всегда константно: 297ms. Если использую qDebug() для вывода в консоль, то время парсинга увеличивается до 1048ms. Правда у этого варианта тоже есть существенный недостаток, чтобы использовать стриминг надо проверять на ошибку после каждого readNext() иначе есть шанс прочитать несуществующие аттрибуты или тектовые элементы. Сообщение отредактировал SABROG - 15.7.2009, 14:54 |
|
|
SABROG |
![]()
Сообщение
#13
|
![]() Профессионал ![]() ![]() ![]() ![]() ![]() Группа: Участник Сообщений: 1207 Регистрация: 8.12.2008 Из: Russia, Moscow Пользователь №: 446 Спасибо сказали: 229 раз(а) Репутация: ![]() ![]() ![]() |
Такой вопрос насчет QXmlStreamReader::PrematureEndOfDocumentError. Предположим я выкачиваю xml из инета и тут же паршу. Где-то в середине на каком-нибудь "packet" данные заканчиваются (медленная скорость выкачивания). Мне надо добавить новые данные через addData() в буффер, когда эти данные скачаются. Так вот я не понимаю, при возникновении ошибки у парсера 2 выхода - добавить данные или завершится. Если данных пока нет и надо их подождать, то ничего не остается как завершить парсинг и начать всё заново, когда поступят все данные. Напрашивается вариант использовать waitForReadyRead, но у QNetworkReply вроде как этот метод не реализован. Как же заморозить текущее состояние QXmlStreamReader, создавать QEventLoop чтоль и ждать?
--- Подобный вопрос задавался в рассылке 2008 года, но Thiago оставил этот вопрос без ответа. --- Проштудировал 2 страницы тем на QtCentre где упоминалось слово QXmlStreamReader, полезной информации ноль. Примеры все шуточные, докачка нигде не реализована. Пошел штудировать QtForum. --- На QtForum ситуация еще хуже, тем 5-6 и все одно и то же мусолят. На prog.org'е вообще тишина, 2 темы и все не о том. На vingrad'e только один человек тему завел. На sources вообще одна тема и то там попался include, а не тема про парсер На этом форуме поиск тоже выдал только меня. В общем с этим классом дорожка не проторенная ни в рунете ни в мире. Похоже есть только один выход изучать примеры на .Net и Java. Сообщение отредактировал Litkevich Yuriy - 15.7.2009, 22:57
Причина редактирования: поправил ссылку на lists.trolltech.com
|
|
|
SABROG |
![]()
Сообщение
#14
|
![]() Профессионал ![]() ![]() ![]() ![]() ![]() Группа: Участник Сообщений: 1207 Регистрация: 8.12.2008 Из: Russia, Moscow Пользователь №: 446 Спасибо сказали: 229 раз(а) Репутация: ![]() ![]() ![]() |
Нашел забавную статью про StAX, пример рассматривается на Java и XmlStreamReader.
Забавно в ней то каким образом они решают проблему здоровых циклов while(). Смысл следующий. Создается абстрактный базовый класс типа ComponentParser. Есть 2 ключа "author" и "entry", при вхождении в эти ключи должен вызываться свой парсер. Поэтому на эти ключи создается 2 класса AuthorParser и EntryParser, оба на базе ComponentParser. Создается map (типа QMap) - {"ИмяКлюча":ОбъектНаБазеComponentParser}. Уже начинает напоминать наши callback'и. Внутри каждого такого ComponentParser'а также есть свой map для дочерних узлов. Вызов метода парсинга для ключа выглядит так:
delegates - наш map, parser - объект, который отвечает за парсинг конкретного ключа. На мой взгляд это мало чем отличается от этого:
Просто они взяли "детский" пример (глубина дерева 1 уровень) xml'я, да еще умудрились наворотить всего вокруг него, а циклы while() заменили на StaxUtil.moveReaderToElement("name",staxXmlReader);, по сути одно и тоже что и это: .В моем предыдущем варианте я использовал свой метод readNext(), который пропускал энное количество ключей. В этой статье предпочитают не пропускать ключи, а искать нужный. Минус в том, что это дополнительные операции сравнения строк имен на каждом ключе. Плюс в том, что код выглядит не уродско. Снова надо делать выбор, либо красота кода, либо скорость парсинга. Таким образом выходит, что для удобной работы в классе QXmlStreamReader не хватает методов типа: nextTag, nextTag("name"), skipSubTree() и возможности работать асинхронно в цикле событий как QNetworkAccessManager, или потокового варианта QXmlStreamReaderThreaded. Дело в том, что если устройство установлено через setDevice, то парсер не увидит новых приходящих данных, скажем по сокету пока у парсера не будет возможности дать отработать циклу событий. QEventLoop хоть и работает, но это имхо костыль. Интерфейс программы размораживается при таком раскладе, значит нужен модальный диалог. Также встает вопрос о прерывании парсинга. Это все таки цикл, значит надо каким-то образом дать знать парсеру, что пора выходить. --- Кстати в статье упоминается метод parseElement, на самом деле это опечатка, в исходниках метод parse(). Еще нашел в исходниках реализацию метода moveReaderToElement:
Он хорош только для ключей в одном экземпляре. Если в xml файле будут списки типа:
То нужен цикл while() и проверка на выход из поддерева иначе парсер поползет дальше по всему xml'ю до самого конца. К сожалению, как я уже говорил, пример в статье слабый, хотя и пишется, что ориентирован он на серьезные xml'и. Продумали не все. Сообщение отредактировал SABROG - 23.7.2009, 15:16 |
|
|
SABROG |
![]()
Сообщение
#15
|
![]() Профессионал ![]() ![]() ![]() ![]() ![]() Группа: Участник Сообщений: 1207 Регистрация: 8.12.2008 Из: Russia, Moscow Пользователь №: 446 Спасибо сказали: 229 раз(а) Репутация: ![]() ![]() ![]() |
Сегодня заметил статью в блоге на QtSoftware и о том, что в демке используется QXmlStreamReader. Глянув на код я немного успокоился, раз уж сами Qt'шники занимаются копи-пастингом (насчитал 3 вложенных цикла while)...
Сиськи Здесь
Сообщение отредактировал SABROG - 24.7.2009, 16:56
Причина редактирования: для длинных исходников используй тэг expand
|
|
|
Litkevich Yuriy |
![]()
Сообщение
#16
|
![]() разработчик РЭА ![]() ![]() ![]() ![]() ![]() ![]() ![]() Группа: Сомодератор Сообщений: 9669 Регистрация: 9.1.2008 Из: Тюмень Пользователь №: 64 Спасибо сказали: 807 раз(а) Репутация: ![]() ![]() ![]() |
|
|
|
SABROG |
![]() ![]()
Сообщение
#17
|
![]() Профессионал ![]() ![]() ![]() ![]() ![]() Группа: Участник Сообщений: 1207 Регистрация: 8.12.2008 Из: Russia, Moscow Пользователь №: 446 Спасибо сказали: 229 раз(а) Репутация: ![]() ![]() ![]() |
![]() В общем я немного заморочился на эту тему и написал класс, который назвал по-страшному - xmlstreamreaderhelper ![]() Несмотря на то, что понять как он работает не просто я постараюсь все же объяснить суть. Все построено на полиморфизме, чтобы избежать дублирование кода. Есть 2 класса:
Класс AbstractTagProcessor наследует класс AbstractTagImplementator и определяет в себе ряд методов для работы с тэгами xml'я. В то время как класс AbstractTagImplementator определяет общий функционал парсера. Если разбирать мой xml из прошлых постов, то его парсинг теперь выглядит так: //определяем в заголовке набор классов, каждый из которых представление необходимых тегов //.h
В этих классах переопределяются так называемые эвенты базовых классов. Всего 3 эвента: beforeEvent(), event(), afterEvent(). В зависимости от сложности парсера переопределяются нужные. В простом случае нужен только beforeEvent(), а вот если нужно будет перебрать все существующие теги или обработать неизвестные, или просто хочется реализовать свой функционал иначе, то надо переопределять метод event(). //реализация классов тегов //.cpp
//Подготовка и вызов парсера //.cpp
Т.е. открываем файл, создаем объект QXmlStreamReader, которому и передаем device (file). Затем создается родительский объект TourML, и по аналогии с QObject'ами тэги-классы выстраиваются в дерево. Потом на корневом теге вызывается метод parse(), который уже проходит по всему дереву детей. Если в процессе парсинга любого тэга xml прервется, при передаче по сокету например, то запускается локальный QEventLoop и ожидает новые данные с устройства. Если они приходят, то парсинг продолжается дальше. Я пробовал генерировать бесконечный поток xml'я по сокету, парсер работает не прерываясь. Имена, которые передаются в конструкторы объектов - реальные названия тегов в xml'е чувствительные к регистру. Еще вот этот код прокомментирую:
В этом случае тэг "references" является всего-лишь "чекпоинтом", нам тег не нужен сам по себе, но мы должны указать короткую дорогу парсеру, чтобы он мог найти тег "country", поэтому нам нет необходимости наследоваться от класса AbstractTagImplementator и мы используем его базовый функционал, которого достаточно. С другой стороны мы могли бы отказаться от этого чекпоинта и парсер бы все равно нашел нужный тег, однако в разных ситуациях это может замедлить скорость парсинга, т.к. заставит парсер сравнивать имена ключей, которые мы могли бы отмести заранее. А в случае, когда у разных подветок имеются ключи с одинаковым названием это может привести к путанице и невозможности узнать какой из подветок принадлежит тег, чтобы соотнести его. Как по мне, так это выглядит более правильно, чем мой первоначальный вариант: Раскрывающийся текст
К тому же замеры скорости не показали каких либо отличительных различий. Классы на C++ я проектирую впервые поэтому был бы рад узнать где я сделал что-то не так и как этом можно улучшить. Есть мысль написать кодо-генератор для парсера, где можно было бы загрузить дерево xml'я и проставляя галочки выбрать путь для парсера. Выкладываю для критики и дополнений исходники. ![]() |
|
|
![]() ![]() ![]() |
![]() |
|
Текстовая версия | Сейчас: 25.5.2025, 14:29 |