Implemented saving/loading simple and advanced searches to/from named XML files. Close issue #220
This commit is contained in:
parent
b53686a084
commit
c49a922e2d
@ -20,180 +20,11 @@
|
||||
#include "advshist.h"
|
||||
#include "guiutils.h"
|
||||
#include "debuglog.h"
|
||||
|
||||
#include <QtXml/QXmlDefaultHandler>
|
||||
#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<SearchData> 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<SearchData>(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<string> 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<string> 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<Rcl::SearchData> AdvSearchHist::getnewest()
|
||||
{
|
||||
if (m_entries.empty())
|
||||
return RefCntr<Rcl::SearchData>();
|
||||
|
||||
return m_entries[0];
|
||||
}
|
||||
|
||||
RefCntr<Rcl::SearchData> AdvSearchHist::getolder()
|
||||
{
|
||||
m_current++;
|
||||
@ -244,18 +83,9 @@ bool AdvSearchHist::read()
|
||||
|
||||
for (list<string>::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<SearchData> sd = xmlToSearchData(*it);
|
||||
if (sd)
|
||||
m_entries.push_back(sd);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -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<Rcl::SearchData>);
|
||||
|
||||
// Get latest. does not change state
|
||||
RefCntr<Rcl::SearchData> getnewest();
|
||||
|
||||
// Cursor
|
||||
RefCntr<Rcl::SearchData> getolder();
|
||||
RefCntr<Rcl::SearchData> getnewer();
|
||||
bool push(RefCntr<Rcl::SearchData>);
|
||||
|
||||
void clear();
|
||||
|
||||
private:
|
||||
|
||||
146
src/qtgui/rclm_saveload.cpp
Normal file
146
src/qtgui/rclm_saveload.cpp
Normal file
@ -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 <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <QSettings>
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
|
||||
#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<Rcl::SearchData> 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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") +
|
||||
"<recollquery version='1.0'>\n" + xml + "\n</recollquery>\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<SearchData> 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"));
|
||||
}
|
||||
@ -76,6 +76,9 @@
|
||||
<addaction name="fileRetryFailedAction"/>
|
||||
<addaction name="fileRebuildIndexAction"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSave_last_query"/>
|
||||
<addaction name="actionLoad_saved_query"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="fileEraseSearchHistoryAction"/>
|
||||
<addaction name="fileEraseDocHistoryAction"/>
|
||||
<addaction name="separator"/>
|
||||
@ -484,6 +487,16 @@
|
||||
<cstring>fileToggleIndexingAction</cstring>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSave_last_query">
|
||||
<property name="text">
|
||||
<string>Save last query</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionLoad_saved_query">
|
||||
<property name="text">
|
||||
<string>Load saved query</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="2" margin="2"/>
|
||||
<customwidgets>
|
||||
|
||||
@ -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()));
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -14,6 +14,9 @@
|
||||
* Free Software Foundation, Inc.,
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
|
||||
#include <qapplication.h>
|
||||
#include <qinputdialog.h>
|
||||
#include <qvariant.h>
|
||||
@ -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 << "<SD type='ssearch'>\n";
|
||||
xml << " <SL>" << stemlang << "</SL>\n";
|
||||
xml << " <T>" << base64_encode(u8) << "</T>\n";
|
||||
|
||||
SSearchType tp = (SSearchType)searchTypCMB->currentIndex();
|
||||
Rcl::SearchData *sdata = 0;
|
||||
|
||||
if (tp == SST_LANG) {
|
||||
xml << " <SM>QL</SM>\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 << " <AS>" << qs2utf8s(prefs.autoSuffs) << "</AS>\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 << " <SM>FN</SM>\n";
|
||||
clp = new Rcl::SearchDataClauseFilename(u8);
|
||||
} else {
|
||||
// ANY or ALL, several words.
|
||||
if (tp == SST_ANY) {
|
||||
xml << " <SM>OR</SM>\n";
|
||||
clp = new Rcl::SearchDataClauseSimple(Rcl::SCLT_OR, u8);
|
||||
} else {
|
||||
xml << " <SM>AND</SM>\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 << " <AP/>\n";
|
||||
sdata->maybeAddAutoPhrase(*rcldb,
|
||||
prefs.ssearchAutoPhraseThreshPC / 100.0);
|
||||
}
|
||||
if (maxexp != -1) {
|
||||
sdata->setMaxExpand(maxexp);
|
||||
}
|
||||
|
||||
for (list<string>::const_iterator it = prefs.activeExtraDbs.begin();
|
||||
it != prefs.activeExtraDbs.end(); it++) {
|
||||
xml << " <EX>" << base64_encode(*it) << "</EX>";
|
||||
}
|
||||
|
||||
xml << "</SD>\n";
|
||||
m_xml = xml.str();
|
||||
LOGDEB(("SSearch::startSimpleSearch:xml:[%s]\n", m_xml.c_str()));
|
||||
|
||||
RefCntr<Rcl::SearchData> rsdata(sdata);
|
||||
emit startSearch(rsdata, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SSearch::fromXML(const SSearchDef& fxml)
|
||||
{
|
||||
string asString;
|
||||
set<string> cur;
|
||||
set<string> 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<string>(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<string>(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<string>(prefs.activeExtraDbs.begin(), prefs.activeExtraDbs.end());
|
||||
stored = set<string>(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;
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
#ifndef _SSEARCH_W_H_INCLUDED_
|
||||
#define _SSEARCH_W_H_INCLUDED_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QVariant>
|
||||
#include <QWidget>
|
||||
|
||||
@ -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);
|
||||
|
||||
345
src/qtgui/xmltosd.cpp
Normal file
345
src/qtgui/xmltosd.cpp
Normal file
@ -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 <QtXml/QXmlDefaultHandler>
|
||||
|
||||
#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<SearchData> 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<SearchData>(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<string> 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<string> 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<Rcl::SearchData> 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<SearchData>();
|
||||
}
|
||||
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;
|
||||
}
|
||||
77
src/qtgui/xmltosd.h
Normal file
77
src/qtgui/xmltosd.h
Normal file
@ -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
|
||||
*
|
||||
* <SD> <!-- Search Data -->
|
||||
* <CL> <!-- Clause List -->
|
||||
* <CLT>AND|OR</CLT> <!-- conjunction AND is default, omitted -->
|
||||
* <C> <!-- Clause -->
|
||||
* [<NEG/>] <!-- Possible negation -->
|
||||
* <CT>AND|OR|FN|PH|NE</CT> <!-- Clause type -->
|
||||
* <F>[base64data]</F> <!-- Optional base64-encoded field name -->
|
||||
* <T>[base64data]</T> <!-- base64-encoded text -->
|
||||
* <S>slack</S> <!-- optional slack for near/phrase -->
|
||||
* </C>
|
||||
*
|
||||
* <ND>[base64 path]</ND> <!-- Path exclusion -->
|
||||
* <YD>[base64 path]</YD> <!-- Path filter -->
|
||||
* </CL>
|
||||
*
|
||||
* <DMI><D>1</D><M>6</M><Y>2014</Y></DMI> <--! datemin -->
|
||||
* <DMA><D>30</D><M>6</M><Y>2014</Y></DMA> <--! datemax -->
|
||||
* <MIS>minsize</MIS> <!-- Min size -->
|
||||
* <MAS>maxsize</MAS> <!-- Max size -->
|
||||
* <ST>space-sep mtypes</ST> <!-- Included mime types -->
|
||||
* <IT>space-sep mtypes</IT> <!-- Excluded mime types -->
|
||||
*
|
||||
* </SD>
|
||||
*
|
||||
* For Simple search:
|
||||
*
|
||||
* <SD type='ssearch'>
|
||||
* <T>base64-encoded query text</T>
|
||||
* <SM>OR|AND|FN|QL</SM>
|
||||
* <SL>space-separated lang list</SL>
|
||||
* <AP/> <!-- autophrase -->
|
||||
* <AS>space-separated suffix list</AS> <!-- autosuffs -->
|
||||
* <EX>base64-encoded config path>/EX> <!-- [multiple] ext index -->
|
||||
* </SD>
|
||||
*/
|
||||
|
||||
#include "refcntr.h"
|
||||
#include "searchdata.h"
|
||||
|
||||
// Parsing XML from advanced search history or saved advanced search to to
|
||||
// SearchData structure:
|
||||
RefCntr<Rcl::SearchData> xmlToSearchData(const string& xml);
|
||||
|
||||
// Parsing XML from saved simple search to ssearch parameters
|
||||
struct SSearchDef {
|
||||
SSearchDef() : autophrase(false), mode(0) {}
|
||||
std::vector<std::string> stemlangs;
|
||||
std::vector<std::string> autosuffs;
|
||||
std::vector<std::string> extindexes;
|
||||
std::string text;
|
||||
bool autophrase;
|
||||
int mode;
|
||||
};
|
||||
bool xmlToSSearch(const string& xml, SSearchDef&);
|
||||
Loading…
x
Reference in New Issue
Block a user