Arrange for plaintorich to keep track of the link between match area and search terms. Use this in preview to allow walking the matches for a given search term group

This commit is contained in:
Jean-Francois Dockes 2012-08-18 12:15:43 +02:00
parent 35ed2b3ca9
commit 1ed0f4ddf8
8 changed files with 291 additions and 156 deletions

View file

@ -204,24 +204,32 @@ void RecollProtocol::queryDetails()
class PlainToRichKio : public PlainToRich {
public:
PlainToRichKio(const string& nm)
: PlainToRich() , m_name(nm)
: m_name(nm)
{
}
virtual ~PlainToRichKio() {}
virtual string header() {
if (m_inputhtml) {
return snull;
return cstr_null;
} else {
return
string("<html><head>"
return string("<html><head>"
"<META http-equiv=\"Content-Type\""
"content=\"text/html;charset=UTF-8\"><title>") +
m_name +
string("</title></head><body><pre>");
"content=\"text/html;charset=UTF-8\"><title>").
append(m_name).
append("</title></head><body><pre>");
}
}
virtual string startMatch() {return string("<font color=\"blue\">");}
virtual string endMatch() {return string("</font>");}
virtual string startMatch(unsigned int)
{
return string("<font color=\"blue\">");
}
virtual string endMatch()
{
return string("</font>");
}
const string &m_name;
};

View file

@ -68,54 +68,130 @@ using std::pair;
// Subclass plainToRich to add <termtag>s and anchors to the preview text
class PlainToRichQtPreview : public PlainToRich {
public:
int lastanchor;
PlainToRichQtPreview()
: m_curanchor(1), m_lastanchor(0)
{
lastanchor = 0;
}
virtual ~PlainToRichQtPreview() {}
virtual string header() {
bool haveAnchors()
{
return m_lastanchor != 0;
}
virtual string header()
{
if (m_inputhtml) {
return snull;
return cstr_null;
} else {
if (prefs.previewPlainPre) {
m_eolbr = false;
return string("<qt><head><title></title></head><body>"
"<pre>");
// Note we could also use the following for line-folding instead of <br>s
// This would be possible without recomputing the whole text, much better perfs
// for toggling wrap/no-wrap
// "<pre style=\"white-space: pre-wrap\">");
// Note: we could also use the following for
// line-folding instead of <br>s This would be
// possible without recomputing the whole text, much
// better perfs for toggling wrap/no-wrap:
// <pre style=\"white-space: pre-wrap\">
} else {
m_eolbr = true;
return string("<qt><head><title></title></head><body>");
}
}
}
virtual string startMatch()
virtual string startMatch(unsigned int grpidx)
{
return string("<span style='color: ")
+ string((const char *)(prefs.qtermcolor.toUtf8()))
+ string(";font-weight: bold;")
+ string("'>");
LOGDEB2(("startMatch, grpidx %u\n", grpidx));
grpidx = m_hdata->grpsugidx[grpidx];
LOGDEB2(("startMatch, ugrpidx %u\n", grpidx));
m_groupanchors[grpidx].push_back(++m_lastanchor);
m_groupcuranchors[grpidx] = 0;
return string("<span style='color: ").
append((const char *)(prefs.qtermcolor.toUtf8())).
append(";font-weight: bold;").
append("'>").
append("<a name=\"").
append(termAnchorName(m_lastanchor)).
append("\">");
}
virtual string endMatch() {return string("</span>");}
virtual string termAnchorName(int i) {
virtual string endMatch()
{
return string("</a></span>");
}
virtual string termAnchorName(int i) const
{
static const char *termAnchorNameBase = "TRM";
char acname[sizeof(termAnchorNameBase) + 20];
sprintf(acname, "%s%d", termAnchorNameBase, i);
if (i > lastanchor)
lastanchor = i;
return string(acname);
}
virtual string startAnchor(int i) {
return string("<a name=\"") + termAnchorName(i) + "\">";
virtual string startChunk()
{
return "<pre>";
}
virtual string endAnchor() {
return string("</a>");
int nextAnchorNum(int grpidx)
{
LOGDEB2(("nextAnchorNum: group %d\n", grpidx));
map<unsigned int, unsigned int>::iterator curit =
m_groupcuranchors.find(grpidx);
map<unsigned int, vector<int> >::iterator vecit =
m_groupanchors.find(grpidx);
if (grpidx == -1 || curit == m_groupcuranchors.end() ||
vecit == m_groupanchors.end()) {
if (m_curanchor >= m_lastanchor)
m_curanchor = 1;
else
m_curanchor++;
} else {
if (curit->second >= vecit->second.size() -1)
m_groupcuranchors[grpidx] = 0;
else
m_groupcuranchors[grpidx]++;
m_curanchor = vecit->second[m_groupcuranchors[grpidx]];
LOGDEB2(("nextAnchorNum: curanchor now %d\n", m_curanchor));
}
virtual string startChunk() { return "<pre>";}
return m_curanchor;
}
int prevAnchorNum(int grpidx)
{
map<unsigned int, unsigned int>::iterator curit =
m_groupcuranchors.find(grpidx);
map<unsigned int, vector<int> >::iterator vecit =
m_groupanchors.find(grpidx);
if (grpidx == -1 || curit == m_groupcuranchors.end() ||
vecit == m_groupanchors.end()) {
if (m_curanchor <= 1)
m_curanchor = m_lastanchor;
else
m_curanchor--;
} else {
if (curit->second <= 0)
m_groupcuranchors[grpidx] = vecit->second.size() -1;
else
m_groupcuranchors[grpidx]--;
m_curanchor = vecit->second[m_groupcuranchors[grpidx]];
}
return m_curanchor;
}
QString curAnchorName() const
{
return QString::fromUtf8(termAnchorName(m_curanchor).c_str());
}
private:
int m_curanchor;
int m_lastanchor;
// Lists of anchor numbers (match locations) for the term (groups)
// in the query (the map key is and index into HighlightData.groups).
map<unsigned int, vector<int> > m_groupanchors;
map<unsigned int, unsigned int> m_groupcuranchors;
};
void Preview::init()
@ -141,8 +217,24 @@ void Preview::init()
QHBoxLayout *layout3 = new QHBoxLayout(0);
searchLabel = new QLabel(this);
layout3->addWidget(searchLabel);
searchTextLine = new QLineEdit(this);
layout3->addWidget(searchTextLine);
searchTextCMB = new QComboBox(this);
searchTextCMB->setEditable(true);
searchTextCMB->setInsertPolicy(QComboBox::NoInsert);
searchTextCMB->setDuplicatesEnabled(false);
for (unsigned int i = 0; i < m_hData.ugroups.size(); i++) {
QString s;
for (unsigned int j = 0; j < m_hData.ugroups[i].size(); j++) {
s.append(QString::fromUtf8(m_hData.ugroups[i][j].c_str()));
if (j != m_hData.ugroups[i].size()-1)
s.append(" ");
}
searchTextCMB->addItem(s);
}
searchTextCMB->setEditText("");
layout3->addWidget(searchTextCMB);
nextButton = new QPushButton(this);
nextButton->setEnabled(TRUE);
layout3->addWidget(nextButton);
@ -160,7 +252,7 @@ void Preview::init()
resize(QSize(640, 480).expandedTo(minimumSizeHint()));
// buddies
searchLabel->setBuddy(searchTextLine);
searchLabel->setBuddy(searchTextCMB);
searchLabel->setText(tr("&Search for:"));
nextButton->setText(tr("&Next"));
@ -176,26 +268,25 @@ void Preview::init()
"RCL.SEARCH.PREVIEW");
// signals and slots connections
connect(searchTextLine, SIGNAL(textChanged(const QString&)),
this, SLOT(searchTextLine_textChanged(const QString&)));
connect(searchTextCMB, SIGNAL(activated(int)),
this, SLOT(searchTextFromIndex(int)));
connect(searchTextCMB, SIGNAL(editTextChanged(const QString&)),
this, SLOT(searchTextChanged(const QString&)));
connect(nextButton, SIGNAL(clicked()), this, SLOT(nextPressed()));
connect(prevButton, SIGNAL(clicked()), this, SLOT(prevPressed()));
connect(clearPB, SIGNAL(clicked()), searchTextLine, SLOT(clear()));
connect(clearPB, SIGNAL(clicked()), searchTextCMB, SLOT(clearEditText()));
connect(pvTab, SIGNAL(currentChanged(QWidget *)),
this, SLOT(currentChanged(QWidget *)));
connect(bt, SIGNAL(clicked()), this, SLOT(closeCurrentTab()));
m_dynSearchActive = false;
m_canBeep = true;
m_currentW = 0;
if (prefs.pvwidth > 100) {
resize(prefs.pvwidth, prefs.pvheight);
}
m_loading = false;
currentChanged(pvTab->currentWidget());
m_justCreated = true;
m_haveAnchors = false;
m_curAnchor = 1;
}
void Preview::closeEvent(QCloseEvent *e)
@ -273,11 +364,11 @@ bool Preview::eventFilter(QObject *target, QEvent *event)
} else if (m_dynSearchActive) {
if (keyEvent->key() == Qt::Key_F3) {
LOGDEB2(("Preview::eventFilter: got F3\n"));
doSearch(searchTextLine->text(), true, false);
doSearch(searchTextCMB->currentText(), true, false);
return true;
}
if (target != searchTextLine)
return QApplication::sendEvent(searchTextLine, event);
if (target != searchTextCMB)
return QApplication::sendEvent(searchTextCMB, event);
} else {
if (edit &&
(target == edit || target == edit->viewport())) {
@ -285,7 +376,7 @@ bool Preview::eventFilter(QObject *target, QEvent *event)
(keyEvent->key() == Qt::Key_F &&
(keyEvent->modifiers() & Qt::ControlModifier))) {
LOGDEB2(("Preview::eventFilter: got / or C-F\n"));
searchTextLine->setFocus();
searchTextCMB->setFocus();
m_dynSearchActive = true;
return true;
} else if (keyEvent->key() == Qt::Key_Space) {
@ -307,23 +398,27 @@ bool Preview::eventFilter(QObject *target, QEvent *event)
return false;
}
void Preview::searchTextLine_textChanged(const QString & text)
void Preview::searchTextChanged(const QString & text)
{
LOGDEB2(("search line text changed. text: '%s'\n", text.ascii()));
LOGDEB1(("Search line text changed. text: '%s'\n",
(const char *)text.toAscii()));
m_searchTextFromIndex = -1;
if (text.isEmpty()) {
m_dynSearchActive = false;
// nextButton->setEnabled(false);
// prevButton->setEnabled(false);
clearPB->setEnabled(false);
} else {
m_dynSearchActive = true;
// nextButton->setEnabled(true);
// prevButton->setEnabled(true);
clearPB->setEnabled(true);
doSearch(text, false, false);
}
}
void Preview::searchTextFromIndex(int idx)
{
LOGDEB1(("search line from index %d\n", idx));
m_searchTextFromIndex = idx;
}
PreviewTextEdit *Preview::currentEditor()
{
LOGDEB2(("Preview::currentEditor()\n"));
@ -351,8 +446,8 @@ void Preview::emitSaveDocToFile()
void Preview::doSearch(const QString &_text, bool next, bool reverse,
bool wordOnly)
{
LOGDEB(("Preview::doSearch: text [%s] txtlen %d next %d rev %d word %d\n",
(const char *)_text.toUtf8(), _text.length(), int(next),
LOGDEB(("Preview::doSearch: text [%s] idx %d next %d rev %d word %d\n",
(const char *)_text.toUtf8(), m_searchTextFromIndex, int(next),
int(reverse), int(wordOnly)));
QString text = _text;
@ -363,25 +458,19 @@ void Preview::doSearch(const QString &_text, bool next, bool reverse,
return;
}
if (text.isEmpty()) {
if (m_haveAnchors == false) {
if (text.isEmpty() || m_searchTextFromIndex != -1) {
if (!edit->m_plaintorich->haveAnchors()) {
LOGDEB(("NO ANCHORS\n"));
return;
}
// The combobox indices are equal to the search ugroup indices
// in hldata, that's how we built the list.
if (reverse) {
if (m_curAnchor == 1)
m_curAnchor = edit->m_plaintorich->lastanchor;
else
m_curAnchor--;
edit->m_plaintorich->prevAnchorNum(m_searchTextFromIndex);
} else {
if (m_curAnchor == edit->m_plaintorich->lastanchor)
m_curAnchor = 1;
else
m_curAnchor++;
edit->m_plaintorich->nextAnchorNum(m_searchTextFromIndex);
}
LOGDEB(("m_curAnchor: %d\n", m_curAnchor));
QString aname =
QString::fromUtf8(edit->m_plaintorich->termAnchorName(m_curAnchor).c_str());
QString aname = edit->m_plaintorich->curAnchorName();
LOGDEB(("Calling scrollToAnchor(%s)\n", (const char *)aname.toUtf8()));
edit->scrollToAnchor(aname);
// Position the cursor approximately at the anchor (top of
@ -440,14 +529,14 @@ void Preview::doSearch(const QString &_text, bool next, bool reverse,
void Preview::nextPressed()
{
LOGDEB2(("PreviewTextEdit::nextPressed\n"));
doSearch(searchTextLine->text(), true, false);
LOGDEB2(("Preview::nextPressed\n"));
doSearch(searchTextCMB->currentText(), true, false);
}
void Preview::prevPressed()
{
LOGDEB2(("PreviewTextEdit::prevPressed\n"));
doSearch(searchTextLine->text(), true, true);
LOGDEB2(("Preview::prevPressed\n"));
doSearch(searchTextCMB->currentText(), true, true);
}
// Called when user clicks on tab
@ -456,7 +545,6 @@ void Preview::currentChanged(QWidget * tw)
LOGDEB2(("PreviewTextEdit::currentChanged\n"));
PreviewTextEdit *edit =
tw->findChild<PreviewTextEdit*>("pvEdit");
m_currentW = tw;
LOGDEB1(("Preview::currentChanged(). Editor: %p\n", edit));
if (edit == 0) {
@ -470,7 +558,7 @@ void Preview::currentChanged(QWidget * tw)
connect(this, SIGNAL(printCurrentPreviewRequest()), edit, SLOT(print()));
edit->installEventFilter(this);
edit->viewport()->installEventFilter(this);
searchTextLine->installEventFilter(this);
searchTextCMB->installEventFilter(this);
emit(previewExposed(this, m_searchId, edit->m_docnum));
}
@ -507,7 +595,7 @@ PreviewTextEdit *Preview::addEditorTab()
void Preview::setCurTabProps(const Rcl::Doc &doc, int docnum)
{
LOGDEB1(("PreviewTextEdit::setCurTabProps\n"));
LOGDEB1(("Preview::setCurTabProps\n"));
QString title;
string ctitle;
if (doc.getmeta(Rcl::Doc::keytt, &ctitle) && !ctitle.empty()) {
@ -721,8 +809,6 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
LoadGuard guard(&m_loading);
CancelCheck::instance().setCancel(false);
m_haveAnchors = false;
setCurTabProps(idoc, docnum);
QString msg = QString("Loading: %1 (size %2 bytes)")
@ -956,23 +1042,20 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
// Position the editor so that the first search term is visible
m_haveAnchors = editor->m_plaintorich->lastanchor != 0;
if (searchTextLine->text().length() != 0) {
if (searchTextCMB->currentText().length() != 0) {
// If there is a current search string, perform the search
m_canBeep = true;
doSearch(searchTextLine->text(), true, false);
doSearch(searchTextCMB->currentText(), true, false);
} else {
// Position to the first query term
if (m_haveAnchors) {
QString aname =
QString::fromUtf8(editor->m_plaintorich->termAnchorName(1).c_str());
if (editor->m_plaintorich->haveAnchors()) {
QString aname = editor->m_plaintorich->curAnchorName();
LOGDEB2(("Call movetoanchor(%s)\n", (const char *)aname.toUtf8()));
editor->scrollToAnchor(aname);
// Position the cursor approximately at the anchor (top of
// viewport) so that searches start from here
QTextCursor cursor = editor->cursorForPosition(QPoint(0, 0));
editor->setTextCursor(cursor);
m_curAnchor = 1;
}
}
@ -989,14 +1072,15 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
return true;
}
PreviewTextEdit::PreviewTextEdit(QWidget* parent,const char* name, Preview *pv)
: QTextEdit(parent), m_preview(pv), m_dspflds(false), m_docnum(-1)
PreviewTextEdit::PreviewTextEdit(QWidget* parent, const char* nm, Preview *pv)
: QTextEdit(parent), m_preview(pv),
m_plaintorich(new PlainToRichQtPreview()),
m_dspflds(false), m_docnum(-1)
{
setContextMenuPolicy(Qt::CustomContextMenu);
setObjectName(name);
setObjectName(nm);
connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
this, SLOT(createPopupMenu(const QPoint&)));
m_plaintorich = new PlainToRichQtPreview();
}
PreviewTextEdit::~PreviewTextEdit()

View file

@ -58,22 +58,27 @@ protected:
void mouseDoubleClickEvent(QMouseEvent *);
private:
PlainToRichQtPreview *m_plaintorich;
Preview *m_preview;
PlainToRichQtPreview *m_plaintorich;
bool m_dspflds;
string m_url; // filename for this tab
string m_ipath; // Internal doc path inside file
int m_docnum; // Index of doc in db search results.
// doc out of internfile (previous fields come from the index) with
// main text erased (for space).
Rcl::Doc m_fdoc;
// The input doc out of the index/query list
Rcl::Doc m_dbdoc;
// Saved rich (or plain actually) text: the textedit seems to
// sometimes (but not always) return its text stripped of tags, so
// this is needed (for printing for example)
QString m_richtxt;
Qt::TextFormat m_format;
// Temporary file name (possibly, if displaying image). The
// TempFile itself is kept inside main.cpp (because that's where
// signal cleanup happens), but we use its name to ask for release
@ -92,14 +97,14 @@ class Preview : public QWidget {
Preview(int sid, // Search Id
const HighlightData& hdata) // Search terms etc. for highlighting
: QWidget(0), m_searchId(sid), m_hData(hdata)
: QWidget(0), m_searchId(sid), m_searchTextFromIndex(-1), m_hData(hdata)
{
init();
}
~Preview(){}
virtual void closeEvent(QCloseEvent *e );
virtual bool eventFilter(QObject *target, QEvent *event );
/**
* Arrange for the document to be displayed either by exposing the tab
* if already loaded, or by creating a new tab and loading it.
@ -112,7 +117,8 @@ class Preview : public QWidget {
friend class PreviewTextEdit;
public slots:
virtual void searchTextLine_textChanged(const QString& text);
virtual void searchTextChanged(const QString& text);
virtual void searchTextFromIndex(int);
virtual void doSearch(const QString& str, bool next, bool reverse,
bool wo = false);
virtual void nextPressed();
@ -138,18 +144,17 @@ private:
int m_searchId;
bool m_dynSearchActive;
// Index value the search text comes from. -1 if text was edited
int m_searchTextFromIndex;
bool m_canBeep;
bool m_loading;
QWidget *m_currentW;
HighlightData m_hData;
bool m_justCreated; // First tab create is different
bool m_haveAnchors; // Search terms are marked in text
int m_lastAnchor; // Number of last anchor. Then rewind to 1
int m_curAnchor;
QTabWidget* pvTab;
QLabel* searchLabel;
QLineEdit* searchTextLine;
QComboBox *searchTextCMB;
QPushButton* nextButton;
QPushButton* prevButton;
QPushButton* clearPB;

View file

@ -250,12 +250,22 @@ string QtGuiResListPager::iconUrl(RclConfig *config, Rcl::Doc& doc)
class PlainToRichQtReslist : public PlainToRich {
public:
virtual ~PlainToRichQtReslist() {}
virtual string startMatch() {
virtual string startMatch(unsigned int idx)
{
if (m_hdata) {
string s1, s2;
stringsToString<vector<string> >(m_hdata->groups[idx], s1);
stringsToString<vector<string> >(m_hdata->ugroups[m_hdata->grpsugidx[idx]], s2);
LOGDEB(("Reslist startmatch: group %s user group %s\n", s1.c_str(), s2.c_str()));
}
return string("<span class='rclmatch' style='color: ")
+ string((const char *)prefs.qtermcolor.toAscii()) + string("'>");
}
virtual string endMatch() {return string("</span>");}
virtual string endMatch()
{
return string("</span>");
}
};
static PlainToRichQtReslist g_hiliter;

View file

@ -38,7 +38,6 @@ public:
setupUi(this);
init();
}
~SSearch(){}
virtual void init();
virtual void setAnyTermMode();

View file

@ -49,13 +49,22 @@ static string vecStringToString(const vector<string>& t)
return sterms;
}
struct MatchEntry {
pair<int, int> offs;
unsigned int grpidx;
MatchEntry(int sta, int sto, unsigned int idx)
: offs(sta, sto), grpidx(idx)
{
}
};
// Text splitter used to take note of the position of query terms
// inside the result text. This is then used to insert highlight tags.
class TextSplitPTR : public TextSplit {
public:
// Out: begin and end byte positions of query terms/groups in text
vector<pair<int, int> > tboffs;
vector<MatchEntry> tboffs;
TextSplitPTR(const HighlightData& hdata)
: m_wcount(0), m_hdata(hdata)
@ -67,7 +76,7 @@ class TextSplitPTR : public TextSplit {
for (vector<vector<string> >::const_iterator vit = hdata.groups.begin();
vit != hdata.groups.end(); vit++) {
if (vit->size() == 1) {
m_terms.insert(vit->front());
m_terms[vit->front()] = vit - hdata.groups.begin();
} else if (vit->size() > 1) {
for (vector<string>::const_iterator it = vit->begin();
it != vit->end(); it++) {
@ -91,8 +100,9 @@ class TextSplitPTR : public TextSplit {
// pos, bts, bte));
// If this word is a search term, remember its byte-offset span.
if (m_terms.find(dumb) != m_terms.end()) {
tboffs.push_back(pair<int, int>(bts, bte));
map<string, unsigned int>::const_iterator it = m_terms.find(dumb);
if (it != m_terms.end()) {
tboffs.push_back(MatchEntry(bts, bte, (*it).second));
}
// If word is part of a search group, update its positions list
@ -114,13 +124,13 @@ class TextSplitPTR : public TextSplit {
virtual bool matchGroups();
private:
virtual bool matchGroup(const vector<string>& terms, int dist);
virtual bool matchGroup(unsigned int idx);
// Word count. Used to call checkCancel from time to time.
int m_wcount;
// In: user query terms
set<string> m_terms;
map<string, unsigned int> m_terms;
// m_gterms holds all the terms in m_groups, as a set for quick lookup
set<string> m_gterms;
@ -191,9 +201,12 @@ static bool do_proximity_test(int window, vector<vector<int>* >& plists,
return false;
}
// Find NEAR matches for the input group of terms, update highlight map
bool TextSplitPTR::matchGroup(const vector<string>& terms, int window)
// Find NEAR matches for one group of terms, update highlight map
bool TextSplitPTR::matchGroup(unsigned int grpidx)
{
const vector<string>& terms = m_hdata.groups[grpidx];
int window = m_hdata.groups[grpidx].size() + m_hdata.slacks[grpidx];
LOGDEB0(("TextSplitPTR::matchGroup:d %d: %s\n", window,
vecStringToString(terms).c_str()));
@ -203,26 +216,23 @@ bool TextSplitPTR::matchGroup(const vector<string>& terms, int window)
// A revert plist->term map. This is so that we can find who is who after
// sorting the plists by length.
map<vector<int>*, string> plistToTerm;
// For traces
vector<string> realgroup;
// Find the position list for each term in the group. Not all
// necessarily exist (esp for NEAR where terms have been
// stem-expanded: we don't know which matched)
// Find the position list for each term in the group. It is
// possible that this particular group was not actually matched by
// the search, so that some terms are not found.
for (vector<string>::const_iterator it = terms.begin();
it != terms.end(); it++) {
map<string, vector<int> >::iterator pl = m_plists.find(*it);
if (pl == m_plists.end()) {
LOGDEB0(("TextSplitPTR::matchGroup: [%s] not found in m_plists\n",
(*it).c_str()));
continue;
return false;
}
plists.push_back(&(pl->second));
plistToTerm[&(pl->second)] = *it;
realgroup.push_back(*it);
}
LOGDEB0(("TextSplitPTR::matchGroup:d %d:real group after expansion %s\n",
window, vecStringToString(realgroup).c_str()));
// I think this can't actually happen, was useful when we used to
// prune the groups, but doesn't hurt.
if (plists.size() < 2) {
LOGDEB0(("TextSplitPTR::matchGroup: no actual groups found\n"));
return false;
@ -261,15 +271,13 @@ bool TextSplitPTR::matchGroup(const vector<string>& terms, int window)
SETMINMAX(pos, sta, sto);
minpos = sto+1;
// Translate the position window into a byte offset window
int bs = 0;
map<int, pair<int, int> >::iterator i1 = m_gpostobytes.find(sta);
map<int, pair<int, int> >::iterator i2 = m_gpostobytes.find(sto);
if (i1 != m_gpostobytes.end() && i2 != m_gpostobytes.end()) {
LOGDEB0(("TextSplitPTR::matchGroup: pushing bpos %d %d\n",
i1->second.first, i2->second.second));
tboffs.push_back(pair<int, int>(i1->second.first,
i2->second.second));
bs = i1->second.first;
tboffs.push_back(MatchEntry(i1->second.first,
i2->second.second, grpidx));
} else {
LOGDEB(("matchGroup: no bpos found for %d or %d\n", sta, sto));
}
@ -284,22 +292,23 @@ bool TextSplitPTR::matchGroup(const vector<string>& terms, int window)
/** Sort integer pairs by increasing first value and decreasing width */
class PairIntCmpFirst {
public:
bool operator()(pair<int,int> a, pair<int, int>b) {
if (a.first != b.first)
return a.first < b.first;
return a.second > b.second;
bool operator()(const MatchEntry& a, const MatchEntry& b) {
if (a.offs.first != b.offs.first)
return a.offs.first < b.offs.first;
return a.offs.second > b.offs.second;
}
};
// Look for matches to PHRASE and NEAR term groups. Actually, we
// handle all groups as NEAR (ignore order).
// Look for matches to PHRASE and NEAR term groups and finalize the
// matched regions list (sort it by increasing start then decreasing
// length)
// Actually, we handle all groups as NEAR (ignore order).
bool TextSplitPTR::matchGroups()
{
for (unsigned int i = 0; i < m_hdata.groups.size(); i++) {
if (m_hdata.groups[i].size() <= 1)
continue;
matchGroup(m_hdata.groups[i],
m_hdata.groups[i].size() + m_hdata.slacks[i]);
matchGroup(i);
}
// Sort regions by increasing start and decreasing width.
@ -324,6 +333,7 @@ bool PlainToRich::plaintorich(const string& in,
{
Chrono chron;
m_hdata = &hdata;
// Compute the positions for the query terms. We use the text
// splitter to break the text into words, and compare the words to
// the search terms,
@ -346,8 +356,8 @@ bool PlainToRich::plaintorich(const string& in,
// Iterator for the list of input term positions. We use it to
// output highlight tags and to compute term positions in the
// output text
vector<pair<int, int> >::iterator tPosIt = splitter.tboffs.begin();
vector<pair<int, int> >::iterator tPosEnd = splitter.tboffs.end();
vector<MatchEntry>::iterator tPosIt = splitter.tboffs.begin();
vector<MatchEntry>::iterator tPosEnd = splitter.tboffs.end();
#if 0
for (vector<pair<int, int> >::const_iterator it = splitter.tboffs.begin();
@ -365,8 +375,6 @@ bool PlainToRich::plaintorich(const string& in,
int hadcr = 0;
int inindent = 1;
// Value for numbered anchors at each term match
int anchoridx = 1;
// HTML state
bool intag = false, inparamvalue = false;
// My tag state
@ -391,22 +399,20 @@ bool PlainToRich::plaintorich(const string& in,
// we are at or after a term match, mark.
if (tPosIt != tPosEnd) {
int ibyteidx = chariter.getBpos();
if (ibyteidx == tPosIt->first) {
if (ibyteidx == tPosIt->offs.first) {
if (!intag && ibyteidx >= (int)headend) {
*olit += startAnchor(anchoridx);
*olit += startMatch();
*olit += startMatch(tPosIt->grpidx);
}
anchoridx++;
inrcltag = 1;
} else if (ibyteidx == tPosIt->second) {
} else if (ibyteidx == tPosIt->offs.second) {
// Output end of match region tags
if (!intag && ibyteidx > (int)headend) {
*olit += endMatch();
*olit += endAnchor();
}
// Skip all highlight areas that would overlap this one
int crend = tPosIt->second;
while (tPosIt != splitter.tboffs.end() && tPosIt->first < crend)
int crend = tPosIt->offs.second;
while (tPosIt != splitter.tboffs.end() &&
tPosIt->offs.first < crend)
tPosIt++;
inrcltag = 0;
}

View file

@ -21,6 +21,7 @@
#include <list>
#include "hldata.h"
#include "cstr.h"
/**
* A class for highlighting search results. Overridable methods allow
@ -31,7 +32,7 @@
class PlainToRich {
public:
PlainToRich()
: m_inputhtml(false)
: m_inputhtml(false), m_eolbr(false), m_hdata(0)
{
}
@ -71,18 +72,35 @@ public:
);
/* Overridable output methods for headers, highlighting and marking tags */
virtual std::string header() {return snull;}
virtual std::string startMatch() {return snull;}
virtual std::string endMatch() {return snull;}
virtual std::string startAnchor(int) {return snull;}
virtual std::string endAnchor() {return snull;}
virtual std::string startChunk() {return snull;}
virtual std::string header()
{
return cstr_null;
}
/** Return match prefix (e.g.: <div class="match">).
@param groupidx the index into hdata.groups */
virtual std::string startMatch(unsigned int)
{
return cstr_null;
}
/** Return data for end of match area (e.g.: </div>). */
virtual std::string endMatch()
{
return cstr_null;
}
virtual std::string startChunk()
{
return cstr_null;
}
protected:
const std::string snull;
bool m_inputhtml;
// Use <br> to break plain text lines (else caller has used a <pre> tag)
bool m_eolbr;
const HighlightData *m_hdata;
};
#endif /* _PLAINTORICH_H_INCLUDED_ */

View file

@ -40,9 +40,14 @@ static const string cstr_hlfontcolor("<font color=\"blue\">");
static const string cstr_hlendfont("</font>");
class PlainToRichHtReslist : public PlainToRich {
public:
virtual ~PlainToRichHtReslist() {}
virtual string startMatch() {return cstr_hlfontcolor;}
virtual string endMatch() {return cstr_hlendfont;}
virtual string startMatch(unsigned int)
{
return cstr_hlfontcolor;
}
virtual string endMatch()
{
return cstr_hlendfont;
}
};
static PlainToRichHtReslist g_hiliter;