Implement anchored searches: terms to be found at a maximum distance of the start or end of the text
This commit is contained in:
@@ -73,6 +73,9 @@ namespace Rcl {
|
||||
#endif
|
||||
|
||||
const string pathelt_prefix = "XP";
|
||||
const string start_of_field_term = "XXST";
|
||||
const string end_of_field_term = "XXND";
|
||||
|
||||
// This is used as a marker inside the abstract frag lists, but
|
||||
// normally doesn't remain in final output (which is built with a
|
||||
// custom sep. by our caller).
|
||||
@@ -831,6 +834,8 @@ class TextSplitDb : public TextSplit {
|
||||
Xapian::Document &d, StopList &_stops)
|
||||
: db(idb), doc(d), basepos(1), curpos(0), stops(_stops), wdfinc(1)
|
||||
{}
|
||||
// Reimplement text_to_words to add start and end special terms
|
||||
virtual bool text_to_words(const string &in);
|
||||
bool takeword(const std::string &term, int pos, int, int);
|
||||
void setprefix(const string& pref) {prefix = pref;}
|
||||
void setwdfinc(int i) {wdfinc = i;}
|
||||
@@ -843,6 +848,38 @@ private:
|
||||
int wdfinc;
|
||||
};
|
||||
|
||||
|
||||
bool TextSplitDb::text_to_words(const string &in)
|
||||
{
|
||||
LOGDEB(("TextSplitDb::text_to_words\n"));
|
||||
string ermsg;
|
||||
try {
|
||||
// Index the possibly prefixed start term.
|
||||
doc.add_posting(prefix + start_of_field_term, basepos, wdfinc);
|
||||
++basepos;
|
||||
} XCATCHERROR(ermsg);
|
||||
if (!ermsg.empty()) {
|
||||
LOGERR(("Db: xapian add_posting error %s\n", ermsg.c_str()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TextSplit::text_to_words(in)) {
|
||||
LOGDEB(("TextSplitDb: TextSplit::text_to_words failed\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Index the possibly prefixed end term.
|
||||
doc.add_posting(prefix + end_of_field_term, basepos+curpos+1, wdfinc);
|
||||
++basepos;
|
||||
} XCATCHERROR(ermsg);
|
||||
if (!ermsg.empty()) {
|
||||
LOGERR(("Db: xapian add_posting error %s\n", ermsg.c_str()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get one term from the doc, remove accents and lowercase, then add posting
|
||||
bool TextSplitDb::takeword(const std::string &_term, int pos, int, int)
|
||||
{
|
||||
|
||||
@@ -287,6 +287,8 @@ private:
|
||||
string version_string();
|
||||
|
||||
extern const string pathelt_prefix;
|
||||
extern const string start_of_field_term;
|
||||
extern const string end_of_field_term;
|
||||
#ifndef NO_NAMESPACES
|
||||
}
|
||||
#endif // NO_NAMESPACES
|
||||
|
||||
+57
-27
@@ -510,13 +510,13 @@ public:
|
||||
|
||||
private:
|
||||
void expandTerm(bool dont, const string& term, list<string>& exp,
|
||||
string& sterm, string *prefix = 0);
|
||||
string& sterm, const string& prefix);
|
||||
// After splitting entry on whitespace: process non-phrase element
|
||||
void processSimpleSpan(const string& span, bool nostemexp, list<Xapian::Query> &pqueries);
|
||||
// Process phrase/near element
|
||||
void processPhraseOrNear(TextSplitQ *splitData,
|
||||
list<Xapian::Query> &pqueries,
|
||||
bool useNear, int slack);
|
||||
bool useNear, int slack, int mods);
|
||||
|
||||
Db& m_db;
|
||||
const string& m_field;
|
||||
@@ -554,7 +554,7 @@ static void listVector(const string& what, const vector<string>&l)
|
||||
void StringToXapianQ::expandTerm(bool nostemexp,
|
||||
const string& term,
|
||||
list<string>& exp,
|
||||
string &sterm, string *prefix)
|
||||
string &sterm, const string& prefix)
|
||||
{
|
||||
LOGDEB2(("expandTerm: field [%s] term [%s] stemlang [%s] nostemexp %d\n",
|
||||
m_field.c_str(), term.c_str(), m_stemlang.c_str(), nostemexp));
|
||||
@@ -571,29 +571,20 @@ void StringToXapianQ::expandTerm(bool nostemexp,
|
||||
nostemexp = true;
|
||||
|
||||
if (nostemexp && !haswild) {
|
||||
// Neither stemming nor wildcard expansion: just the word
|
||||
string pfx;
|
||||
const FieldTraits *ftp;
|
||||
if (!m_field.empty() && m_db.fieldToTraits(m_field, &ftp)) {
|
||||
pfx = ftp->pfx;
|
||||
}
|
||||
|
||||
sterm = term;
|
||||
m_uterms.push_back(sterm);
|
||||
exp.push_front(pfx+term);
|
||||
exp.push_front(prefix + term);
|
||||
exp.resize(1);
|
||||
if (prefix)
|
||||
*prefix = pfx;
|
||||
} else {
|
||||
TermMatchResult res;
|
||||
if (haswild) {
|
||||
m_db.termMatch(Rcl::Db::ET_WILD, m_stemlang, term, res, -1,
|
||||
m_field, prefix);
|
||||
m_field);
|
||||
} else {
|
||||
sterm = term;
|
||||
m_uterms.push_back(sterm);
|
||||
m_db.termMatch(Rcl::Db::ET_STEM, m_stemlang, term, res, -1, m_field,
|
||||
prefix);
|
||||
m_db.termMatch(Rcl::Db::ET_STEM, m_stemlang, term, res, -1,
|
||||
m_field);
|
||||
}
|
||||
for (list<TermMatchEntry>::const_iterator it = res.entries.begin();
|
||||
it != res.entries.end(); it++) {
|
||||
@@ -642,8 +633,15 @@ void StringToXapianQ::processSimpleSpan(const string& span, bool nostemexp,
|
||||
{
|
||||
list<string> exp;
|
||||
string sterm; // dumb version of user term
|
||||
|
||||
string prefix;
|
||||
expandTerm(nostemexp, span, exp, sterm, &prefix);
|
||||
const FieldTraits *ftp;
|
||||
if (!m_field.empty() && m_db.fieldToTraits(m_field, &ftp)) {
|
||||
prefix = ftp->pfx;
|
||||
}
|
||||
|
||||
expandTerm(nostemexp, span, exp, sterm, prefix);
|
||||
|
||||
// m_terms is used for highlighting, we don't want prefixes in there.
|
||||
for (list<string>::const_iterator it = exp.begin();
|
||||
it != exp.end(); it++) {
|
||||
@@ -658,10 +656,9 @@ void StringToXapianQ::processSimpleSpan(const string& span, bool nostemexp,
|
||||
// less wqf). This does not happen if there are wildcards anywhere
|
||||
// in the search.
|
||||
if (m_doBoostUserTerms && !sterm.empty()) {
|
||||
xq = Xapian::Query(Xapian::Query::OP_OR,
|
||||
xq,
|
||||
Xapian::Query(prefix+sterm,
|
||||
original_term_wqf_booster));
|
||||
xq = Xapian::Query(Xapian::Query::OP_OR, xq,
|
||||
Xapian::Query(prefix+sterm,
|
||||
original_term_wqf_booster));
|
||||
}
|
||||
pqueries.push_back(xq);
|
||||
}
|
||||
@@ -672,7 +669,7 @@ void StringToXapianQ::processSimpleSpan(const string& span, bool nostemexp,
|
||||
// don't do stemming for PHRASE though)
|
||||
void StringToXapianQ::processPhraseOrNear(TextSplitQ *splitData,
|
||||
list<Xapian::Query> &pqueries,
|
||||
bool useNear, int slack)
|
||||
bool useNear, int slack, int mods)
|
||||
{
|
||||
Xapian::Query::op op = useNear ? Xapian::Query::OP_NEAR :
|
||||
Xapian::Query::OP_PHRASE;
|
||||
@@ -680,6 +677,17 @@ void StringToXapianQ::processPhraseOrNear(TextSplitQ *splitData,
|
||||
bool hadmultiple = false;
|
||||
vector<vector<string> >groups;
|
||||
|
||||
string prefix;
|
||||
const FieldTraits *ftp;
|
||||
if (!m_field.empty() && m_db.fieldToTraits(m_field, &ftp)) {
|
||||
prefix = ftp->pfx;
|
||||
}
|
||||
|
||||
if (mods & Rcl::SearchDataClause::SDCM_ANCHORSTART) {
|
||||
orqueries.push_back(Xapian::Query(prefix + start_of_field_term));
|
||||
slack++;
|
||||
}
|
||||
|
||||
// Go through the list and perform stem/wildcard expansion for each element
|
||||
vector<bool>::iterator nxit = splitData->nostemexps.begin();
|
||||
for (vector<string>::iterator it = splitData->terms.begin();
|
||||
@@ -691,8 +699,7 @@ void StringToXapianQ::processPhraseOrNear(TextSplitQ *splitData,
|
||||
|
||||
string sterm;
|
||||
list<string>exp;
|
||||
string prefix;
|
||||
expandTerm(nostemexp, *it, exp, sterm, &prefix);
|
||||
expandTerm(nostemexp, *it, exp, sterm, prefix);
|
||||
|
||||
// groups is used for highlighting, we don't want prefixes in there.
|
||||
vector<string> noprefs;
|
||||
@@ -709,6 +716,11 @@ void StringToXapianQ::processPhraseOrNear(TextSplitQ *splitData,
|
||||
#endif
|
||||
}
|
||||
|
||||
if (mods & Rcl::SearchDataClause::SDCM_ANCHOREND) {
|
||||
orqueries.push_back(Xapian::Query(prefix + end_of_field_term));
|
||||
slack++;
|
||||
}
|
||||
|
||||
// Generate an appropriate PHRASE/NEAR query with adjusted slack
|
||||
// For phrases, give a relevance boost like we do for original terms
|
||||
LOGDEB2(("PHRASE/NEAR: alltermcount %d lastpos %d\n",
|
||||
@@ -727,6 +739,23 @@ void StringToXapianQ::processPhraseOrNear(TextSplitQ *splitData,
|
||||
m_groups.insert(m_groups.end(), allcombs.begin(), allcombs.end());
|
||||
}
|
||||
|
||||
// Trim string beginning with ^ or ending with $ and convert to flags
|
||||
static int stringToMods(string& s)
|
||||
{
|
||||
int mods = 0;
|
||||
// Check for an anchored search
|
||||
trimstring(s);
|
||||
if (s.length() > 0 && s[0] == '^') {
|
||||
mods |= Rcl::SearchDataClause::SDCM_ANCHORSTART;
|
||||
s.erase(0, 1);
|
||||
}
|
||||
if (s.length() > 0 && s[s.length()-1] == '$') {
|
||||
mods |= Rcl::SearchDataClause::SDCM_ANCHOREND;
|
||||
s.erase(s.length()-1);
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn user entry string (NOT query language) into a list of xapian queries.
|
||||
* We just separate words and phrases, and do wildcard and stem expansion,
|
||||
@@ -772,7 +801,8 @@ bool StringToXapianQ::processUserString(const string &iq,
|
||||
for (list<string>::iterator it = phrases.begin();
|
||||
it != phrases.end(); it++) {
|
||||
LOGDEB0(("strToXapianQ: phrase/word: [%s]\n", it->c_str()));
|
||||
|
||||
int mods = stringToMods(*it);
|
||||
int terminc = mods != 0 ? 1 : 0;
|
||||
// If there are multiple spans in this element, including
|
||||
// at least one composite, we have to increase the slack
|
||||
// else a phrase query including a span would fail.
|
||||
@@ -803,7 +833,7 @@ bool StringToXapianQ::processUserString(const string &iq,
|
||||
}
|
||||
|
||||
LOGDEB0(("strToXapianQ: termcount: %d\n", splitter->terms.size()));
|
||||
switch (splitter->terms.size()) {
|
||||
switch (splitter->terms.size() + terminc) {
|
||||
case 0:
|
||||
continue;// ??
|
||||
case 1:
|
||||
@@ -811,7 +841,7 @@ bool StringToXapianQ::processUserString(const string &iq,
|
||||
splitter->nostemexps.front(), pqueries);
|
||||
break;
|
||||
default:
|
||||
processPhraseOrNear(splitter, pqueries, useNear, slack);
|
||||
processPhraseOrNear(splitter, pqueries, useNear, slack, mods);
|
||||
}
|
||||
}
|
||||
} catch (const Xapian::Error &e) {
|
||||
|
||||
@@ -165,7 +165,8 @@ private:
|
||||
|
||||
class SearchDataClause {
|
||||
public:
|
||||
enum Modifier {SDCM_NONE=0, SDCM_NOSTEMMING=1};
|
||||
enum Modifier {SDCM_NONE=0, SDCM_NOSTEMMING=1, SDCM_ANCHORSTART=2,
|
||||
SDCM_ANCHOREND=4};
|
||||
|
||||
SearchDataClause(SClType tp)
|
||||
: m_tp(tp), m_parentSearch(0), m_haveWildCards(0),
|
||||
@@ -182,6 +183,12 @@ public:
|
||||
SClType getTp() {return m_tp;}
|
||||
void setParent(SearchData *p) {m_parentSearch = p;}
|
||||
virtual void setModifiers(Modifier mod) {m_modifiers = mod;}
|
||||
virtual int getModifiers() {return m_modifiers;}
|
||||
virtual void addModifier(Modifier mod) {
|
||||
int imod = getModifiers();
|
||||
imod |= mod;
|
||||
setModifiers(Modifier(imod));
|
||||
}
|
||||
virtual void setWeight(float w) {m_weight = w;}
|
||||
friend class SearchData;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user