Задача: при SAX-разборе перехватить полное содержимое определённых тегов в виде исходного текста, как оно есть, со всеми вложенными подтегами.
Хотя задача эта, по-моему, достаточно простая, банальная и затребованная, но по какой-то не понятной причине не нашёл избытка описания готовых её решений.
Зачем удобно перехватывать полный «сырой» текст XML? С ходу столкнулся с двумя возможными причинами:
1. Как известно и у SAX и у DOM разбора есть свои преимущества и недостатки. Возможен большой XML-документ, который по крайней мере удобно разбирать SAX-парсером, но в нём попадаются отсносительно небольшие участки, которые как раз было бы удобно и естественно с помощью DOM.
2. В определённных XML-тегах может находится XHTML-содержимое, разбирать которое не нужно вообще, его надо просто целиком сохранить, например, отправить на визуализацию в GUI-контрол.
Вероятно хватает и других задач, при которых удобно временно перевести SAX-разбор в режим «перехвата», при попадании на определённый тег.
Итак, как оказалось в Qt это сделать очень просто: достаточно перекрыть в классе QXmlInputSource публичный виртуальный метод next, который «кормит» прасер анализируемым XML посимвольно.
Вариант №1:
Обработчик содержимого (наследник QXmlDefaultHandler):
(В случае если при SAX-разборе попался один из интересующих нас «особых» тегов, мы переходим в режим «перехвата», а при закрытии тега выходим из этого режима, обрабатываем результат).
bool Report::startElement(const QString &namespaceURI, const QString &localName, const QString &name, const QXmlAttributes &attrs) { if (name=="information") // "information" is a special tag name { m_xmlsource->BeginIntercept(); } return RemoteTable::startElement(namespaceURI, localName, name, attrs); } // RemoteTable is a parent class that handle XML content bool Report::endElement(const QString &namespaceURI, const QString &localName, const QString &name) { if (name=="information") { QString info = m_xmlsource->EndIntercept(); if (m_pHeader!=NULL) { // send XHTML content into QLabel const QString was = m_pHeader->text(); m_pHeader->setText( was+info ); } } return RemoteTable::endElement(namespaceURI, localName, name); }
#ifndef EXTXMLINPUTSOURCE_H #define EXTXMLINPUTSOURCE_H #include <QXmlInputSource> #include <QIODevice> class ExtXmlInputSource : public QXmlInputSource { public: ExtXmlInputSource(QIODevice* dev); virtual void BeginIntercept(); virtual QString EndIntercept(); virtual QChar next(); protected: bool m_interception; QString m_content; }; #endif // EXTXMLINPUTSOURCE_H // cpp: ExtXmlInputSource::ExtXmlInputSource(QIODevice* dev) : QXmlInputSource(dev) , m_interception(false) { } void ExtXmlInputSource::BeginIntercept() { m_interception = true; } QChar ExtXmlInputSource::next() { QChar ret = QXmlInputSource::next(); if (m_interception) m_content+=ret; return ret; }Таким образом в накопителе ExtXmlInputSource::m_content получится нужный блок XML-текста плюс закрывающийся тег (если открывающий тег не был вообще «самозакрытым»). Тег для симментрии убираем (если результат направляется в DOM, можно наоборот в начале приписать открывающийся тег):
QString ExtXmlInputSource::EndIntercept() { m_interception = false; const QString res = m_content; m_content.clear(); // if needed: remove the last (closing) tag if (!res.isEmpty()) // non-empty tag? { int pos = res.lastIndexOf("</"); if (pos>=0) return res.left(pos); } return res; }
#ifndef EXTXMLINPUTSOURCE2_H #define EXTXMLINPUTSOURCE2_H #include "extxmlinputsource.h" #include <QXmlDefaultHandler> #include <QXmlSimpleReader> class ExtXmlInputSource2 : public ExtXmlInputSource, public QXmlDefaultHandler { public: ExtXmlInputSource2(QIODevice* dev); void Intercept(const QString& spectag, QXmlSimpleReader* reader, QXmlDefaultHandler* old); virtual bool endElement(const QString& , const QString& , const QString &name); protected: QString m_spectag; QXmlSimpleReader* m_reader; QXmlDefaultHandler* m_old; }; #endif // EXTXMLINPUTSOURCE2_H // cpp: ExtXmlInputSource2::ExtXmlInputSource2(QIODevice* dev) : ExtXmlInputSource(dev) { } void ExtXmlInputSource2::Intercept(const QString& spectag, QXmlSimpleReader* reader, QXmlDefaultHandler* old) { m_spectag = spectag; m_reader = reader; m_old = old; if (m_reader!=NULL) m_reader->setContentHandler(this); BeginIntercept(); } bool ExtXmlInputSource2::endElement(const QString& , const QString& , const QString &name) { if (name==m_spectag) // warning: no nested tags 'spectag' are expected! { if (m_old!=NULL) m_old->characters( EndIntercept() ); if (m_reader!=NULL) m_reader->setContentHandler(m_old); } return true; // QXmlDefaultHandler::endElement(namespaceURI, localName, name); == always true }
bool Report::startElement(const QString &namespaceURI, const QString &localName, const QString &name, const QXmlAttributes &attrs) { if (name=="information") { m_xmlsource->Intercept(name, m_reader, this); } return RemoteTable::startElement(namespaceURI, localName, name, attrs); } // RemoteTable is a parent class that handle XML content bool Report::characters(const QString &str) { if (m_name=="information") // m_name set in RemoteTable::startElement { if (m_pHeader!=NULL) { // send XHTML content into QLabel const QString was = m_pHeader->text(); m_pHeader->setText( was+str ); } } return RemoteTable::characters(str); }