From c49a922e2d43585d3d3187e350a342b87e6b0e58 Mon Sep 17 00:00:00 2001 From: Jean-Francois Dockes Date: Thu, 11 Jun 2015 18:35:16 +0200 Subject: [PATCH] Implemented saving/loading simple and advanced searches to/from named XML files. Close issue #220 --- src/qtgui/advshist.cpp | 194 ++------------------ src/qtgui/advshist.h | 15 +- src/qtgui/rclm_saveload.cpp | 146 +++++++++++++++ src/qtgui/rclmain.ui | 13 ++ src/qtgui/rclmain_w.cpp | 8 + src/qtgui/rclmain_w.h | 2 + src/qtgui/recoll.pro.in | 3 +- src/qtgui/ssearch_w.cpp | 94 +++++++++- src/qtgui/ssearch_w.h | 14 +- src/qtgui/xmltosd.cpp | 345 ++++++++++++++++++++++++++++++++++++ src/qtgui/xmltosd.h | 77 ++++++++ 11 files changed, 723 insertions(+), 188 deletions(-) create mode 100644 src/qtgui/rclm_saveload.cpp create mode 100644 src/qtgui/xmltosd.cpp create mode 100644 src/qtgui/xmltosd.h diff --git a/src/qtgui/advshist.cpp b/src/qtgui/advshist.cpp index d3e0d212..41d2be8b 100644 --- a/src/qtgui/advshist.cpp +++ b/src/qtgui/advshist.cpp @@ -20,180 +20,11 @@ #include "advshist.h" #include "guiutils.h" #include "debuglog.h" - -#include +#include "xmltosd.h" using namespace std; using namespace Rcl; -class SDHXMLHandler : public QXmlDefaultHandler { -public: - SDHXMLHandler() - { - resetTemps(); - } - bool startElement(const QString & /* namespaceURI */, - const QString & /* localName */, - const QString &qName, - const QXmlAttributes &attributes); - bool endElement(const QString & /* namespaceURI */, - const QString & /* localName */, - const QString &qName); - bool characters(const QString &str) - { - currentText += str; - return true; - } - - // The object we set up - RefCntr sd; - -private: - void resetTemps() - { - currentText = whatclause = ""; - text.clear(); - field.clear(); - slack = 0; - d = m = y = di.d1 = di.m1 = di.y1 = di.d2 = di.m2 = di.y2 = 0; - hasdates = false; - exclude = false; - } - - // Temporary data while parsing. - QString currentText; - QString whatclause; - string field, text; - int slack; - int d, m, y; - DateInterval di; - bool hasdates; - bool exclude; -}; - -bool SDHXMLHandler::startElement(const QString & /* namespaceURI */, - const QString & /* localName */, - const QString &qName, - const QXmlAttributes &) -{ - LOGDEB2(("SDHXMLHandler::startElement: name [%s]\n", - (const char *)qName.toUtf8())); - if (qName == "SD") { - resetTemps(); - // A new search descriptor. Allocate data structure - sd = RefCntr(new SearchData); - if (sd.isNull()) { - LOGERR(("SDHXMLHandler::startElement: out of memory\n")); - return false; - } - } - return true; -} - -bool SDHXMLHandler::endElement(const QString & /* namespaceURI */, - const QString & /* localName */, - const QString &qName) -{ - LOGDEB2(("SDHXMLHandler::endElement: name [%s]\n", - (const char *)qName.toUtf8())); - - if (qName == "CLT") { - if (currentText == "OR") { - sd->setTp(SCLT_OR); - } - } else if (qName == "CT") { - whatclause = currentText.trimmed(); - } else if (qName == "NEG") { - exclude = true; - } else if (qName == "F") { - field = base64_decode(qs2utf8s(currentText.trimmed())); - } else if (qName == "T") { - text = base64_decode(qs2utf8s(currentText.trimmed())); - } else if (qName == "S") { - slack = atoi((const char *)currentText.toUtf8()); - } else if (qName == "C") { - SearchDataClause *c; - if (whatclause == "AND" || whatclause.isEmpty()) { - c = new SearchDataClauseSimple(SCLT_AND, text, field); - c->setexclude(exclude); - } else if (whatclause == "OR") { - c = new SearchDataClauseSimple(SCLT_OR, text, field); - c->setexclude(exclude); - } else if (whatclause == "EX") { - // Compat with old hist. We don't generete EX (SCLT_EXCL) anymore - // it's replaced with OR + exclude flag - c = new SearchDataClauseSimple(SCLT_OR, text, field); - c->setexclude(true); - } else if (whatclause == "FN") { - c = new SearchDataClauseFilename(text); - c->setexclude(exclude); - } else if (whatclause == "PH") { - c = new SearchDataClauseDist(SCLT_PHRASE, text, slack, field); - c->setexclude(exclude); - } else if (whatclause == "NE") { - c = new SearchDataClauseDist(SCLT_NEAR, text, slack, field); - c->setexclude(exclude); - } else { - LOGERR(("Bad clause type [%s]\n", qs2utf8s(whatclause).c_str())); - return false; - } - sd->addClause(c); - whatclause = ""; - text.clear(); - field.clear(); - slack = 0; - exclude = false; - } else if (qName == "D") { - d = atoi((const char *)currentText.toUtf8()); - } else if (qName == "M") { - m = atoi((const char *)currentText.toUtf8()); - } else if (qName == "Y") { - y = atoi((const char *)currentText.toUtf8()); - } else if (qName == "DMI") { - di.d1 = d; - di.m1 = m; - di.y1 = y; - hasdates = true; - } else if (qName == "DMA") { - di.d2 = d; - di.m2 = m; - di.y2 = y; - hasdates = true; - } else if (qName == "MIS") { - sd->setMinSize(atoll((const char *)currentText.toUtf8())); - } else if (qName == "MAS") { - sd->setMaxSize(atoll((const char *)currentText.toUtf8())); - } else if (qName == "ST") { - string types = (const char *)currentText.toUtf8(); - vector vt; - stringToTokens(types, vt); - for (unsigned int i = 0; i < vt.size(); i++) - sd->addFiletype(vt[i]); - } else if (qName == "IT") { - string types(qs2utf8s(currentText)); - vector vt; - stringToTokens(types, vt); - for (unsigned int i = 0; i < vt.size(); i++) - sd->remFiletype(vt[i]); - } else if (qName == "YD") { - string d; - base64_decode(qs2utf8s(currentText.trimmed()), d); - sd->addClause(new SearchDataClausePath(d)); - } else if (qName == "ND") { - string d; - base64_decode(qs2utf8s(currentText.trimmed()), d); - sd->addClause(new SearchDataClausePath(d, true)); - } else if (qName == "SD") { - // Closing current search descriptor. Finishing touches... - if (hasdates) - sd->setDateSpan(&di); - resetTemps(); - } - currentText.clear(); - return true; -} - - AdvSearchHist::AdvSearchHist() { read(); @@ -208,6 +39,14 @@ AdvSearchHist::~AdvSearchHist() } } +RefCntr AdvSearchHist::getnewest() +{ + if (m_entries.empty()) + return RefCntr(); + + return m_entries[0]; +} + RefCntr AdvSearchHist::getolder() { m_current++; @@ -244,18 +83,9 @@ bool AdvSearchHist::read() for (list::const_iterator it = lxml.begin(); it != lxml.end(); it++) { - SDHXMLHandler handler; - QXmlSimpleReader reader; - reader.setContentHandler(&handler); - reader.setErrorHandler(&handler); - QXmlInputSource xmlInputSource; - xmlInputSource.setData(QString::fromUtf8(it->c_str())); - if (!reader.parse(xmlInputSource)) { - LOGERR(("AdvSearchHist::read: parse failed for [%s]\n", - it->c_str())); - return false; - } - m_entries.push_back(handler.sd); + RefCntr sd = xmlToSearchData(*it); + if (sd) + m_entries.push_back(sd); } return true; } diff --git a/src/qtgui/advshist.h b/src/qtgui/advshist.h index 3b8d58de..ec10f3ab 100644 --- a/src/qtgui/advshist.h +++ b/src/qtgui/advshist.h @@ -33,14 +33,25 @@ * turn the XML back into a SearchData object, which is then passed to * the advanced search object fromSearch() method to rebuild the * window state. - */ + * + * XML generation is performed by ../rcldb/searchdataxml.cpp. + * See xmltosd.h for a schema description + */ class AdvSearchHist { public: AdvSearchHist(); ~AdvSearchHist(); + + // Add entry + bool push(RefCntr); + + // Get latest. does not change state + RefCntr getnewest(); + + // Cursor RefCntr getolder(); RefCntr getnewer(); - bool push(RefCntr); + void clear(); private: diff --git a/src/qtgui/rclm_saveload.cpp b/src/qtgui/rclm_saveload.cpp new file mode 100644 index 00000000..2325efcf --- /dev/null +++ b/src/qtgui/rclm_saveload.cpp @@ -0,0 +1,146 @@ +/* Copyright (C) 2005 J.F.Dockes + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "autoconfig.h" + +/** Saving and restoring named queries */ + +#include +#include +#include + +#include +#include +#include + +#include "rclmain_w.h" +#include "debuglog.h" +#include "readfile.h" +#include "xmltosd.h" +#include "searchdata.h" + +using namespace std; +using namespace Rcl; + +static QString prevDir() +{ + QSettings settings; + QString prevdir = + settings.value("/Recoll/prefs/lastQuerySaveDir").toString(); + string defpath = path_cat(theconfig->getConfDir(), "saved_queries"); + if (prevdir.isEmpty()) { + if (access(defpath.c_str(), 0) != 0) { + mkdir(defpath.c_str(), 0700); + } + return QString::fromLocal8Bit(defpath.c_str()); + } else { + return prevdir; + } +} + +void RclMain::saveLastQuery() +{ + string xml; + if (lastSearchSimple()) { + xml = sSearch->asXML(); + } else { + if (g_advshistory) { + RefCntr sd; + sd = g_advshistory->getnewest(); + if (sd) { + xml = sd->asXML(); + } + } + } + if (xml.empty()) { + QMessageBox::information(this, tr("No search"), + tr("No preserved previous search")); + return; + } + xml = string("\n") + + "\n" + xml + "\n\n"; + + QFileDialog fileDialog(this, tr("Choose file to save")); + fileDialog.setNameFilter(tr("Saved Queries (*.rclq)")); + fileDialog.setDefaultSuffix("rclq"); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + fileDialog.setDirectory(prevDir()); + + if (!fileDialog.exec()) + return; + + QString s = fileDialog.selectedFiles().first(); + if (s.isEmpty()) { + return; + } + + string tofile((const char *)s.toLocal8Bit()); + + LOGDEB(("RclMain::saveLastQuery: XML: [%s]\n", xml.c_str())); + + int fd = ::open(tofile.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0600); + if (fd < 0) { + QMessageBox::warning(this, tr("Open failed"), + tr("Could not open/create file")); + return; + } + if (::write(fd, xml.c_str(), xml.size()) != int(xml.size())) { + ::close(fd); + QMessageBox::warning(this, tr("Write failed"), + tr("Could not write to file")); + return; + } + if (::close(fd) != 0) { + QMessageBox::warning(this, tr("Close failed"), tr("File close error")); + return; + } +} + + +void RclMain::loadSavedQuery() +{ + QString s = + QFileDialog::getOpenFileName(this, "Open saved query", prevDir(), + tr("Saved Queries (*.rclq)")); + if (s.isEmpty()) + return; + + string fromfile((const char *)s.toLocal8Bit()); + string xml, reason; + if (!file_to_string(fromfile, xml, &reason)) { + QMessageBox::warning(this, tr("Read failed"), + tr("Could not open file: ") + + QString::fromUtf8(reason.c_str())); + return; + } + + // Try to parse as SearchData + RefCntr sd = xmlToSearchData(xml); + if (sd) { + showAdvSearchDialog(); + asearchform->fromSearch(sd); + return; + } + + // Try to parse as Simple Search + SSearchDef sdef; + if (xmlToSSearch(xml, sdef)) { + if (sSearch->fromXML(sdef)) + return; + } + QMessageBox::warning(this, tr("Load error"), + tr("Could not load saved query")); +} diff --git a/src/qtgui/rclmain.ui b/src/qtgui/rclmain.ui index 80bab13f..bb690cbc 100644 --- a/src/qtgui/rclmain.ui +++ b/src/qtgui/rclmain.ui @@ -76,6 +76,9 @@ + + + @@ -484,6 +487,16 @@ fileToggleIndexingAction + + + Save last query + + + + + Load saved query + + diff --git a/src/qtgui/rclmain_w.cpp b/src/qtgui/rclmain_w.cpp index baee8058..a20bb818 100644 --- a/src/qtgui/rclmain_w.cpp +++ b/src/qtgui/rclmain_w.cpp @@ -324,6 +324,11 @@ void RclMain::init() this, SLOT(eraseDocHistory())); connect(fileEraseSearchHistoryAction, SIGNAL(triggered()), this, SLOT(eraseSearchHistory())); + connect(actionSave_last_query, SIGNAL(triggered()), + this, SLOT(saveLastQuery())); + connect(actionLoad_saved_query, SIGNAL(triggered()), + this, SLOT(loadSavedQuery())); + connect(helpAbout_RecollAction, SIGNAL(triggered()), this, SLOT(showAboutDialog())); connect(showMissingHelpers_Action, SIGNAL(triggered()), @@ -1077,6 +1082,9 @@ void RclMain::showAdvSearchDialog() { if (asearchform == 0) { asearchform = new AdvSearch(0); + if (asearchform == 0) { + return; + } connect(new QShortcut(quitKeySeq, asearchform), SIGNAL (activated()), this, SLOT (fileExit())); diff --git a/src/qtgui/rclmain_w.h b/src/qtgui/rclmain_w.h index 3c186c78..974d8fec 100644 --- a/src/qtgui/rclmain_w.h +++ b/src/qtgui/rclmain_w.h @@ -153,6 +153,8 @@ public slots: virtual void resetSearch(); virtual void eraseDocHistory(); virtual void eraseSearchHistory(); + virtual void saveLastQuery(); + virtual void loadSavedQuery(); virtual void setStemLang(QAction *id); virtual void adjustPrefsMenu(); virtual void catgFilter(int); diff --git a/src/qtgui/recoll.pro.in b/src/qtgui/recoll.pro.in index a2273216..a2ccfb9b 100644 --- a/src/qtgui/recoll.pro.in +++ b/src/qtgui/recoll.pro.in @@ -52,6 +52,7 @@ SOURCES += \ ptrans_w.cpp \ rclhelp.cpp \ rclmain_w.cpp \ + rclm_saveload.cpp \ rclzg.cpp \ respopup.cpp \ reslist.cpp \ @@ -64,7 +65,7 @@ SOURCES += \ systray.cpp \ uiprefs_w.cpp \ viewaction_w.cpp \ - + xmltosd.cpp FORMS = \ advsearch.ui \ diff --git a/src/qtgui/ssearch_w.cpp b/src/qtgui/ssearch_w.cpp index 2048123a..d2e714eb 100644 --- a/src/qtgui/ssearch_w.cpp +++ b/src/qtgui/ssearch_w.cpp @@ -14,6 +14,9 @@ * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include +#include + #include #include #include @@ -35,6 +38,10 @@ #include "textsplit.h" #include "wasatorcl.h" #include "rclhelp.h" +#include "xmltosd.h" +#include "smallut.h" + +using namespace std; // Typing interval after which we consider starting autosearch: no sense to do // this is user is typing fast and continuously @@ -226,21 +233,36 @@ void SSearch::setPrefs() } } +string SSearch::asXML() +{ + return m_xml; +} + bool SSearch::startSimpleSearch(const string& u8, int maxexp) { LOGDEB(("SSearch::startSimpleSearch(%s)\n", u8.c_str())); string stemlang = prefs.stemlang(); + ostringstream xml; + xml << "\n"; + xml << " " << stemlang << "\n"; + xml << " " << base64_encode(u8) << "\n"; + SSearchType tp = (SSearchType)searchTypCMB->currentIndex(); Rcl::SearchData *sdata = 0; if (tp == SST_LANG) { + xml << " QL\n"; string reason; - if (prefs.autoSuffsEnable) + if (prefs.autoSuffsEnable) { sdata = wasaStringToRcl(theconfig, stemlang, u8, reason, (const char *)prefs.autoSuffs.toUtf8()); - else + if (!prefs.autoSuffs.isEmpty()) { + xml << " " << qs2utf8s(prefs.autoSuffs) << "\n"; + } + } else { sdata = wasaStringToRcl(theconfig, stemlang, u8, reason); + } if (sdata == 0) { QMessageBox::warning(0, "Recoll", tr("Bad query string") + ": " + QString::fromUtf8(reason.c_str())); @@ -254,12 +276,15 @@ bool SSearch::startSimpleSearch(const string& u8, int maxexp) } Rcl::SearchDataClause *clp = 0; if (tp == SST_FNM) { + xml << " FN\n"; clp = new Rcl::SearchDataClauseFilename(u8); } else { // ANY or ALL, several words. if (tp == SST_ANY) { + xml << " OR\n"; clp = new Rcl::SearchDataClauseSimple(Rcl::SCLT_OR, u8); } else { + xml << " AND\n"; clp = new Rcl::SearchDataClauseSimple(Rcl::SCLT_AND, u8); } } @@ -267,17 +292,82 @@ bool SSearch::startSimpleSearch(const string& u8, int maxexp) } if (prefs.ssearchAutoPhrase && rcldb) { + xml << " \n"; sdata->maybeAddAutoPhrase(*rcldb, prefs.ssearchAutoPhraseThreshPC / 100.0); } if (maxexp != -1) { sdata->setMaxExpand(maxexp); } + + for (list::const_iterator it = prefs.activeExtraDbs.begin(); + it != prefs.activeExtraDbs.end(); it++) { + xml << " " << base64_encode(*it) << ""; + } + + xml << "\n"; + m_xml = xml.str(); + LOGDEB(("SSearch::startSimpleSearch:xml:[%s]\n", m_xml.c_str())); + RefCntr rsdata(sdata); emit startSearch(rsdata, true); return true; } +bool SSearch::fromXML(const SSearchDef& fxml) +{ + string asString; + set cur; + set stored; + + // Retrieve current list of stemlangs. prefs returns a + // space-separated list Warn if stored differs from current, + // but don't change the latter. + stringToStrings(prefs.stemlang(), cur); + stored = set(fxml.stemlangs.begin(), fxml.stemlangs.end()); + stringsToString(fxml.stemlangs, asString); + if (cur != stored) { + QMessageBox::warning( + 0, "Recoll", tr("Stemming languages for stored query: ") + + QString::fromUtf8(asString.c_str()) + + tr(" differ from current preferences (kept)")); + } + + // Same for autosuffs + stringToStrings(qs2utf8s(prefs.autoSuffs), cur); + stored = set(fxml.autosuffs.begin(), fxml.autosuffs.end()); + stringsToString(fxml.stemlangs, asString); + if (cur != stored) { + QMessageBox::warning( + 0, "Recoll", tr("Auto suffixes for stored query: ") + + QString::fromUtf8(asString.c_str()) + + tr(" differ from current preferences (kept)")); + } + + cur = set(prefs.activeExtraDbs.begin(), prefs.activeExtraDbs.end()); + stored = set(fxml.extindexes.begin(), fxml.extindexes.end()); + stringsToString(fxml.extindexes, asString); + if (cur != stored) { + QMessageBox::warning( + 0, "Recoll", tr("External indexes for stored query: ") + + QString::fromUtf8(asString.c_str()) + + tr(" differ from current preferences (kept)")); + } + + if (prefs.ssearchAutoPhrase && !fxml.autophrase) { + QMessageBox::warning( + 0, "Recoll", + tr("Autophrase is set but it was unset for stored query")); + } else if (!prefs.ssearchAutoPhrase && fxml.autophrase) { + QMessageBox::warning( + 0, "Recoll", + tr("Autophrase is unset but it was set for stored query")); + } + setSearchString(QString::fromUtf8(fxml.text.c_str())); + searchTypCMB->setCurrentIndex(prefs.ssearchTyp); + return true; +} + void SSearch::setSearchString(const QString& txt) { m_disableAutosearch = true; diff --git a/src/qtgui/ssearch_w.h b/src/qtgui/ssearch_w.h index 689263fe..cb9b2841 100644 --- a/src/qtgui/ssearch_w.h +++ b/src/qtgui/ssearch_w.h @@ -17,6 +17,8 @@ #ifndef _SSEARCH_W_H_INCLUDED_ #define _SSEARCH_W_H_INCLUDED_ +#include + #include #include @@ -28,11 +30,15 @@ class QTimer; #include "ui_ssearchb.h" +struct SSearchDef; + class SSearch : public QWidget, public Ui::SSearchBase { Q_OBJECT public: + // The values MUST NOT change, there are assumptions about them in + // different parts of the code enum SSearchType {SST_ANY = 0, SST_ALL = 1, SST_FNM = 2, SST_LANG = 3}; SSearch(QWidget* parent = 0, const char * = 0) @@ -47,7 +53,12 @@ public: virtual void completion(); virtual bool eventFilter(QObject *target, QEvent *event); virtual bool hasSearchString(); - virtual void setPrefs(); + virtual void setPrefs(); + // Return last performed search as XML text. + virtual std::string asXML(); + // Restore ssearch UI from saved search + virtual bool fromXML(const SSearchDef& fxml); + public slots: virtual void searchTextChanged(const QString & text); virtual void searchTypeChanged(int); @@ -76,6 +87,7 @@ signals: bool m_keystroke; QString m_tstartqs; QAbstractItemModel *m_savedModel; + std::string m_xml; /* Saved xml version of the search, as we start it */ int partialWord(string& s); int completionList(string s, QStringList& lst, int max = 100); diff --git a/src/qtgui/xmltosd.cpp b/src/qtgui/xmltosd.cpp new file mode 100644 index 00000000..5c5c1f19 --- /dev/null +++ b/src/qtgui/xmltosd.cpp @@ -0,0 +1,345 @@ +/* Copyright (C) 2005 J.F.Dockes + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "autoconfig.h" + +#include + +#include "ssearch_w.h" + +#include "guiutils.h" +#include "debuglog.h" +#include "xmltosd.h" +#include "smallut.h" +#include "recoll.h" + +using namespace std; +using namespace Rcl; + +class SDHXMLHandler : public QXmlDefaultHandler { +public: + SDHXMLHandler() + : isvalid(false) + { + resetTemps(); + } + bool startElement(const QString & /* namespaceURI */, + const QString & /* localName */, + const QString &qName, + const QXmlAttributes &attributes); + bool endElement(const QString & /* namespaceURI */, + const QString & /* localName */, + const QString &qName); + bool characters(const QString &str) + { + currentText += str; + return true; + } + + // The object we set up + RefCntr sd; + bool isvalid; + +private: + void resetTemps() + { + currentText = whatclause = ""; + text.clear(); + field.clear(); + slack = 0; + d = m = y = di.d1 = di.m1 = di.y1 = di.d2 = di.m2 = di.y2 = 0; + hasdates = false; + exclude = false; + } + + // Temporary data while parsing. + QString currentText; + QString whatclause; + string field, text; + int slack; + int d, m, y; + DateInterval di; + bool hasdates; + bool exclude; +}; + +bool SDHXMLHandler::startElement(const QString & /* namespaceURI */, + const QString & /* localName */, + const QString &qName, + const QXmlAttributes &attrs) +{ + LOGDEB2(("SDHXMLHandler::startElement: name [%s]\n", + (const char *)qName.toUtf8())); + if (qName == "SD") { + // Advanced search history entries have no type. So we're good + // either if type is absent, or if it's searchdata + int idx = attrs.index("type"); + if (idx >= 0 && attrs.value(idx).compare("searchdata")) { + LOGDEB(("XMLTOSD: bad type\n")) + return false; + } + resetTemps(); + // A new search descriptor. Allocate data structure + sd = RefCntr(new SearchData); + if (sd.isNull()) { + LOGERR(("SDHXMLHandler::startElement: out of memory\n")); + return false; + } + } + return true; +} + +bool SDHXMLHandler::endElement(const QString & /* namespaceURI */, + const QString & /* localName */, + const QString &qName) +{ + LOGDEB2(("SDHXMLHandler::endElement: name [%s]\n", + (const char *)qName.toUtf8())); + + if (qName == "CLT") { + if (currentText == "OR") { + sd->setTp(SCLT_OR); + } + } else if (qName == "CT") { + whatclause = currentText.trimmed(); + } else if (qName == "NEG") { + exclude = true; + } else if (qName == "F") { + field = base64_decode(qs2utf8s(currentText.trimmed())); + } else if (qName == "T") { + text = base64_decode(qs2utf8s(currentText.trimmed())); + } else if (qName == "S") { + slack = atoi((const char *)currentText.toUtf8()); + } else if (qName == "C") { + SearchDataClause *c; + if (whatclause == "AND" || whatclause.isEmpty()) { + c = new SearchDataClauseSimple(SCLT_AND, text, field); + c->setexclude(exclude); + } else if (whatclause == "OR") { + c = new SearchDataClauseSimple(SCLT_OR, text, field); + c->setexclude(exclude); + } else if (whatclause == "EX") { + // Compat with old hist. We don't generete EX (SCLT_EXCL) anymore + // it's replaced with OR + exclude flag + c = new SearchDataClauseSimple(SCLT_OR, text, field); + c->setexclude(true); + } else if (whatclause == "FN") { + c = new SearchDataClauseFilename(text); + c->setexclude(exclude); + } else if (whatclause == "PH") { + c = new SearchDataClauseDist(SCLT_PHRASE, text, slack, field); + c->setexclude(exclude); + } else if (whatclause == "NE") { + c = new SearchDataClauseDist(SCLT_NEAR, text, slack, field); + c->setexclude(exclude); + } else { + LOGERR(("Bad clause type [%s]\n", qs2utf8s(whatclause).c_str())); + return false; + } + sd->addClause(c); + whatclause = ""; + text.clear(); + field.clear(); + slack = 0; + exclude = false; + } else if (qName == "D") { + d = atoi((const char *)currentText.toUtf8()); + } else if (qName == "M") { + m = atoi((const char *)currentText.toUtf8()); + } else if (qName == "Y") { + y = atoi((const char *)currentText.toUtf8()); + } else if (qName == "DMI") { + di.d1 = d; + di.m1 = m; + di.y1 = y; + hasdates = true; + } else if (qName == "DMA") { + di.d2 = d; + di.m2 = m; + di.y2 = y; + hasdates = true; + } else if (qName == "MIS") { + sd->setMinSize(atoll((const char *)currentText.toUtf8())); + } else if (qName == "MAS") { + sd->setMaxSize(atoll((const char *)currentText.toUtf8())); + } else if (qName == "ST") { + string types = (const char *)currentText.toUtf8(); + vector vt; + stringToTokens(types, vt); + for (unsigned int i = 0; i < vt.size(); i++) + sd->addFiletype(vt[i]); + } else if (qName == "IT") { + string types(qs2utf8s(currentText)); + vector vt; + stringToTokens(types, vt); + for (unsigned int i = 0; i < vt.size(); i++) + sd->remFiletype(vt[i]); + } else if (qName == "YD") { + string d; + base64_decode(qs2utf8s(currentText.trimmed()), d); + sd->addClause(new SearchDataClausePath(d)); + } else if (qName == "ND") { + string d; + base64_decode(qs2utf8s(currentText.trimmed()), d); + sd->addClause(new SearchDataClausePath(d, true)); + } else if (qName == "SD") { + // Closing current search descriptor. Finishing touches... + if (hasdates) + sd->setDateSpan(&di); + resetTemps(); + isvalid = true; + } + currentText.clear(); + return true; +} + + +RefCntr xmlToSearchData(const string& xml) +{ + SDHXMLHandler handler; + QXmlSimpleReader reader; + reader.setContentHandler(&handler); + reader.setErrorHandler(&handler); + + QXmlInputSource xmlInputSource; + xmlInputSource.setData(QString::fromUtf8(xml.c_str())); + + if (!reader.parse(xmlInputSource) || !handler.isvalid) { + LOGERR(("xmlToSearchData: parse failed for [%s]\n", + xml.c_str())); + return RefCntr(); + } + return handler.sd; +} + + +// Handler for parsing saved simple search data +class SSHXMLHandler : public QXmlDefaultHandler { +public: + SSHXMLHandler() + : isvalid(false) + { + resetTemps(); + } + bool startElement(const QString & /* namespaceURI */, + const QString & /* localName */, + const QString &qName, + const QXmlAttributes &attributes); + bool endElement(const QString & /* namespaceURI */, + const QString & /* localName */, + const QString &qName); + bool characters(const QString &str) + { + currentText += str; + return true; + } + + // The object we set up + SSearchDef data; + bool isvalid; + +private: + void resetTemps() + { + currentText = whatclause = ""; + text.clear(); + } + + // Temporary data while parsing. + QString currentText; + QString whatclause; + string text; +}; + +bool SSHXMLHandler::startElement(const QString & /* namespaceURI */, + const QString & /* localName */, + const QString &qName, + const QXmlAttributes &attrs) +{ + LOGDEB2(("SSHXMLHandler::startElement: name [%s]\n", + (const char *)qName.toUtf8())); + if (qName == "SD") { + // Simple search saved data has a type='ssearch' attribute. + int idx = attrs.index("type"); + if (idx < 0 && attrs.value(idx).compare("ssearch")) { + LOGDEB(("XMLTOSSS: bad type\n")); + return false; + } + resetTemps(); + } + return true; +} + +bool SSHXMLHandler::endElement(const QString & /* namespaceURI */, + const QString & /* localName */, + const QString &qName) +{ + LOGDEB2(("SSHXMLHandler::endElement: name [%s]\n", + (const char *)qName.toUtf8())); + + currentText = currentText.trimmed(); + + if (qName == "SL") { + stringToStrings(qs2utf8s(currentText), data.stemlangs); + } else if (qName == "T") { + base64_decode(qs2utf8s(currentText), data.text); + } else if (qName == "EX") { + data.extindexes.push_back(base64_decode(qs2utf8s(currentText))); + } else if (qName == "SM") { + if (!currentText.compare("QL")) { + data.mode = SSearch::SST_LANG; + } else if (!currentText.compare("FN")) { + data.mode = SSearch::SST_FNM; + } else if (!currentText.compare("OR")) { + data.mode = SSearch::SST_ANY; + } else if (!currentText.compare("AND")) { + data.mode = SSearch::SST_ALL; + } else { + LOGERR(("BAD SEARCH MODE: [%s]\n", qs2utf8s(currentText).c_str())); + return false; + } + } else if (qName == "AS") { + stringToStrings(qs2utf8s(currentText), data.autosuffs); + } else if (qName == "AP") { + data.autophrase = true; + } else if (qName == "SD") { + // Closing current search descriptor. Finishing touches... + resetTemps(); + isvalid = true; + } + currentText.clear(); + return true; +} + +bool xmlToSSearch(const string& xml, SSearchDef& data) +{ + SSHXMLHandler handler; + QXmlSimpleReader reader; + reader.setContentHandler(&handler); + reader.setErrorHandler(&handler); + + QXmlInputSource xmlInputSource; + xmlInputSource.setData(QString::fromUtf8(xml.c_str())); + + if (!reader.parse(xmlInputSource) || !handler.isvalid) { + LOGERR(("xmlToSSearch: parse failed for [%s]\n", + xml.c_str())); + return false; + } + data = handler.data; + return true; +} diff --git a/src/qtgui/xmltosd.h b/src/qtgui/xmltosd.h new file mode 100644 index 00000000..4f58842f --- /dev/null +++ b/src/qtgui/xmltosd.h @@ -0,0 +1,77 @@ +/* Copyright (C) 2014 J.F.Dockes + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/** Parsing XML from saved queries or advanced search history. + * + * Here is how the schemas looks like: + * + * For advanced search + * + * + * + * AND|OR + * + * [] + * AND|OR|FN|PH|NE + * [base64data] + * [base64data] + * slack + * + * + * [base64 path] + * [base64 path] + * + * + * 162014 <--! datemin --> + * 3062014 <--! datemax --> + * minsize + * maxsize + * space-sep mtypes + * space-sep mtypes + * + * + * + * For Simple search: + * + * + * base64-encoded query text + * OR|AND|FN|QL + * space-separated lang list + * + * space-separated suffix list + * base64-encoded config path>/EX> + * + */ + +#include "refcntr.h" +#include "searchdata.h" + +// Parsing XML from advanced search history or saved advanced search to to +// SearchData structure: +RefCntr xmlToSearchData(const string& xml); + +// Parsing XML from saved simple search to ssearch parameters +struct SSearchDef { + SSearchDef() : autophrase(false), mode(0) {} + std::vector stemlangs; + std::vector autosuffs; + std::vector extindexes; + std::string text; + bool autophrase; + int mode; +}; +bool xmlToSSearch(const string& xml, SSearchDef&);