2009-02-05 14:21:47 +00:00
|
|
|
/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.berlios.de>
|
2009-01-28 20:55:45 +00:00
|
|
|
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
|
|
|
|
|
|
|
#include "article_maker.hh"
|
|
|
|
#include "config.hh"
|
|
|
|
#include "htmlescape.hh"
|
|
|
|
#include "utf8.hh"
|
2009-04-18 17:20:12 +00:00
|
|
|
#include "wstring_qt.hh"
|
2009-04-10 12:48:40 +00:00
|
|
|
#include <limits.h>
|
2009-01-28 20:55:45 +00:00
|
|
|
#include <QFile>
|
|
|
|
|
|
|
|
using std::vector;
|
|
|
|
using std::string;
|
2009-04-18 17:20:12 +00:00
|
|
|
using gd::wstring;
|
2009-01-28 20:55:45 +00:00
|
|
|
using std::set;
|
2009-03-26 19:00:08 +00:00
|
|
|
using std::list;
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
ArticleMaker::ArticleMaker( vector< sptr< Dictionary::Class > > const & dictionaries_,
|
|
|
|
vector< Instances::Group > const & groups_ ):
|
|
|
|
dictionaries( dictionaries_ ),
|
|
|
|
groups( groups_ )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2009-02-01 00:08:08 +00:00
|
|
|
std::string ArticleMaker::makeHtmlHeader( QString const & word,
|
|
|
|
QString const & icon )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
|
|
|
string result =
|
2009-03-26 19:00:08 +00:00
|
|
|
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
|
2009-01-28 20:55:45 +00:00
|
|
|
"<html><head>"
|
|
|
|
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">";
|
|
|
|
|
2009-02-08 17:21:46 +00:00
|
|
|
// Add a css stylesheet
|
2009-05-01 12:20:33 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
QFile builtInCssFile( ":/article-style.css" );
|
|
|
|
builtInCssFile.open( QFile::ReadOnly );
|
|
|
|
QByteArray css = builtInCssFile.readAll();
|
|
|
|
|
|
|
|
QFile cssFile( Config::getUserCssFileName() );
|
2009-02-08 17:21:46 +00:00
|
|
|
|
2009-05-01 12:20:33 +00:00
|
|
|
if ( cssFile.open( QFile::ReadOnly ) )
|
|
|
|
css += cssFile.readAll();
|
|
|
|
|
|
|
|
result += "<style type=\"text/css\" media=\"all\">\n";
|
|
|
|
result += css.data();
|
|
|
|
result += "</style>\n";
|
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-05-01 12:20:33 +00:00
|
|
|
// Add print-only css
|
2009-02-08 17:21:46 +00:00
|
|
|
|
2009-05-01 12:20:33 +00:00
|
|
|
{
|
|
|
|
QFile builtInCssFile( ":/article-style-print.css" );
|
|
|
|
builtInCssFile.open( QFile::ReadOnly );
|
|
|
|
QByteArray css = builtInCssFile.readAll();
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-05-01 12:20:33 +00:00
|
|
|
QFile cssFile( Config::getUserCssPrintFileName() );
|
|
|
|
|
|
|
|
if ( cssFile.open( QFile::ReadOnly ) )
|
|
|
|
css += cssFile.readAll();
|
|
|
|
|
|
|
|
result += "<style type=\"text/css\" media=\"print\">\n";
|
|
|
|
result += css.data();
|
|
|
|
result += "</style>\n";
|
|
|
|
}
|
|
|
|
|
2009-04-18 17:20:12 +00:00
|
|
|
result += "<title>" + Html::escape( Utf8::encode( gd::toWString( word ) ) ) + "</title>";
|
2009-02-01 00:08:08 +00:00
|
|
|
|
|
|
|
// This doesn't seem to be much of influence right now, but we'll keep
|
|
|
|
// it anyway.
|
|
|
|
if ( icon.size() )
|
|
|
|
result += "<link rel=\"icon\" type=\"image/png\" href=\"qrcx://localhost/flags/" + Html::escape( icon.toUtf8().data() ) + "\" />\n";
|
|
|
|
|
|
|
|
result += "</head><body>";
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
std::string ArticleMaker::makeNotFoundBody( QString const & word, QString const & group )
|
2009-02-01 00:08:08 +00:00
|
|
|
{
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
return string( "<div class=\"gdnotfound\"><p>" ) +
|
|
|
|
tr( "No translation for <b>%1</b> was found in group <b>%2</b>." ).
|
|
|
|
arg( QString::fromUtf8( Html::escape( word.toUtf8().data() ).c_str() ) ).
|
|
|
|
arg( QString::fromUtf8( Html::escape( group.toUtf8().data() ).c_str() ) ).
|
|
|
|
toUtf8().data()
|
|
|
|
+"</p></div>";
|
|
|
|
}
|
|
|
|
|
|
|
|
sptr< Dictionary::DataRequest > ArticleMaker::makeDefinitionFor(
|
2009-04-10 12:48:40 +00:00
|
|
|
QString const & inWord, unsigned groupId ) const
|
2009-03-26 19:00:08 +00:00
|
|
|
{
|
2009-04-10 12:48:40 +00:00
|
|
|
if ( groupId == UINT_MAX )
|
2009-02-08 18:35:29 +00:00
|
|
|
{
|
|
|
|
// This is a special group containing internal welcome/help pages
|
|
|
|
string result = makeHtmlHeader( inWord, QString() );
|
|
|
|
|
2009-04-12 19:41:58 +00:00
|
|
|
if ( inWord == tr( "Welcome!" ) )
|
2009-02-08 18:35:29 +00:00
|
|
|
{
|
|
|
|
result += tr(
|
|
|
|
"<h3 align=\"center\">Welcome to <b>GoldenDict</b>!</h3>"
|
2009-03-29 12:26:54 +00:00
|
|
|
"<p>To start working with the program, first visit <b>Edit|Sources</b> to add some directory paths where to search "
|
|
|
|
"for the dictionary files, and/or set up various Wikipedia sources. "
|
|
|
|
"After that, you can optionally organize all the dictionaries found into groups "
|
2009-02-08 18:35:29 +00:00
|
|
|
"in <b>Edit|Groups</b>."
|
|
|
|
"<p>You can also check out the available program preferences at <b>Edit|Preferences</b>. "
|
|
|
|
"All settings there have tooltips, be sure to read them if you are in doubt about anything."
|
2009-02-08 21:32:33 +00:00
|
|
|
"<p>And then you're ready to look up your words! You can do that in this window "
|
2009-02-08 18:35:29 +00:00
|
|
|
"by using a pane to the left, or you can <a href=\"Working with popup\">look up words from other active applications</a>. "
|
|
|
|
"<p>Should you need further help, have any questions, "
|
|
|
|
"suggestions or just wonder what the others think, you are welcome at the program's <a href=\"http://goldendict.berlios.de/forum/\">forum</a>."
|
|
|
|
"<p>You can also contact the author directly by writing an <a href=\"mailto: Konstantin Isakov <ikm@users.berlios.de>\">e-mail</a>."
|
|
|
|
"<p>Check program's <a href=\"http://goldendict.berlios.de/\">website</a> for the updates. "
|
|
|
|
"<p>(c) 2008-2009 Konstantin Isakov. Licensed under GPLv3 or later."
|
|
|
|
|
|
|
|
).toUtf8().data();
|
|
|
|
}
|
|
|
|
else
|
2009-04-12 19:41:58 +00:00
|
|
|
if ( inWord == tr( "Working with popup" ) )
|
2009-02-08 18:35:29 +00:00
|
|
|
{
|
|
|
|
result += ( tr( "<h3 align=\"center\">Working with the popup</h3>"
|
|
|
|
|
|
|
|
"To look up words from other active applications, you would need to first activate the <i>\"Scan popup functionality\"</i> in <b>Preferences</b>, "
|
|
|
|
"and then enable it at any time either by triggering the 'Popup' icon above, or "
|
|
|
|
"by clicking the tray icon down below with your right mouse button and choosing so in the menu you've popped. " ) +
|
|
|
|
|
|
|
|
#ifdef Q_OS_WIN32
|
|
|
|
tr( "Then just stop the cursor over the word you want to look up in another application, "
|
|
|
|
"and a window would pop up which would describe it to you." )
|
|
|
|
#else
|
|
|
|
tr( "Then just select any word you want to look up in another application by your mouse "
|
|
|
|
"(double-click it or swipe it with mouse with the button pressed), "
|
|
|
|
"and a window would pop up which would describe the word to you." )
|
|
|
|
#endif
|
|
|
|
).toUtf8().data();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Not found
|
2009-04-10 12:48:40 +00:00
|
|
|
return makeNotFoundTextFor( inWord, "help" );
|
2009-02-08 18:35:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
result += "</body></html>";
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
sptr< Dictionary::DataRequestInstant > r = new Dictionary::DataRequestInstant( true );
|
|
|
|
|
|
|
|
r->getData().resize( result.size() );
|
|
|
|
memcpy( &( r->getData().front() ), result.data(), result.size() );
|
|
|
|
|
|
|
|
return r;
|
2009-02-08 18:35:29 +00:00
|
|
|
}
|
2009-03-26 19:00:08 +00:00
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
// Find the given group
|
|
|
|
|
|
|
|
Instances::Group const * activeGroup = 0;
|
|
|
|
|
|
|
|
for( unsigned x = 0; x < groups.size(); ++x )
|
2009-04-10 12:48:40 +00:00
|
|
|
if ( groups[ x ].id == groupId )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
|
|
|
activeGroup = &groups[ x ];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we've found a group, use its dictionaries; otherwise, use the global
|
|
|
|
// heap.
|
|
|
|
std::vector< sptr< Dictionary::Class > > const & activeDicts =
|
|
|
|
activeGroup ? activeGroup->dictionaries : dictionaries;
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
string header = makeHtmlHeader( inWord.trimmed(),
|
2009-02-01 00:08:08 +00:00
|
|
|
activeGroup && activeGroup->icon.size() ?
|
|
|
|
activeGroup->icon : QString() );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-04-10 12:48:40 +00:00
|
|
|
return new ArticleRequest( inWord.trimmed(), activeGroup ? activeGroup->name : "", activeDicts, header );
|
2009-03-26 19:00:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sptr< Dictionary::DataRequest > ArticleMaker::makeNotFoundTextFor(
|
|
|
|
QString const & word, QString const & group ) const
|
|
|
|
{
|
|
|
|
string result = makeHtmlHeader( word, QString() ) + makeNotFoundBody( word, group ) +
|
|
|
|
"</body></html>";
|
|
|
|
|
|
|
|
sptr< Dictionary::DataRequestInstant > r = new Dictionary::DataRequestInstant( true );
|
|
|
|
|
|
|
|
r->getData().resize( result.size() );
|
|
|
|
memcpy( &( r->getData().front() ), result.data(), result.size() );
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
//////// ArticleRequest
|
|
|
|
|
|
|
|
ArticleRequest::ArticleRequest(
|
|
|
|
QString const & word_, QString const & group_,
|
|
|
|
vector< sptr< Dictionary::Class > > const & activeDicts_,
|
|
|
|
string const & header ):
|
|
|
|
word( word_ ), group( group_ ), activeDicts( activeDicts_ ),
|
2009-04-12 16:22:42 +00:00
|
|
|
altsDone( false ), bodyDone( false ), foundAnyDefinitions( false ),
|
|
|
|
closePrevSpan( false )
|
2009-03-26 19:00:08 +00:00
|
|
|
{
|
|
|
|
// No need to lock dataMutex on construction
|
|
|
|
|
|
|
|
hasAnyData = true;
|
|
|
|
|
|
|
|
data.resize( header.size() );
|
|
|
|
memcpy( &data.front(), header.data(), header.size() );
|
2009-01-29 19:16:25 +00:00
|
|
|
|
2009-01-28 20:55:45 +00:00
|
|
|
// Accumulate main forms
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
for( unsigned x = 0; x < activeDicts.size(); ++x )
|
|
|
|
{
|
2009-04-18 17:20:12 +00:00
|
|
|
sptr< Dictionary::WordSearchRequest > s = activeDicts[ x ]->findHeadwordsForSynonym( gd::toWString( word ) );
|
2009-03-26 19:00:08 +00:00
|
|
|
|
|
|
|
connect( s.get(), SIGNAL( finished() ),
|
|
|
|
this, SLOT( altSearchFinished() ) );
|
|
|
|
|
|
|
|
altSearches.push_back( s );
|
|
|
|
}
|
|
|
|
|
|
|
|
altSearchFinished(); // Handle any ones which have already finished
|
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
void ArticleRequest::altSearchFinished()
|
|
|
|
{
|
|
|
|
if ( altsDone )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Check every request for finishing
|
|
|
|
for( list< sptr< Dictionary::WordSearchRequest > >::iterator i =
|
|
|
|
altSearches.begin(); i != altSearches.end(); )
|
|
|
|
{
|
|
|
|
if ( (*i)->isFinished() )
|
|
|
|
{
|
|
|
|
// This one's finished
|
|
|
|
for( size_t count = (*i)->matchesCount(), x = 0; x < count; ++x )
|
|
|
|
alts.insert( (**i)[ x ].word );
|
|
|
|
|
|
|
|
altSearches.erase( i++ );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( altSearches.empty() )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
printf( "alts finished\n" );
|
|
|
|
|
|
|
|
// They all've finished! Now we can look up bodies
|
|
|
|
|
|
|
|
altsDone = true; // So any pending signals in queued mode won't mess us up
|
|
|
|
|
|
|
|
vector< wstring > altsVector( alts.begin(), alts.end() );
|
|
|
|
|
|
|
|
for( unsigned x = 0; x < altsVector.size(); ++x )
|
|
|
|
{
|
|
|
|
printf( "Alt: %ls\n", altsVector[ x ].c_str() );
|
|
|
|
}
|
|
|
|
|
2009-04-18 17:20:12 +00:00
|
|
|
wstring wordStd = gd::toWString( word );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
|
|
|
for( unsigned x = 0; x < activeDicts.size(); ++x )
|
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
sptr< Dictionary::DataRequest > r =
|
|
|
|
activeDicts[ x ]->getArticle( wordStd, altsVector );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
connect( r.get(), SIGNAL( finished() ),
|
|
|
|
this, SLOT( bodyFinished() ) );
|
|
|
|
|
|
|
|
bodyRequests.push_back( r );
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
bodyFinished(); // Handle any ones which have already finished
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
2009-03-26 19:00:08 +00:00
|
|
|
}
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
void ArticleRequest::bodyFinished()
|
|
|
|
{
|
|
|
|
if ( bodyDone )
|
|
|
|
return;
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
printf( "some body finished\n" );
|
|
|
|
|
|
|
|
bool wasUpdated = false;
|
|
|
|
|
|
|
|
while ( bodyRequests.size() )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
// Since requests should go in order, check the first one first
|
|
|
|
if ( bodyRequests.front()->isFinished() )
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
// Good
|
|
|
|
|
|
|
|
printf( "one finished.\n" );
|
|
|
|
|
|
|
|
Dictionary::DataRequest & req = *bodyRequests.front();
|
|
|
|
|
|
|
|
QString errorString = req.getErrorString();
|
|
|
|
|
|
|
|
if ( req.dataSize() >= 0 || errorString.size() )
|
|
|
|
{
|
2009-04-12 16:22:42 +00:00
|
|
|
string dictId = activeDicts[ activeDicts.size() - bodyRequests.size() ]->getId();
|
|
|
|
|
|
|
|
string head;
|
|
|
|
|
|
|
|
if ( closePrevSpan )
|
|
|
|
{
|
|
|
|
head += "</span>";
|
|
|
|
closePrevSpan = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
string jsVal = Html::escapeForJavaScript( dictId );
|
|
|
|
head += "<script language=\"JavaScript\">var gdArticleContents; "
|
|
|
|
"if ( !gdArticleContents ) gdArticleContents = \"" + jsVal +" \"; "
|
|
|
|
"else gdArticleContents += \"" + jsVal + " \";</script>";
|
|
|
|
|
|
|
|
head += "<span id=\"gdfrom-" + Html::escape( dictId ) + "\">";
|
|
|
|
|
|
|
|
closePrevSpan = true;
|
|
|
|
|
|
|
|
head += string( "<div class=\"gddictname\">" ) +
|
2009-03-26 19:00:08 +00:00
|
|
|
Html::escape(
|
|
|
|
tr( "From %1" ).arg( QString::fromUtf8( activeDicts[ activeDicts.size() - bodyRequests.size() ]->getName().c_str() ) ).toUtf8().data() )
|
|
|
|
+ "</div>";
|
|
|
|
|
|
|
|
if ( errorString.size() )
|
|
|
|
{
|
|
|
|
head += "<div class=\"gderrordesc\">" +
|
|
|
|
Html::escape( tr( "Query error: %1" ).arg( errorString ).toUtf8().data() )
|
|
|
|
+ "</div>";
|
|
|
|
}
|
|
|
|
|
|
|
|
Mutex::Lock _( dataMutex );
|
|
|
|
|
|
|
|
size_t offset = data.size();
|
|
|
|
|
|
|
|
data.resize( data.size() + head.size() + ( req.dataSize() > 0 ? req.dataSize() : 0 ) );
|
|
|
|
|
|
|
|
memcpy( &data.front() + offset, head.data(), head.size() );
|
|
|
|
|
|
|
|
if ( req.dataSize() > 0 )
|
|
|
|
bodyRequests.front()->getDataSlice( 0, req.dataSize(),
|
|
|
|
&data.front() + offset + head.size() );
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
wasUpdated = true;
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
foundAnyDefinitions = true;
|
|
|
|
}
|
|
|
|
printf( "erasing..\n" );
|
|
|
|
bodyRequests.pop_front();
|
|
|
|
printf( "erase done..\n" );
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
2009-03-26 19:00:08 +00:00
|
|
|
else
|
2009-01-28 20:55:45 +00:00
|
|
|
{
|
2009-03-26 19:00:08 +00:00
|
|
|
printf( "one not finished.\n" );
|
|
|
|
break;
|
2009-01-28 20:55:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
if ( bodyRequests.empty() )
|
|
|
|
{
|
|
|
|
// No requests left, end the article
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
bodyDone = true;
|
|
|
|
|
|
|
|
{
|
|
|
|
string footer;
|
2009-01-28 20:55:45 +00:00
|
|
|
|
2009-04-12 16:22:42 +00:00
|
|
|
if ( closePrevSpan )
|
|
|
|
{
|
|
|
|
footer += "</span>";
|
|
|
|
closePrevSpan = false;
|
|
|
|
}
|
|
|
|
|
2009-03-26 19:00:08 +00:00
|
|
|
if ( !foundAnyDefinitions )
|
|
|
|
{
|
|
|
|
// No definitions were ever found, say so to the user.
|
|
|
|
footer += ArticleMaker::makeNotFoundBody( word, group );
|
|
|
|
|
2009-04-17 13:51:50 +00:00
|
|
|
// When there were no definitions, we run stemmed search.
|
|
|
|
stemmedWordFinder = new WordFinder( this );
|
|
|
|
|
|
|
|
connect( stemmedWordFinder.get(), SIGNAL( finished() ),
|
|
|
|
this, SLOT( stemmedSearchFinished() ), Qt::QueuedConnection );
|
|
|
|
|
|
|
|
stemmedWordFinder->stemmedMatch( word, activeDicts );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
footer += "</body></html>";
|
|
|
|
}
|
2009-03-26 19:00:08 +00:00
|
|
|
|
|
|
|
Mutex::Lock _( dataMutex );
|
|
|
|
|
|
|
|
size_t offset = data.size();
|
|
|
|
|
|
|
|
data.resize( data.size() + footer.size() );
|
|
|
|
|
|
|
|
memcpy( &data.front() + offset, footer.data(), footer.size() );
|
|
|
|
}
|
|
|
|
|
2009-04-17 13:51:50 +00:00
|
|
|
if ( stemmedWordFinder.get() )
|
|
|
|
update();
|
|
|
|
else
|
|
|
|
finish();
|
2009-03-26 19:00:08 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
if ( wasUpdated )
|
|
|
|
update();
|
2009-02-01 00:08:08 +00:00
|
|
|
}
|
2009-03-26 19:00:08 +00:00
|
|
|
|
2009-04-17 13:51:50 +00:00
|
|
|
void ArticleRequest::stemmedSearchFinished()
|
|
|
|
{
|
|
|
|
// Got stemmed matching results
|
|
|
|
|
|
|
|
WordFinder::SearchResults sr = stemmedWordFinder->getResults();
|
|
|
|
|
|
|
|
string footer;
|
|
|
|
|
|
|
|
if ( sr.size() )
|
|
|
|
{
|
|
|
|
footer += "<div class=\"gdstemmedsuggestion\"><span class=\"gdstemmedsuggestion_head\">" +
|
|
|
|
Html::escape( tr( "Close words: " ).toUtf8().data() ) +
|
|
|
|
"</span><span class=\"gdstemmedsuggestion_body\">";
|
|
|
|
|
|
|
|
for( unsigned x = 0; x < sr.size(); ++x )
|
|
|
|
{
|
|
|
|
string escapedResult = Html::escape( sr[ x ].first.toUtf8().data() );
|
|
|
|
footer += "<a href=\"bword://" + escapedResult + "\">" + escapedResult +"</a>";
|
|
|
|
|
|
|
|
if ( x != sr.size() - 1 )
|
|
|
|
{
|
|
|
|
footer += ", ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
footer += "</span></div>";
|
|
|
|
}
|
|
|
|
|
|
|
|
footer += "</body></html>";
|
|
|
|
|
|
|
|
{
|
|
|
|
Mutex::Lock _( dataMutex );
|
|
|
|
|
|
|
|
size_t offset = data.size();
|
|
|
|
|
|
|
|
data.resize( data.size() + footer.size() );
|
|
|
|
|
|
|
|
memcpy( &data.front() + offset, footer.data(), footer.size() );
|
|
|
|
}
|
|
|
|
|
|
|
|
finish();
|
|
|
|
}
|
|
|
|
|