Switch to side-by-side view

--- a/src/rcldb/searchdata.cpp
+++ b/src/rcldb/searchdata.cpp
@@ -16,17 +16,22 @@
  */
 
 // Handle translation from rcl's SearchData structures to Xapian Queries
+
+#include "autoconfig.h"
+
 #include <stdio.h>
 #include <fnmatch.h>
 
 #include <string>
 #include <vector>
 #include <algorithm>
+using namespace std;
 
 #include "xapian.h"
 
 #include "cstr.h"
 #include "rcldb.h"
+#include "rcldb_p.h"
 #include "searchdata.h"
 #include "debuglog.h"
 #include "smallut.h"
@@ -36,11 +41,11 @@
 #include "stoplist.h"
 #include "rclconfig.h"
 #include "termproc.h"
-
-#ifndef NO_NAMESPACES
-using namespace std;
+#include "synfamily.h"
+#include "stemdb.h"
+#include "expansiondbs.h"
+
 namespace Rcl {
-#endif
 
 typedef  vector<SearchDataClause *>::iterator qlist_it_t;
 typedef  vector<SearchDataClause *>::const_iterator qlist_cit_t;
@@ -71,13 +76,35 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
  * USA
  */
+
+#ifdef RCL_INDEX_STRIPCHARS
+#define bufprefix(BUF, L) {(BUF)[0] = L;}
+#define bpoffs() 1
+#else
+static inline void bufprefix(char *buf, char c)
+{
+    if (o_index_stripchars) {
+	buf[0] = c;
+    } else {
+	buf[0] = ':'; 
+	buf[1] = c; 
+	buf[2] = ':';
+    }
+}
+static inline int bpoffs() 
+{
+    return o_index_stripchars ? 1 : 3;
+}
+#endif
+
 static Xapian::Query
 date_range_filter(int y1, int m1, int d1, int y2, int m2, int d2)
 {
     // Xapian uses a smallbuf and snprintf. Can't be bothered, we're
     // only doing %d's !
     char buf[200];
-    sprintf(buf, "D%04d%02d", y1, m1);
+    bufprefix(buf, 'D');
+    sprintf(buf+bpoffs(), "%04d%02d", y1, m1);
     vector<Xapian::Query> v;
 
     int d_last = monthdays(m1, y1);
@@ -88,11 +115,11 @@
     // Deal with any initial partial month
     if (d1 > 1 || d_end < d_last) {
     	for ( ; d1 <= d_end ; d1++) {
-	    sprintf(buf + 7, "%02d", d1);
+	    sprintf(buf + 6 + bpoffs(), "%02d", d1);
 	    v.push_back(Xapian::Query(buf));
 	}
     } else {
-	buf[0] = 'M';
+	bufprefix(buf, 'M');
 	v.push_back(Xapian::Query(buf));
     }
     
@@ -102,36 +129,36 @@
 
     int m_last = (y1 < y2) ? 12 : m2 - 1;
     while (++m1 <= m_last) {
-	sprintf(buf + 5, "%02d", m1);
-	buf[0] = 'M';
+	sprintf(buf + 4 + bpoffs(), "%02d", m1);
+	bufprefix(buf, 'M');
 	v.push_back(Xapian::Query(buf));
     }
 	
     if (y1 < y2) {
 	while (++y1 < y2) {
-	    sprintf(buf + 1, "%04d", y1);
-	    buf[0] = 'Y';
+	    sprintf(buf + bpoffs(), "%04d", y1);
+	    bufprefix(buf, 'Y');
 	    v.push_back(Xapian::Query(buf));
 	}
-	sprintf(buf + 1, "%04d", y2);
-	buf[0] = 'M';
+	sprintf(buf + bpoffs(), "%04d", y2);
+	bufprefix(buf, 'M');
 	for (m1 = 1; m1 < m2; m1++) {
-	    sprintf(buf + 5, "%02d", m1);
+	    sprintf(buf + 4 + bpoffs(), "%02d", m1);
 	    v.push_back(Xapian::Query(buf));
 	}
     }
 	
-    sprintf(buf + 5, "%02d", m2);
+    sprintf(buf + 2 + bpoffs(), "%02d", m2);
 
     // Deal with any final partial month
     if (d2 < monthdays(m2, y2)) {
-	buf[0] = 'D';
+	bufprefix(buf, 'D');
     	for (d1 = 1 ; d1 <= d2; d1++) {
-	    sprintf(buf + 7, "%02d", d1);
+	    sprintf(buf + 6 + bpoffs(), "%02d", d1);
 	    v.push_back(Xapian::Query(buf));
 	}
     } else {
-	buf[0] = 'M';
+	bufprefix(buf, 'M');
 	v.push_back(Xapian::Query(buf));
     }
 
@@ -172,31 +199,27 @@
     return true;
 }
 
-bool SearchData::toNativeQuery(Rcl::Db &db, void *d)
-{
-    LOGDEB2(("SearchData::toNativeQuery: stemlang [%s]\n", 
-	    m_stemlang.c_str()));
+bool SearchData::clausesToQuery(Rcl::Db &db, SClType tp, 
+				vector<SearchDataClause*>& query, 
+				string& reason, void *d)
+{
     Xapian::Query xq;
-    m_reason.erase();
-
-    // Walk the clause list translating each in turn and building the 
-    // Xapian query tree
-    for (qlist_it_t it = m_query.begin(); it != m_query.end(); it++) {
+    for (qlist_it_t it = query.begin(); it != query.end(); it++) {
 	Xapian::Query nq;
-	if (!(*it)->toNativeQuery(db, &nq, m_stemlang)) {
-	    LOGERR(("SearchData::toNativeQuery: failed\n"));
-	    m_reason = (*it)->getReason();
+	if (!(*it)->toNativeQuery(db, &nq)) {
+	    LOGERR(("SearchData::clausesToQuery: toNativeQuery failed\n"));
+	    reason = (*it)->getReason();
 	    return false;
 	}	    
         if (nq.empty()) {
-            LOGDEB(("SearchData::toNativeQuery: skipping empty clause\n"));
+            LOGDEB(("SearchData::clausesToQuery: skipping empty clause\n"));
             continue;
         }
 	// If this structure is an AND list, must use AND_NOT for excl clauses.
 	// Else this is an OR list, and there can't be excl clauses (checked by
 	// addClause())
 	Xapian::Query::op op;
-	if (m_tp == SCLT_AND) {
+	if (tp == SCLT_AND) {
             if ((*it)->m_tp == SCLT_EXCL) {
                 op =  Xapian::Query::OP_AND_NOT;
             } else {
@@ -216,6 +239,23 @@
     }
     if (xq.empty())
 	xq = Xapian::Query::MatchAll;
+
+   *((Xapian::Query *)d) = xq;
+    return true;
+}
+
+bool SearchData::toNativeQuery(Rcl::Db &db, void *d)
+{
+    LOGDEB(("SearchData::toNativeQuery: stemlang [%s]\n", m_stemlang.c_str()));
+    m_reason.erase();
+
+    // Walk the clause list translating each in turn and building the 
+    // Xapian query tree
+    Xapian::Query xq;
+    if (!clausesToQuery(db, m_tp, m_query, m_reason, &xq)) {
+	LOGERR(("SearchData::toNativeQuery: clausesToQuery failed\n"));
+	return false;
+    }
 
     if (m_haveDates) {
         // If one of the extremities is unset, compute db extremas
@@ -326,10 +366,10 @@
 	stringToTokens(dit->dir, vpath, "/");
 	vector<string> pvpath;
 	if (dit->dir[0] == '/')
-	    pvpath.push_back(pathelt_prefix);
+	    pvpath.push_back(wrap_prefix(pathelt_prefix));
 	for (vector<string>::const_iterator pit = vpath.begin(); 
 	     pit != vpath.end(); pit++){
-	    pvpath.push_back(pathelt_prefix + *pit);
+	    pvpath.push_back(wrap_prefix(pathelt_prefix) + *pit);
 	}
 	Xapian::Query::op tdop;
 	if (dit->weight == 1.0) {
@@ -446,7 +486,7 @@
 	// My type is AND. Change it to OR and insert two queries, one
 	// being the original query as a subquery, the other the
 	// phrase.
-	SearchData *sd = new SearchData(m_tp);
+	SearchData *sd = new SearchData(m_tp, m_stemlang);
 	sd->m_query = m_query;
 	sd->m_stemlang = m_stemlang;
 	m_tp = SCLT_OR;
@@ -586,25 +626,28 @@
     { }
 
     bool processUserString(const string &iq,
+			   int mods, 
 			   string &ermsg,
 			   vector<Xapian::Query> &pqueries, 
-			   const StopList &stops,
 			   int slack = 0, bool useNear = false);
 private:
-    void expandTerm(bool dont, const string& term, vector<string>& exp, 
+    void expandTerm(int mods, 
+		    const string& term, vector<string>& exp, 
                     string& sterm, const string& prefix);
     // After splitting entry on whitespace: process non-phrase element
-    void processSimpleSpan(const string& span, bool nostemexp, 
+    void processSimpleSpan(const string& span, 
+			   int mods,
 			   vector<Xapian::Query> &pqueries);
     // Process phrase/near element
     void processPhraseOrNear(TextSplitQ *splitData, 
+			     int mods,
 			     vector<Xapian::Query> &pqueries,
-			     bool useNear, int slack, int mods);
+			     bool useNear, int slack);
 
     Db&           m_db;
     const string& m_field;
     const string& m_stemlang;
-    bool          m_doBoostUserTerms;
+    const bool    m_doBoostUserTerms;
     HighlightData& m_hld;
 };
 
@@ -619,61 +662,204 @@
 }
 #endif
 
-/** Take simple term and expand stem and wildcards
+/** Expand term into term list, using appropriate mode: stem, wildcards, 
+ *  diacritics... 
  *
- * @param nostemexp don't perform stem expansion. This is mainly used to
- *   prevent stem expansion inside phrases (because the user probably
- *   does not expect it). This does NOT prevent wild card expansion.
- *   Other factors than nostemexp can prevent stem expansion: 
- *   a null stemlang, resulting from a global user preference, a
- *   capitalized term, or wildcard(s)
+ * @param mods stem expansion, case and diacritics sensitivity control.
  * @param term input single word
  * @param exp output expansion list
  * @param sterm output original input term if there were no wildcards
+ * @param prefix field prefix in index. We could recompute it, but the caller
+ *  has it already. Used in the simple case where there is nothing to expand, 
+ *  and we just return the prefixed term (else Db::termMatch deals with it).
  */
-void StringToXapianQ::expandTerm(bool nostemexp, 
-                                 const string& term, 
-                                 vector<string>& exp,
-                                 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));
-    sterm.erase();
-    exp.clear();
-    if (term.empty()) {
+void StringToXapianQ::expandTerm(int mods, 
+				 const string& term, 
+                                 vector<string>& oexp, string &sterm,
+				 const string& prefix)
+{
+    LOGDEB0(("expandTerm: mods 0x%x fld [%s] trm [%s] lang [%s]\n",
+	     mods, m_field.c_str(), term.c_str(), m_stemlang.c_str()));
+    sterm.clear();
+    oexp.clear();
+    if (term.empty())
 	return;
-    }
 
     bool haswild = term.find_first_of(cstr_minwilds) != string::npos;
 
-    // No stemming if there are wildcards or prevented globally.
+    // If there are no wildcards, add term to the list of user-entered terms
+    if (!haswild)
+	m_hld.uterms.insert(term);
+
+    bool nostemexp = (mods & SearchDataClause::SDCM_NOSTEMMING) != 0;
+
+    // No stem expansion if there are wildcards or if prevented by caller
     if (haswild || m_stemlang.empty()) {
 	LOGDEB2(("expandTerm: found wildcards or stemlang empty: no exp\n"));
 	nostemexp = true;
     }
 
-    if (!haswild)
-	m_hld.uterms.insert(term);
-
-    if (nostemexp && !haswild) {
+    bool noexpansion = nostemexp && !haswild;
+
+#ifndef RCL_INDEX_STRIPCHARS
+    bool diac_sensitive = (mods & SearchDataClause::SDCM_DIACSENS) != 0;
+    bool case_sensitive = (mods & SearchDataClause::SDCM_CASESENS) != 0;
+
+    if (o_index_stripchars) {
+	diac_sensitive = case_sensitive = false;
+    } else {
+	// If we are working with a raw index, apply the rules for case and 
+	// diacritics sensitivity.
+
+	// If any character has a diacritic, we become
+	// diacritic-sensitive. Note that the way that the test is
+	// performed (conversion+comparison) will automatically ignore
+	// accented characters which are actually a separate letter
+	if (unachasaccents(term))
+	    diac_sensitive = true;
+
+	// If any character apart the first is uppercase, we become
+	// case-sensitive.  The first character is reserved for
+	// turning off stemming. You need to use a query language
+	// modifier to search for Floor in a case-sensitive way.
+	Utf8Iter it(term);
+	it++;
+	if (unachasuppercase(term.substr(it.getBpos())))
+	    case_sensitive = true;
+
+	// If we are sensitive to case or diacritics turn stemming off
+	if (diac_sensitive || case_sensitive)
+	    nostemexp = true;
+
+	if (!case_sensitive || !diac_sensitive)
+	    noexpansion = false;
+    }
+#endif
+
+    if (noexpansion) {
 	sterm = term;
-	exp.resize(1);
-	exp[0] = prefix + term;
-    } else {
-	TermMatchResult res;
-	if (haswild) {
-	    m_db.termMatch(Rcl::Db::ET_WILD, m_stemlang, term, res, -1, 
-                           m_field);
-	} else {
-	    sterm = term;
-	    m_db.termMatch(Rcl::Db::ET_STEM, m_stemlang, term, res, -1, 
-			   m_field);
-	}
-	for (vector<TermMatchEntry>::const_iterator it = res.entries.begin(); 
-	     it != res.entries.end(); it++) {
-	    exp.push_back(it->term);
-	}
-    }
+	oexp.push_back(prefix + term);
+	LOGDEB(("ExpandTerm: final: %s\n", stringsToString(oexp).c_str()));
+	return;
+    } 
+
+    SynTermTransUnac unacfoldtrans(UNACOP_UNACFOLD);
+    XapComputableSynFamMember synac(m_db.m_ndb->xrdb, synFamDiCa, "all", 
+				    &unacfoldtrans);
+    vector<string> lexp;
+
+    TermMatchResult res;
+    if (haswild) {
+	// Note that if there are wildcards, we do a direct from-index
+	// expansion, which means that we are casediac-sensitive. There
+	// would be nothing to prevent us to expand from the casediac
+	// synonyms first. To be done later
+	m_db.termMatch(Rcl::Db::ET_WILD, m_stemlang, term, res, -1, 
+		       m_field);
+	goto termmatchtoresult;
+    }
+
+    sterm = term;
+
+#ifdef RCL_INDEX_STRIPCHARS
+
+    m_db.termMatch(Rcl::Db::ET_STEM, m_stemlang, term, res, -1, m_field);
+
+#else
+
+    if (o_index_stripchars) {
+	// If the index is raw, we can only come here if nostemexp is unset
+	// and we just need stem expansion.
+	m_db.termMatch(Rcl::Db::ET_STEM, m_stemlang, term, res, -1, m_field);
+	goto termmatchtoresult;
+    } 
+
+    // No stem expansion when diacritic or case sensitivity is set, it
+    // makes no sense (it would mess with the diacritics anyway if
+    // they are not in the stem part).  In these 3 cases, perform
+    // appropriate expansion from the charstripping db, and do a bogus
+    // wildcard expansion (there is no wild card) to generate the
+    // result:
+
+    if (diac_sensitive && case_sensitive) {
+	// No expansion whatsoever
+	m_db.termMatch(Rcl::Db::ET_WILD, m_stemlang, term, res, -1, m_field);
+	goto termmatchtoresult;
+    }
+
+    if (diac_sensitive) {
+	// Expand for accents and case, filtering for same accents,
+	// then bogus wildcard expansion for generating result
+	SynTermTransUnac foldtrans(UNACOP_FOLD);
+	synac.synExpand(term, lexp, &foldtrans);
+	goto exptotermatch;
+    } 
+
+    if (case_sensitive) {
+	// Expand for accents and case, filtering for same case, then
+	// bogus wildcard expansion for generating result
+	SynTermTransUnac unactrans(UNACOP_UNAC);
+	synac.synExpand(term, lexp, &unactrans);
+	goto exptotermatch;
+    }
+
+    // We are neither accent- nor case- sensitive and may need stem
+    // expansion or not.
+
+    // Expand for accents and case
+    synac.synExpand(term, lexp);
+    LOGDEB(("ExpTerm: casediac: %s\n", stringsToString(lexp).c_str()));
+    if (nostemexp)
+	goto exptotermatch;
+
+    // Need stem expansion. Lowercase the result of accent and case
+    // expansion for input to stemdb.
+    for (unsigned int i = 0; i < lexp.size(); i++) {
+	string lower;
+	unacmaybefold(lexp[i], lower, "UTF-8", UNACOP_FOLD);
+	lexp[i] = lower;
+    }
+    sort(lexp.begin(), lexp.end());
+    {
+	vector<string>::iterator uit = unique(lexp.begin(), lexp.end());
+	lexp.resize(uit - lexp.begin());
+	StemDb db(m_db.m_ndb->xrdb);
+	vector<string> exp1;
+	for (vector<string>::const_iterator it = lexp.begin(); 
+	     it != lexp.end(); it++) {
+	    db.stemExpand(m_stemlang, *it, exp1);
+	}
+	LOGDEB(("ExpTerm: stem: %s\n", stringsToString(exp1).c_str()));
+
+	// Expand the resulting list for case (all stemdb content
+	// is lowercase)
+	lexp.clear();
+	for (vector<string>::const_iterator it = exp1.begin(); 
+	     it != exp1.end(); it++) {
+	    synac.synExpand(*it, lexp);
+	}
+	sort(lexp.begin(), lexp.end());
+	uit = unique(lexp.begin(), lexp.end());
+	lexp.resize(uit - lexp.begin());
+    }
+    LOGDEB(("ExpTerm: case exp of stem: %s\n", stringsToString(lexp).c_str()));
+
+    // Bogus wildcard expand to generate the result
+exptotermatch:
+    for (vector<string>::const_iterator it = lexp.begin();
+	 it != lexp.end(); it++) {
+	m_db.termMatch(Rcl::Db::ET_WILD, m_stemlang, *it, 
+		       res, -1, m_field);
+    }
+#endif
+
+    // Term match entries to vector of terms
+termmatchtoresult:
+    for (vector<TermMatchEntry>::const_iterator it = res.entries.begin(); 
+	 it != res.entries.end(); it++) {
+	oexp.push_back(it->term);
+    }
+    LOGDEB(("ExpandTerm: final: %s\n", stringsToString(oexp).c_str()));
 }
 
 // Do distribution of string vectors: a,b c,d -> a,c a,d b,c b,d
@@ -710,21 +896,22 @@
     }
 }
 
-void StringToXapianQ::processSimpleSpan(const string& span, bool nostemexp,
+void StringToXapianQ::processSimpleSpan(const string& span, 
+					int mods,
 					vector<Xapian::Query> &pqueries)
 {
-    LOGDEB2(("StringToXapianQ::processSimpleSpan: [%s] nostemexp %d\n",
-	     span.c_str(), int(nostemexp)));
+    LOGDEB0(("StringToXapianQ::processSimpleSpan: [%s] mods 0x%x\n",
+	    span.c_str(), (unsigned int)mods));
     vector<string> exp;  
     string sterm; // dumb version of user term
 
     string prefix;
     const FieldTraits *ftp;
     if (!m_field.empty() && m_db.fieldToTraits(m_field, &ftp)) {
-	prefix = ftp->pfx;
-    }
-
-    expandTerm(nostemexp, span, exp, sterm, prefix);
+	prefix = wrap_prefix(ftp->pfx);
+    }
+
+    expandTerm(mods, span, exp, sterm, prefix);
     
     // Set up the highlight data. No prefix should go in there
     for (vector<string>::const_iterator it = exp.begin(); 
@@ -755,8 +942,9 @@
 // queries if the terms get expanded by stemming or wildcards (we
 // don't do stemming for PHRASE though)
 void StringToXapianQ::processPhraseOrNear(TextSplitQ *splitData, 
+					  int mods,
 					  vector<Xapian::Query> &pqueries,
-					  bool useNear, int slack, int mods)
+					  bool useNear, int slack)
 {
     Xapian::Query::op op = useNear ? Xapian::Query::OP_NEAR : 
 	Xapian::Query::OP_PHRASE;
@@ -769,7 +957,7 @@
     string prefix;
     const FieldTraits *ftp;
     if (!m_field.empty() && m_db.fieldToTraits(m_field, &ftp)) {
-	prefix = ftp->pfx;
+	prefix = wrap_prefix(ftp->pfx);
     }
 
     if (mods & Rcl::SearchDataClause::SDCM_ANCHORSTART) {
@@ -790,10 +978,12 @@
 	    || hadmultiple
 #endif // single OR inside NEAR
 	    ;
-
+	int lmods = mods;
+	if (nostemexp)
+	    lmods |= SearchDataClause::SDCM_NOSTEMMING;
 	string sterm;
 	vector<string> exp;
-	expandTerm(nostemexp, *it, exp, sterm, prefix);
+	expandTerm(lmods, *it, exp, sterm, prefix);
 	LOGDEB0(("ProcessPhraseOrNear: exp size %d\n", exp.size()));
 	listVector("", exp);
 	// groups is used for highlighting, we don't want prefixes in there.
@@ -882,15 +1072,18 @@
  *   count)
  */
 bool StringToXapianQ::processUserString(const string &iq,
+					int mods, 
 					string &ermsg,
 					vector<Xapian::Query> &pqueries,
-					const StopList& stops,
 					int slack, 
 					bool useNear
 					)
 {
-    LOGDEB(("StringToXapianQ:: query string: [%s], slack %d, near %d\n", iq.c_str(), slack, useNear));
+    LOGDEB(("StringToXapianQ:: qstr [%s] mods 0x%x slack %d near %d\n", 
+	    iq.c_str(), mods, slack, useNear));
     ermsg.erase();
+
+    const StopList stops = m_db.getStopList();
 
     // Simple whitespace-split input into user-level words and
     // double-quoted phrases: word1 word2 "this is a phrase". 
@@ -908,8 +1101,10 @@
 	for (vector<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;
+	    // Anchoring modifiers
+	    int amods = stringToMods(*it);
+	    int terminc = amods != 0 ? 1 : 0;
+	    mods |= amods;
 	    // 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. 
@@ -930,11 +1125,15 @@
             TermProcStop tpstop(nxt, stops); nxt = &tpstop;
             //TermProcCommongrams tpcommon(nxt, stops); nxt = &tpcommon;
             //tpcommon.onlygrams(true);
-	    TermProcPrep tpprep(nxt); nxt = &tpprep;
+	    TermProcPrep tpprep(nxt);
+#ifndef RCL_INDEX_STRIPCHARS
+	    if (o_index_stripchars)
+#endif
+		nxt = &tpprep;
 
 	    TextSplitQ splitter(TextSplit::Flags(TextSplit::TXTS_ONLYSPANS | 
-						  TextSplit::TXTS_KEEPWILD), 
-                                 stops, nxt);
+						 TextSplit::TXTS_KEEPWILD), 
+				stops, nxt);
 	    tpq.setTSQ(&splitter);
 	    splitter.text_to_words(*it);
 
@@ -944,14 +1143,17 @@
 	    switch (splitter.terms.size() + terminc) {
 	    case 0: 
 		continue;// ??
-	    case 1: 
+	    case 1: {
+		int lmods = mods;
+		if (splitter.nostemexps.front())
+		    lmods |= SearchDataClause::SDCM_NOSTEMMING;
 		m_hld.ugroups.push_back(vector<string>(1, *it));
-		processSimpleSpan(splitter.terms.front(), 
-                                  splitter.nostemexps.front(), pqueries);
+		processSimpleSpan(splitter.terms.front(), lmods, pqueries);
+	    }
 		break;
 	    default:
 		m_hld.ugroups.push_back(vector<string>(1, *it));
-		processPhraseOrNear(&splitter, pqueries, useNear, slack, mods);
+		processPhraseOrNear(&splitter, mods, pqueries, useNear, slack);
 	    }
 	}
     } catch (const Xapian::Error &e) {
@@ -971,13 +1173,10 @@
 }
 
 // Translate a simple OR, AND, or EXCL search clause. 
-bool SearchDataClauseSimple::toNativeQuery(Rcl::Db &db, void *p, 
-					   const string& stemlang)
-{
-    const string& l_stemlang = (m_modifiers&SDCM_NOSTEMMING)? cstr_null:
-	stemlang;
+bool SearchDataClauseSimple::toNativeQuery(Rcl::Db &db, void *p)
+{
     LOGDEB2(("SearchDataClauseSimple::toNativeQuery: stemlang [%s]\n",
-	     stemlang.c_str()));
+	     getStemLang().c_str()));
 
     Xapian::Query *qp = (Xapian::Query *)p;
     *qp = Xapian::Query();
@@ -1000,8 +1199,8 @@
 	(m_parentSearch && !m_parentSearch->haveWildCards()) || 
 	(m_parentSearch == 0 && !m_haveWildCards);
 
-    StringToXapianQ tr(db, m_hldata, m_field, l_stemlang, doBoostUserTerm);
-    if (!tr.processUserString(m_text, m_reason, pqueries, db.getStopList()))
+    StringToXapianQ tr(db, m_hldata, m_field, getStemLang(), doBoostUserTerm);
+    if (!tr.processUserString(m_text, getModifiers(), m_reason, pqueries))
 	return false;
     if (pqueries.empty()) {
 	LOGERR(("SearchDataClauseSimple: resolved to null query\n"));
@@ -1024,8 +1223,7 @@
 // about expanding multiple fragments in the past. We just take the
 // value blanks and all and expand this against the indexed unsplit
 // file names
-bool SearchDataClauseFilename::toNativeQuery(Rcl::Db &db, void *p, 
-					     const string&)
+bool SearchDataClauseFilename::toNativeQuery(Rcl::Db &db, void *p)
 {
     Xapian::Query *qp = (Xapian::Query *)p;
     *qp = Xapian::Query();
@@ -1041,11 +1239,8 @@
 }
 
 // Translate NEAR or PHRASE clause. 
-bool SearchDataClauseDist::toNativeQuery(Rcl::Db &db, void *p, 
-					 const string& stemlang)
-{
-    const string& l_stemlang = (m_modifiers&SDCM_NOSTEMMING)? cstr_null:
-	stemlang;
+bool SearchDataClauseDist::toNativeQuery(Rcl::Db &db, void *p)
+{
     LOGDEB(("SearchDataClauseDist::toNativeQuery\n"));
 
     Xapian::Query *qp = (Xapian::Query *)p;
@@ -1069,8 +1264,8 @@
     }
     string s = cstr_dquote + m_text + cstr_dquote;
     bool useNear = (m_tp == SCLT_NEAR);
-    StringToXapianQ tr(db, m_hldata, m_field, l_stemlang, doBoostUserTerm);
-    if (!tr.processUserString(s, m_reason, pqueries, db.getStopList(),
+    StringToXapianQ tr(db, m_hldata, m_field, getStemLang(), doBoostUserTerm);
+    if (!tr.processUserString(s, getModifiers(), m_reason, pqueries, 
 			      m_slack, useNear))
 	return false;
     if (pqueries.empty()) {