2012-02-20 21:47:14 +00:00
/* This file is (c) 2008-2012 Konstantin Isakov <ikm@goldendict.org>
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>
2013-05-31 04:20:25 +00:00
# include <QUrl>
2013-06-02 11:20:33 +00:00
# include <QTextDocumentFragment>
2010-03-30 13:41:14 +00:00
# include "folding.hh"
2010-04-03 09:43:39 +00:00
# include "langcoder.hh"
2013-11-16 18:34:09 +00:00
# include "gddebug.hh"
2021-11-27 07:17:33 +00:00
# include "utils.hh"
2022-01-08 06:51:24 +00:00
# include "globalbroadcaster.h"
2009-01-28 20:55:45 +00:00
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_ ,
2009-05-11 11:03:36 +00:00
vector < Instances : : Group > const & groups_ ,
2012-12-10 14:14:13 +00:00
QString const & displayStyle_ ,
QString const & addonStyle_ ) :
2009-01-28 20:55:45 +00:00
dictionaries ( dictionaries_ ) ,
2009-05-11 11:03:36 +00:00
groups ( groups_ ) ,
2012-09-16 10:19:47 +00:00
displayStyle ( displayStyle_ ) ,
2012-12-10 14:14:13 +00:00
addonStyle ( addonStyle_ ) ,
2012-09-16 10:19:47 +00:00
needExpandOptionalParts ( true )
2013-06-02 11:20:33 +00:00
, collapseBigArticles ( true )
, articleLimitSize ( 500 )
2009-01-28 20:55:45 +00:00
{
}
2012-12-10 14:14:13 +00:00
void ArticleMaker : : setDisplayStyle ( QString const & st , QString const & adst )
2009-05-11 11:03:36 +00:00
{
displayStyle = st ;
2012-12-10 14:14:13 +00:00
addonStyle = adst ;
2009-05-11 11:03:36 +00:00
}
2009-02-01 00:08:08 +00:00
std : : string ArticleMaker : : makeHtmlHeader ( QString const & word ,
2014-04-22 13:47:02 +00:00
QString const & icon ,
bool expandOptionalParts ) const
2009-01-28 20:55:45 +00:00
{
string result =
2022-04-10 12:40:09 +00:00
" <!DOCTYPE html> "
2021-12-19 10:37:27 +00:00
" <html><head> "
" <meta http-equiv= \" Content-Type \" content= \" text/html; charset=utf-8 \" > " ;
2009-01-28 20:55:45 +00:00
2021-12-19 10:37:27 +00:00
// add jquery
{
result + = " <script type= \" text/javascript \" "
2022-09-04 02:01:26 +00:00
" src= \" qrc:///scripts/jquery-3.6.0.slim.min.js \" ></script> " ;
2021-12-31 15:41:45 +00:00
2022-02-07 23:56:48 +00:00
result + = " <script> var $_$=$.noConflict(); </script> " ;
2022-01-12 13:48:03 +00:00
2021-12-31 15:41:45 +00:00
//custom javascript
2022-12-24 22:01:50 +00:00
result + = R " (<script type= " text / javascript " src= " qrc : ///scripts/gd-custom.js"></script>)";
2022-09-04 01:55:39 +00:00
//iframe resizer javascript
2022-12-24 22:01:50 +00:00
result + = R " (<script type= " text / javascript " src= " qrc : ///scripts/iframeResizer.min.js"></script>)";
2021-12-19 10:37:27 +00:00
}
// add qwebchannel
{
2022-12-24 22:01:50 +00:00
result + = R " (<script type= " text / javascript " src= " qrc : ///qtwebchannel/qwebchannel.js"></script>)";
2021-12-19 10:37:27 +00:00
}
2009-05-01 12:20:33 +00:00
2021-12-19 10:37:27 +00:00
// document ready ,init webchannel
{
result + = " <script> "
2022-01-11 12:18:46 +00:00
" $_$(document).ready( function ($){ "
" console.log( \" webchannel ready... \" ); "
2022-01-12 13:48:03 +00:00
" new QWebChannel(qt.webChannelTransport, function(channel) { "
2021-12-19 10:37:27 +00:00
" window.articleview = channel.objects.articleview; "
" }); "
" }); "
" </script> " ;
}
// Add a css stylesheet
2009-05-01 12:20:33 +00:00
{
2022-12-24 22:01:50 +00:00
result + = R " (<link href= " qrc : ///article-style.css" media="all" rel="stylesheet" type="text/css">)";
2016-08-02 14:56:35 +00:00
2009-05-11 11:03:36 +00:00
if ( displayStyle . size ( ) )
{
// Load an additional stylesheet
2022-01-12 13:48:03 +00:00
QString displayStyleCssFile = QString ( " qrc:///article-style-st-%1.css " ) . arg ( displayStyle ) ;
result + = " <link href= \" " + displayStyleCssFile . toStdString ( ) +
2022-12-24 22:01:50 +00:00
R " ( " media = " all " rel = " stylesheet " type = " text/css " > ) " ;
2009-05-11 11:03:36 +00:00
}
2022-01-12 13:48:03 +00:00
result + = readCssFile ( Config : : getUserCssFileName ( ) , " all " ) ;
2012-12-10 14:14:13 +00:00
if ( ! addonStyle . isEmpty ( ) )
{
QString name = Config : : getStylesDir ( ) + addonStyle
+ QDir : : separator ( ) + " article-style.css " ;
2022-01-12 13:48:03 +00:00
result + = readCssFile ( name , " all " ) ;
2012-12-10 14:14:13 +00:00
}
2012-09-16 10:19:47 +00:00
// Turn on/off expanding of article optional parts
2014-04-22 13:47:02 +00:00
if ( expandOptionalParts )
2016-08-02 14:56:35 +00:00
{
result + = " <!-- Expand optional parts css --> \n " ;
result + = " <style type= \" text/css \" media= \" all \" > \n " ;
2012-09-16 10:19:47 +00:00
result + = " \n .dsl_opt \n { \n display: inline; \n } \n \n .hidden_expand_opt \n { \n display: none; \n } \n " ;
2016-08-02 14:56:35 +00:00
result + = " </style> \n " ;
}
2012-09-16 10:19:47 +00:00
2009-05-01 12:20:33 +00:00
}
2009-01-28 20:55:45 +00:00
2009-05-01 12:20:33 +00:00
// Add print-only css
{
2022-12-24 22:01:50 +00:00
result + = R " (<link href= " qrc : ///article-style-print.css" media="print" rel="stylesheet" type="text/css">)";
2009-01-28 20:55:45 +00:00
2022-01-12 13:48:03 +00:00
result + = readCssFile ( Config : : getUserCssPrintFileName ( ) , " print " ) ;
2012-12-10 14:14:13 +00:00
if ( ! addonStyle . isEmpty ( ) )
{
QString name = Config : : getStylesDir ( ) + addonStyle
+ QDir : : separator ( ) + " article-style-print.css " ;
2022-01-12 13:48:03 +00:00
result + = readCssFile ( name , " print " ) ;
2012-12-10 14:14:13 +00:00
}
2009-05-01 12:20:33 +00:00
}
2012-12-10 14:14:13 +00:00
2022-03-31 09:51:22 +00:00
result + = " <title> " + Html : : escape ( word . toStdString ( ) ) + " </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 ( ) )
2022-12-24 22:01:50 +00:00
result + = R " (<link rel= " icon " type= " image / png " href= " qrcx : //localhost/flags/)" + Html::escape( icon.toUtf8().data() ) + "\" />\n";
2009-02-01 00:08:08 +00:00
2013-05-29 07:03:37 +00:00
result + = " <script type= \" text/javascript \" > "
2022-01-12 14:30:43 +00:00
" function tr(key) { "
" var tr_map = { "
" \" Expand article \" : \" " ;
result + = tr ( " Expand article " ) . toUtf8 ( ) . data ( ) ;
2022-12-24 22:01:50 +00:00
result + = R " ( " , " Collapse article " : " ) " ;
2022-01-12 14:30:43 +00:00
result + = tr ( " Collapse article " ) . toUtf8 ( ) . data ( ) ;
result + = " \" }; "
" return tr_map[key] || ''; "
" } "
2009-05-11 22:25:22 +00:00
" </script> " ;
2022-12-24 22:01:50 +00:00
result + = R " (<script type= " text / javascript " src= " qrc : ///scripts/gd-builtin.js"></script>)";
2022-12-24 00:56:52 +00:00
2022-12-26 17:26:29 +00:00
if ( GlobalBroadcaster : : instance ( ) - > getPreference ( ) - > darkReaderMode )
2022-12-24 00:56:52 +00:00
{
2023-02-28 18:22:59 +00:00
// #242525 because Darkreader will invert pure white to this value
2022-12-26 17:26:29 +00:00
result + = R " (
< script src = " qrc:///scripts/darkreader.js " > < / script >
2023-02-28 18:22:59 +00:00
< style >
body { background : # 242525 ; }
. gdarticle { background : initial ; }
< / style >
2022-12-26 17:26:29 +00:00
< script >
2023-03-01 21:19:54 +00:00
// This function returns a promise, but it is synchroneous because it does not use await
function fetchShim ( src ) {
if ( src . startsWith ( ' gdlookup : //')) {
// See https://github.com/xiaoyifang/goldendict/issues/363
console . error ( ' Dark Reader discovered unexpected URL ' , src ) ;
return Promise . resolve ( { blob : ( ) = > new Blob ( ) } ) ;
}
if ( src . startsWith ( ' qrcx : //') || src.startsWith('qrc://')) {
// This is a resource URL, need to fetch and transform
return new Promise ( ( resolve ) = > {
const img = document . createElement ( ' img ' ) ;
img . addEventListener ( ' load ' , ( ) = > {
// Set willReadFrequently to true to tell engine to store data in RAM-backed buffer and not on GPU
const canvas = document . createElement ( ' canvas ' , { willReadFrequently : true } ) ;
canvas . width = img . naturalWidth ;
canvas . height = img . naturalHeight ;
const ctx = canvas . getContext ( ' 2 d ' ) ;
ctx . drawImage ( img , 0 , 0 ) ;
canvas . toBlob ( ( blob ) = > {
resolve ( { blob : ( ) = > blob } ) ;
} ) ;
} , false ) ;
img . src = src ;
} ) ;
}
// This is a standard URL, can fetch it directly
return fetch ( src ) ;
}
DarkReader . setFetchMethod ( fetchShim ) ;
2022-12-26 17:26:29 +00:00
DarkReader . enable ( {
brightness : 100 ,
contrast : 90 ,
sepia : 10
} ) ;
< / script >
) " ;
2022-12-24 00:56:52 +00:00
}
2009-02-01 00:08:08 +00:00
result + = " </head><body> " ;
return result ;
}
2022-01-12 13:48:03 +00:00
std : : string ArticleMaker : : readCssFile ( QString const & fileName , std : : string media ) const {
QFile addonCss ( fileName ) ;
std : : string result ;
if ( addonCss . open ( QFile : : ReadOnly ) ) {
QByteArray css = addonCss . readAll ( ) ;
if ( ! css . isEmpty ( ) ) {
result + = " <!-- Addon style css --> \n " ;
2022-12-24 22:01:50 +00:00
result + = R " (<style type= " text / css " media= " ) " + media + " \ " > \n " ;
2022-01-12 13:48:03 +00:00
result + = css . data ( ) ;
result + = " </style> \n " ;
}
}
return result ;
}
2009-05-11 11:03:36 +00:00
std : : string ArticleMaker : : makeNotFoundBody ( QString const & word ,
QString const & group )
2009-02-01 00:08:08 +00:00
{
2010-03-30 13:41:14 +00:00
string result ( " <div class= \" gdnotfound \" ><p> " ) ;
2009-02-01 00:08:08 +00:00
2013-07-16 13:59:56 +00:00
QString str ( word ) ;
if ( str . isRightToLeft ( ) )
{
str . insert ( 0 , ( ushort ) 0x202E ) ; // RLE, Right-to-Left Embedding
str . append ( ( ushort ) 0x202C ) ; // PDF, POP DIRECTIONAL FORMATTING
}
2010-03-30 13:41:14 +00:00
if ( word . size ( ) )
result + = tr ( " No translation for <b>%1</b> was found in group <b>%2</b>. " ) .
2013-07-16 13:59:56 +00:00
arg ( QString : : fromUtf8 ( Html : : escape ( str . toUtf8 ( ) . data ( ) ) . c_str ( ) ) ) .
2010-03-30 13:41:14 +00:00
arg ( QString : : fromUtf8 ( Html : : escape ( group . toUtf8 ( ) . data ( ) ) . c_str ( ) ) ) .
toUtf8 ( ) . data ( ) ;
else
result + = tr ( " No translation was found in group <b>%1</b>. " ) .
arg ( QString : : fromUtf8 ( Html : : escape ( group . toUtf8 ( ) . data ( ) ) . c_str ( ) ) ) .
toUtf8 ( ) . data ( ) ;
result + = " </p></div> " ;
return result ;
2009-03-26 19:00:08 +00:00
}
2021-06-10 16:13:11 +00:00
sptr < Dictionary : : DataRequest > ArticleMaker : : makeDefinitionFor (
Config : : InputPhrase const & phrase , unsigned groupId ,
2009-09-23 18:44:38 +00:00
QMap < QString , QString > const & contexts ,
2014-04-16 16:18:28 +00:00
QSet < QString > const & mutedDicts ,
2018-06-13 16:00:42 +00:00
QStringList const & dictIDs , bool ignoreDiacritics ) const
2009-03-26 19:00:08 +00:00
{
2014-04-16 16:18:28 +00:00
if ( ! dictIDs . isEmpty ( ) )
{
QStringList ids = dictIDs ;
std : : vector < sptr < Dictionary : : Class > > ftsDicts ;
// Find dictionaries by ID's
for ( unsigned x = 0 ; x < dictionaries . size ( ) ; x + + )
{
for ( QStringList : : Iterator it = ids . begin ( ) ; it ! = ids . end ( ) ; + + it )
{
if ( * it = = QString : : fromStdString ( dictionaries [ x ] - > getId ( ) ) )
{
ftsDicts . push_back ( dictionaries [ x ] ) ;
ids . erase ( it ) ;
break ;
}
}
2014-04-22 13:47:02 +00:00
if ( ids . isEmpty ( ) )
break ;
2014-04-16 16:18:28 +00:00
}
2021-06-10 16:13:11 +00:00
string header = makeHtmlHeader ( phrase . phrase , QString ( ) , true ) ;
2014-04-16 16:18:28 +00:00
2022-11-29 03:54:31 +00:00
return std : : make_shared < ArticleRequest > ( phrase , " " ,
2014-04-16 16:18:28 +00:00
contexts , ftsDicts , header ,
2014-04-22 13:47:02 +00:00
- 1 , true ) ;
2014-04-16 16:18:28 +00:00
}
2010-05-08 14:01:59 +00:00
if ( groupId = = Instances : : Group : : HelpGroupId )
2009-02-08 18:35:29 +00:00
{
// This is a special group containing internal welcome/help pages
2021-06-10 16:13:11 +00:00
string result = makeHtmlHeader ( phrase . phrase , QString ( ) , needExpandOptionalParts ) ;
2013-05-31 03:28:05 +00:00
2021-06-10 16:13:11 +00:00
if ( phrase . phrase = = tr ( " Welcome! " ) )
2009-02-08 18:35:29 +00:00
{
result + = tr (
" <h3 align= \" center \" >Welcome to <b>GoldenDict</b>!</h3> "
2009-05-24 17:38:38 +00:00
" <p>To start working with the program, first visit <b>Edit|Dictionaries</b> to add some directory paths where to search "
" for the dictionary files, set up various Wikipedia sites or other sources, adjust dictionary order or create dictionary groups. "
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>. "
2009-05-24 17:38:38 +00:00
" <p>To customize program, check out the available 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 18:35:29 +00:00
" <p>Should you need further help, have any questions, "
2023-01-06 01:34:53 +00:00
" suggestions or just wonder what the others think, you are welcome at the program's <a href= \" https://github.com/xiaoyifang/goldendict/discussions \" >forum</a>. "
" <p>Check program's <a href= \" https://github.com/xiaoyifang/goldendict \" >website</a> for the updates. "
2013-01-07 09:30:31 +00:00
" <p>(c) 2008-2013 Konstantin Isakov. Licensed under GPLv3 or later. "
2013-05-31 03:28:05 +00:00
2009-02-08 18:35:29 +00:00
) . toUtf8 ( ) . data ( ) ;
}
else
2021-06-10 16:13:11 +00:00
if ( phrase . phrase = = 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
2021-06-10 16:13:11 +00:00
return makeNotFoundTextFor ( phrase . phrase , " help " ) ;
2009-02-08 18:35:29 +00:00
}
2013-05-31 03:28:05 +00:00
2009-02-08 18:35:29 +00:00
result + = " </body></html> " ;
2022-11-29 03:54:31 +00:00
sptr < Dictionary : : DataRequestInstant > r = std : : make_shared < Dictionary : : DataRequestInstant > ( true ) ;
2009-03-26 19:00:08 +00:00
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 ;
2021-06-10 16:13:11 +00:00
string header = makeHtmlHeader ( phrase . phrase ,
2009-02-01 00:08:08 +00:00
activeGroup & & activeGroup - > icon . size ( ) ?
2014-04-22 13:47:02 +00:00
activeGroup - > icon : QString ( ) ,
needExpandOptionalParts ) ;
2009-01-28 20:55:45 +00:00
2009-09-23 18:44:38 +00:00
if ( mutedDicts . size ( ) )
{
std : : vector < sptr < Dictionary : : Class > > unmutedDicts ;
unmutedDicts . reserve ( activeDicts . size ( ) ) ;
for ( unsigned x = 0 ; x < activeDicts . size ( ) ; + + x )
if ( ! mutedDicts . contains (
QString : : fromStdString ( activeDicts [ x ] - > getId ( ) ) ) )
unmutedDicts . push_back ( activeDicts [ x ] ) ;
2022-11-29 03:54:31 +00:00
return std : : make_shared < ArticleRequest > ( phrase , activeGroup ? activeGroup - > name : " " ,
2013-06-02 11:20:33 +00:00
contexts , unmutedDicts , header ,
2013-06-03 13:38:57 +00:00
collapseBigArticles ? articleLimitSize : - 1 ,
2018-06-13 16:00:42 +00:00
needExpandOptionalParts , ignoreDiacritics ) ;
2009-09-23 18:44:38 +00:00
}
else
2022-11-29 03:54:31 +00:00
return std : : make_shared < ArticleRequest > ( phrase , activeGroup ? activeGroup - > name : " " ,
2013-06-02 11:20:33 +00:00
contexts , activeDicts , header ,
2013-06-03 13:38:57 +00:00
collapseBigArticles ? articleLimitSize : - 1 ,
2018-06-13 16:00:42 +00:00
needExpandOptionalParts , ignoreDiacritics ) ;
2009-03-26 19:00:08 +00:00
}
sptr < Dictionary : : DataRequest > ArticleMaker : : makeNotFoundTextFor (
QString const & word , QString const & group ) const
{
2014-04-22 13:47:02 +00:00
string result = makeHtmlHeader ( word , QString ( ) , true ) + makeNotFoundBody ( word , group ) +
2009-03-26 19:00:08 +00:00
" </body></html> " ;
2022-11-29 03:54:31 +00:00
sptr < Dictionary : : DataRequestInstant > r = std : : make_shared < Dictionary : : DataRequestInstant > ( true ) ;
2009-03-26 19:00:08 +00:00
r - > getData ( ) . resize ( result . size ( ) ) ;
memcpy ( & ( r - > getData ( ) . front ( ) ) , result . data ( ) , result . size ( ) ) ;
return r ;
}
2009-05-11 15:33:57 +00:00
sptr < Dictionary : : DataRequest > ArticleMaker : : makeEmptyPage ( ) const
{
2014-04-22 13:47:02 +00:00
string result = makeHtmlHeader ( tr ( " (untitled) " ) , QString ( ) , true ) +
2009-05-11 15:33:57 +00:00
" </body></html> " ;
sptr < Dictionary : : DataRequestInstant > r =
2022-11-29 03:54:31 +00:00
std : : make_shared < Dictionary : : DataRequestInstant > ( true ) ;
2009-05-11 15:33:57 +00:00
r - > getData ( ) . resize ( result . size ( ) ) ;
memcpy ( & ( r - > getData ( ) . front ( ) ) , result . data ( ) , result . size ( ) ) ;
return r ;
}
2012-12-07 11:59:29 +00:00
sptr < Dictionary : : DataRequest > ArticleMaker : : makePicturePage ( string const & url ) const
{
2014-04-22 13:47:02 +00:00
string result = makeHtmlHeader ( tr ( " (picture) " ) , QString ( ) , true )
2012-12-08 09:09:21 +00:00
+ " <a href= \" javascript: if(history.length>2) history.go(-1) \" > "
+ " <img src= \" " + url + " \" /></a> "
2012-12-07 11:59:29 +00:00
+ " </body></html> " ;
sptr < Dictionary : : DataRequestInstant > r =
2022-11-29 03:54:31 +00:00
std : : make_shared < Dictionary : : DataRequestInstant > ( true ) ;
2012-12-07 11:59:29 +00:00
r - > getData ( ) . resize ( result . size ( ) ) ;
memcpy ( & ( r - > getData ( ) . front ( ) ) , result . data ( ) , result . size ( ) ) ;
return r ;
}
2012-09-16 10:19:47 +00:00
void ArticleMaker : : setExpandOptionalParts ( bool expand )
{
needExpandOptionalParts = expand ;
}
2013-06-02 11:20:33 +00:00
void ArticleMaker : : setCollapseParameters ( bool autoCollapse , int articleSize )
{
collapseBigArticles = autoCollapse ;
articleLimitSize = articleSize ;
}
2012-12-13 20:21:33 +00:00
bool ArticleMaker : : adjustFilePath ( QString & fileName )
{
QFileInfo info ( fileName ) ;
if ( ! info . isFile ( ) )
{
QString dir = Config : : getConfigDir ( ) ;
dir . chop ( 1 ) ;
info . setFile ( dir + fileName ) ;
if ( info . isFile ( ) )
{
fileName = info . canonicalFilePath ( ) ;
return true ;
}
}
return false ;
}
2009-03-26 19:00:08 +00:00
//////// ArticleRequest
ArticleRequest : : ArticleRequest (
2021-06-10 16:13:11 +00:00
Config : : InputPhrase const & phrase , QString const & group_ ,
2009-05-29 19:48:50 +00:00
QMap < QString , QString > const & contexts_ ,
2009-03-26 19:00:08 +00:00
vector < sptr < Dictionary : : Class > > const & activeDicts_ ,
2013-06-02 11:20:33 +00:00
string const & header ,
2018-06-13 16:00:42 +00:00
int sizeLimit , bool needExpandOptionalParts_ , bool ignoreDiacritics_ ) :
2021-06-10 16:13:11 +00:00
word ( phrase . phrase ) , group ( group_ ) , contexts ( contexts_ ) ,
2009-05-29 19:48:50 +00:00
activeDicts ( activeDicts_ ) ,
2009-04-12 16:22:42 +00:00
altsDone ( false ) , bodyDone ( false ) , foundAnyDefinitions ( false ) ,
closePrevSpan ( false )
2013-06-02 11:20:33 +00:00
, articleSizeLimit ( sizeLimit )
2013-06-03 13:38:57 +00:00
, needExpandOptionalParts ( needExpandOptionalParts_ )
2018-06-13 16:00:42 +00:00
, ignoreDiacritics ( ignoreDiacritics_ )
2009-03-26 19:00:08 +00:00
{
2021-06-10 16:13:11 +00:00
if ( ! phrase . punctuationSuffix . isEmpty ( ) )
alts . insert ( gd : : toWString ( phrase . phraseWithSuffix ( ) ) ) ;
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
2022-11-04 13:23:42 +00:00
//clear founded dicts.
2022-10-22 11:12:55 +00:00
emit GlobalBroadcaster : : instance ( ) - > dictionaryClear ( ActiveDictIds { word } ) ;
2009-01-28 20:55:45 +00:00
2022-11-04 13:23:42 +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
2022-12-26 02:08:17 +00:00
connect ( s . get ( ) , & Dictionary : : Request : : finished , this , & ArticleRequest : : altSearchFinished , Qt : : QueuedConnection ) ;
2009-03-26 19:00:08 +00:00
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 ;
2013-05-31 03:28:05 +00:00
2009-03-26 19:00:08 +00:00
// 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
{
2013-09-20 14:25:44 +00:00
# ifdef QT_DEBUG
2022-05-29 02:05:44 +00:00
qDebug ( " alts finished " ) ;
2013-09-20 14:25:44 +00:00
# endif
2013-05-31 03:28:05 +00:00
2009-03-26 19:00:08 +00:00
// 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 ( ) ) ;
2013-05-31 03:28:05 +00:00
2013-09-20 14:25:44 +00:00
# ifdef QT_DEBUG
2009-03-26 19:00:08 +00:00
for ( unsigned x = 0 ; x < altsVector . size ( ) ; + + x )
{
2013-08-04 19:19:57 +00:00
qDebug ( ) < < " Alt: " < < gd : : toQString ( altsVector [ x ] ) ;
2009-03-26 19:00:08 +00:00
}
2013-09-20 14:25:44 +00:00
# endif
2009-03-26 19:00:08 +00:00
2009-04-18 17:20:12 +00:00
wstring wordStd = gd : : toWString ( word ) ;
2009-01-28 20:55:45 +00:00
2013-06-03 17:14:05 +00:00
if ( activeDicts . size ( ) < = 1 )
articleSizeLimit = - 1 ; // Don't collapse article if only one dictionary presented
2009-01-28 20:55:45 +00:00
for ( unsigned x = 0 ; x < activeDicts . size ( ) ; + + x )
{
2013-09-19 19:43:16 +00:00
try
{
sptr < Dictionary : : DataRequest > r =
activeDicts [ x ] - > getArticle ( wordStd , altsVector ,
2018-06-13 16:00:42 +00:00
gd : : toWString ( contexts . value ( QString : : fromStdString ( activeDicts [ x ] - > getId ( ) ) ) ) ,
ignoreDiacritics ) ;
2009-01-28 20:55:45 +00:00
2022-12-26 02:08:17 +00:00
connect ( r . get ( ) , & Dictionary : : Request : : finished , this , & ArticleRequest : : bodyFinished , Qt : : QueuedConnection ) ;
2009-03-26 19:00:08 +00:00
2013-09-19 19:43:16 +00:00
bodyRequests . push_back ( r ) ;
}
catch ( std : : exception & e )
{
2013-11-16 18:34:09 +00:00
gdWarning ( " getArticle request error (%s) in \" %s \" \n " ,
e . what ( ) , activeDicts [ x ] - > getName ( ) . c_str ( ) ) ;
2013-09-19 19:43:16 +00:00
}
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
2013-06-03 13:38:57 +00:00
int ArticleRequest : : findEndOfCloseDiv ( const QString & str , int pos )
{
for ( ; ; )
{
int n1 = str . indexOf ( " </div> " , pos ) ;
if ( n1 < = 0 )
return n1 ;
int n2 = str . indexOf ( " <div " , pos ) ;
if ( n2 < = 0 | | n2 > n1 )
return n1 + 6 ;
pos = findEndOfCloseDiv ( str , n2 + 1 ) ;
if ( pos < = 0 )
return pos ;
}
}
2009-03-26 19:00:08 +00:00
void ArticleRequest : : bodyFinished ( )
{
if ( bodyDone )
return ;
2009-01-28 20:55:45 +00:00
2022-05-29 02:05:44 +00:00
GD_DPRINTF ( " some body finished " ) ;
2013-05-31 03:28:05 +00:00
2009-03-26 19:00:08 +00:00
bool wasUpdated = false ;
2013-05-31 03:28:05 +00:00
2022-01-08 06:51:24 +00:00
QStringList dictIds ;
2009-03-26 19:00:08 +00:00
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
2022-05-29 02:05:44 +00:00
GD_DPRINTF ( " one finished. " ) ;
2009-03-26 19:00:08 +00:00
Dictionary : : DataRequest & req = * bodyRequests . front ( ) ;
QString errorString = req . getErrorString ( ) ;
if ( req . dataSize ( ) > = 0 | | errorString . size ( ) )
{
2010-04-03 09:43:39 +00:00
sptr < Dictionary : : Class > const & activeDict =
activeDicts [ activeDicts . size ( ) - bodyRequests . size ( ) ] ;
string dictId = activeDict - > getId ( ) ;
2022-01-08 06:51:24 +00:00
dictIds < < QString : : fromStdString ( dictId ) ;
2009-04-12 16:22:42 +00:00
string head ;
2009-05-11 19:14:28 +00:00
string gdFrom = " gdfrom- " + Html : : escape ( dictId ) ;
2009-04-12 16:22:42 +00:00
if ( closePrevSpan )
{
2022-12-24 22:01:50 +00:00
head + = R " (</div></div><div style= " clear : both ; " ></div><span class= " gdarticleseparator " ></span>) " ;
2009-04-12 16:22:42 +00:00
}
2013-06-02 11:20:33 +00:00
bool collapse = false ;
if ( articleSizeLimit > = 0 )
{
try
{
Mutex : : Lock _ ( dataMutex ) ;
QString text = QString : : fromUtf8 ( req . getFullData ( ) . data ( ) , req . getFullData ( ) . size ( ) ) ;
2013-06-03 13:38:57 +00:00
if ( ! needExpandOptionalParts )
{
// Strip DSL optional parts
int pos = 0 ;
for ( ; ; )
{
pos = text . indexOf ( " <div class= \" dsl_opt \" " ) ;
if ( pos > 0 )
{
int endPos = findEndOfCloseDiv ( text , pos + 1 ) ;
if ( endPos > pos )
text . remove ( pos , endPos - pos ) ;
else
break ;
}
else
break ;
}
}
2022-04-20 12:53:57 +00:00
int size = htmlTextSize ( text ) ;
2013-06-02 11:20:33 +00:00
if ( size > articleSizeLimit )
collapse = true ;
}
catch ( . . . )
{
}
}
2009-04-12 16:22:42 +00:00
string jsVal = Html : : escapeForJavaScript ( dictId ) ;
2009-05-11 22:25:22 +00:00
2017-06-18 10:39:01 +00:00
head + = string ( " <div class= \" gdarticle " ) +
2009-05-11 22:25:22 +00:00
( closePrevSpan ? " " : " gdactivearticle " ) +
2013-06-02 11:20:33 +00:00
( collapse ? " gdcollapsedarticle " : " " ) +
2009-05-11 22:25:22 +00:00
" \" id= \" " + gdFrom +
2009-05-14 20:38:17 +00:00
" \" onClick= \" gdMakeArticleActive( ' " + jsVal + " ' ); \" " +
" onContextMenu= \" gdMakeArticleActive( ' " + jsVal + " ' ); \" "
2009-05-11 19:14:28 +00:00
+ " > " ;
2009-04-12 16:22:42 +00:00
closePrevSpan = true ;
2010-04-03 09:43:39 +00:00
2022-12-24 22:01:50 +00:00
head + = string ( R " (<div class= " gddictname " onclick= " gdExpandArticle ( ' ) " ) + dictId + " \ ' ) ; "
2013-06-02 11:20:33 +00:00
+ ( collapse ? " \" style= \" cursor:pointer; " : " " )
+ " \" id= \" gddictname- " + Html : : escape ( dictId ) + " \" "
+ ( collapse ? string ( " title= \" " ) + tr ( " Expand article " ) . toUtf8 ( ) . data ( ) + " \" " : " " )
2022-12-24 22:01:50 +00:00
+ R " (><span class= " gddicticon " ><img src= " gico : //)" + Html::escape( dictId )
+ R " (/dicticon.png " > < / span > < span class = " gdfromprefix " >) " +
2013-06-02 12:28:38 +00:00
Html : : escape ( tr ( " From " ) . toUtf8 ( ) . data ( ) ) + " </span><span class= \" gddicttitle \" > " +
Html : : escape ( activeDict - > getName ( ) . c_str ( ) ) + " </span> "
2022-12-24 22:01:50 +00:00
+ R " (<span class= " collapse_expand_area " ><img src= " qrcx : //localhost/icons/blank.png" class=")"
2013-06-02 11:20:33 +00:00
+ ( collapse ? " gdexpandicon " : " gdcollapseicon " )
+ " \" id= \" expandicon- " + Html : : escape ( dictId ) + " \" "
+ ( collapse ? " " : string ( " title= \" " ) + tr ( " Collapse article " ) . toUtf8 ( ) . data ( ) + " \" " )
+ " ></span> " + " </div> " ;
2009-03-26 19:00:08 +00:00
2013-06-02 16:27:19 +00:00
head + = " <div class= \" gddictnamebodyseparator \" ></div> " ;
2009-03-26 19:00:08 +00:00
2017-06-09 12:38:23 +00:00
head + = " <div class= \" gdarticlebody gdlangfrom- " ;
2013-02-03 20:19:55 +00:00
head + = LangCoder : : intToCode2 ( activeDict - > getLangFrom ( ) ) . toLatin1 ( ) . data ( ) ;
2010-04-03 09:43:39 +00:00
head + = " \" lang= \" " ;
2013-02-03 20:19:55 +00:00
head + = LangCoder : : intToCode2 ( activeDict - > getLangTo ( ) ) . toLatin1 ( ) . data ( ) ;
2013-06-02 11:20:33 +00:00
head + = " \" " ;
head + = " style= \" display: " ;
head + = collapse ? " none " : " inline " ;
head + = string ( " \" id= \" gdarticlefrom- " ) + Html : : escape ( dictId ) + " \" > " ;
2010-04-03 09:43:39 +00:00
2009-03-26 19:00:08 +00:00
if ( errorString . size ( ) )
{
head + = " <div class= \" gderrordesc \" > " +
Html : : escape ( tr ( " Query error: %1 " ) . arg ( errorString ) . toUtf8 ( ) . data ( ) )
+ " </div> " ;
}
Mutex : : Lock _ ( dataMutex ) ;
2013-05-31 03:28:05 +00:00
2009-03-26 19:00:08 +00:00
size_t offset = data . size ( ) ;
2013-05-31 03:28:05 +00:00
2009-03-26 19:00:08 +00:00
data . resize ( data . size ( ) + head . size ( ) + ( req . dataSize ( ) > 0 ? req . dataSize ( ) : 0 ) ) ;
2013-05-31 03:28:05 +00:00
2009-03-26 19:00:08 +00:00
memcpy ( & data . front ( ) + offset , head . data ( ) , head . size ( ) ) ;
2013-09-19 19:43:16 +00:00
try
{
if ( req . dataSize ( ) > 0 )
bodyRequests . front ( ) - > getDataSlice ( 0 , req . dataSize ( ) ,
& data . front ( ) + offset + head . size ( ) ) ;
}
catch ( std : : exception & e )
{
2013-11-16 18:34:09 +00:00
gdWarning ( " getDataSlice error: %s \n " , e . what ( ) ) ;
2013-09-19 19:43:16 +00:00
}
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 ;
}
2022-05-29 02:05:44 +00:00
GD_DPRINTF ( " erasing.. " ) ;
2009-03-26 19:00:08 +00:00
bodyRequests . pop_front ( ) ;
2022-05-29 02:05:44 +00:00
GD_DPRINTF ( " erase done.. " ) ;
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
{
2022-05-29 02:05:44 +00:00
GD_DPRINTF ( " one not finished. " ) ;
2014-05-10 21:02:31 +00:00
break ;
2009-01-28 20:55:45 +00:00
}
}
2022-01-08 06:51:24 +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 ;
2013-05-31 03:28:05 +00:00
2009-03-26 19:00:08 +00:00
{
string footer ;
2009-01-28 20:55:45 +00:00
2009-04-12 16:22:42 +00:00
if ( closePrevSpan )
{
2017-06-09 12:38:23 +00:00
footer + = " </div></div> " ;
2009-04-12 16:22:42 +00:00
closePrevSpan = false ;
}
2013-05-31 03:28:05 +00:00
2009-03-26 19:00:08 +00:00
if ( ! foundAnyDefinitions )
{
// No definitions were ever found, say so to the user.
2010-03-30 13:41:14 +00:00
2018-07-07 09:33:15 +00:00
// Larger words are usually whole sentences - don't clutter the output
2010-03-30 13:41:14 +00:00
// with their full bodies.
footer + = ArticleMaker : : makeNotFoundBody ( word . size ( ) < 40 ? word : " " , group ) ;
2009-03-26 19:00:08 +00:00
2009-04-17 13:51:50 +00:00
// When there were no definitions, we run stemmed search.
2022-11-29 03:54:31 +00:00
stemmedWordFinder = std : : make_shared < WordFinder > ( this ) ;
2009-04-17 13:51:50 +00:00
2022-12-26 02:08:17 +00:00
connect ( stemmedWordFinder . get ( ) ,
& WordFinder : : finished ,
this ,
& ArticleRequest : : stemmedSearchFinished ,
Qt : : QueuedConnection ) ;
2009-04-17 13:51:50 +00:00
stemmedWordFinder - > stemmedMatch ( word , activeDicts ) ;
}
else
{
footer + = " </body></html> " ;
}
2009-03-26 19:00:08 +00:00
Mutex : : Lock _ ( dataMutex ) ;
2013-05-31 03:28:05 +00:00
2009-03-26 19:00:08 +00:00
size_t offset = data . size ( ) ;
2013-05-31 03:28:05 +00:00
2009-03-26 19:00:08 +00:00
data . resize ( data . size ( ) + footer . size ( ) ) ;
2013-05-31 03:28:05 +00:00
2009-03-26 19:00:08 +00:00
memcpy ( & data . front ( ) + offset , footer . data ( ) , footer . size ( ) ) ;
}
2009-04-17 13:51:50 +00:00
if ( stemmedWordFinder . get ( ) )
2022-01-19 12:16:45 +00:00
{
update ( ) ;
qDebug ( ) < < " send dicts(stemmed): " < < word < < " : " < < dictIds ;
2022-05-14 09:22:34 +00:00
emit GlobalBroadcaster : : instance ( ) - > dictionaryChanges ( ActiveDictIds { word , dictIds } ) ;
2022-01-19 12:16:45 +00:00
dictIds . clear ( ) ;
}
2022-01-08 06:51:24 +00:00
else {
2009-04-17 13:51:50 +00:00
finish ( ) ;
2022-01-19 12:16:45 +00:00
qDebug ( ) < < " send dicts(finished): " < < word < < " : " < < dictIds ;
2022-05-14 09:22:34 +00:00
emit GlobalBroadcaster : : instance ( ) - > dictionaryChanges ( ActiveDictIds { word , dictIds } ) ;
2022-01-19 12:16:45 +00:00
dictIds . clear ( ) ;
2022-01-08 06:51:24 +00:00
}
2022-01-08 13:45:10 +00:00
} else if ( wasUpdated ) {
2009-03-26 19:00:08 +00:00
update ( ) ;
2022-01-18 15:04:27 +00:00
qDebug ( ) < < " send dicts(updated): " < < word < < " : " < < dictIds ;
2022-05-14 09:22:34 +00:00
emit GlobalBroadcaster : : instance ( ) - > dictionaryChanges ( ActiveDictIds { word , dictIds } ) ;
2022-01-19 12:16:45 +00:00
dictIds . clear ( ) ;
2022-01-08 13:45:10 +00:00
}
2009-02-01 00:08:08 +00:00
}
2009-03-26 19:00:08 +00:00
2022-04-20 12:53:57 +00:00
int ArticleRequest : : htmlTextSize ( QString html )
{
// website dictionary.
if ( html . contains ( QRegularExpression ( " <iframe \\ s*[^>]*> " , QRegularExpression : : CaseInsensitiveOption ) ) )
{
//arbitary number;
return 1000 ;
}
//https://bugreports.qt.io/browse/QTBUG-102757
QString stripStyleSheet =
html . remove ( QRegularExpression ( " <link \\ s*[^>]*> " , QRegularExpression : : CaseInsensitiveOption ) )
2022-12-24 22:01:50 +00:00
. remove ( QRegularExpression ( R " (<script[ \ s \ S]*?>[ \ s \ S]*?< \ /script>) " , QRegularExpression : : CaseInsensitiveOption | QRegularExpression : : MultilineOption ) ) ;
2022-04-20 12:53:57 +00:00
int size = QTextDocumentFragment : : fromHtml ( stripStyleSheet ) . toPlainText ( ) . length ( ) ;
return size ;
}
2009-04-17 13:51:50 +00:00
void ArticleRequest : : stemmedSearchFinished ( )
{
// Got stemmed matching results
WordFinder : : SearchResults sr = stemmedWordFinder - > getResults ( ) ;
string footer ;
2010-03-30 13:41:14 +00:00
bool continueMatching = false ;
2009-04-17 13:51:50 +00:00
if ( sr . size ( ) )
{
2022-12-24 22:01:50 +00:00
footer + = R " (<div class= " gdstemmedsuggestion " ><span class= " gdstemmedsuggestion_head " >) " +
2009-04-17 13:51:50 +00:00
Html : : escape ( tr ( " Close words: " ) . toUtf8 ( ) . data ( ) ) +
" </span><span class= \" gdstemmedsuggestion_body \" > " ;
for ( unsigned x = 0 ; x < sr . size ( ) ; + + x )
{
2010-03-30 13:41:14 +00:00
footer + = linkWord ( sr [ x ] . first ) ;
2009-04-17 13:51:50 +00:00
if ( x ! = sr . size ( ) - 1 )
{
footer + = " , " ;
}
}
footer + = " </span></div> " ;
}
2010-03-30 13:41:14 +00:00
splittedWords = splitIntoWords ( word ) ;
if ( splittedWords . first . size ( ) > 1 ) // Contains more than one word
{
2022-12-26 02:08:17 +00:00
disconnect ( stemmedWordFinder . get ( ) , & WordFinder : : finished , this , & ArticleRequest : : stemmedSearchFinished ) ;
2010-03-30 13:41:14 +00:00
2022-12-26 02:08:17 +00:00
connect ( stemmedWordFinder . get ( ) ,
& WordFinder : : finished ,
this ,
& ArticleRequest : : individualWordFinished ,
Qt : : QueuedConnection ) ;
2010-03-30 13:41:14 +00:00
currentSplittedWordStart = - 1 ;
currentSplittedWordEnd = currentSplittedWordStart ;
firstCompoundWasFound = false ;
compoundSearchNextStep ( false ) ;
continueMatching = true ;
}
if ( ! continueMatching )
footer + = " </body></html> " ;
2009-04-17 13:51:50 +00:00
{
Mutex : : Lock _ ( dataMutex ) ;
2013-05-31 03:28:05 +00:00
2009-04-17 13:51:50 +00:00
size_t offset = data . size ( ) ;
2013-05-31 03:28:05 +00:00
2009-04-17 13:51:50 +00:00
data . resize ( data . size ( ) + footer . size ( ) ) ;
2013-05-31 03:28:05 +00:00
2009-04-17 13:51:50 +00:00
memcpy ( & data . front ( ) + offset , footer . data ( ) , footer . size ( ) ) ;
}
2010-03-30 13:41:14 +00:00
if ( continueMatching )
update ( ) ;
else
finish ( ) ;
}
void ArticleRequest : : compoundSearchNextStep ( bool lastSearchSucceeded )
{
if ( ! lastSearchSucceeded )
{
// Last search was unsuccessful. First, emit what we had.
string footer ;
2010-05-29 20:50:16 +00:00
if ( lastGoodCompoundResult . size ( ) ) // We have something to append
2010-03-30 13:41:14 +00:00
{
2022-01-15 07:29:20 +00:00
// GD_DPRINTF( "Appending\n" );
2010-03-30 13:41:14 +00:00
if ( ! firstCompoundWasFound )
{
// Append the beginning
2022-12-24 22:01:50 +00:00
footer + = R " (<div class= " gdstemmedsuggestion " ><span class= " gdstemmedsuggestion_head " >) " +
2010-03-30 13:41:14 +00:00
Html : : escape ( tr ( " Compound expressions: " ) . toUtf8 ( ) . data ( ) ) +
" </span><span class= \" gdstemmedsuggestion_body \" > " ;
firstCompoundWasFound = true ;
}
else
{
// Append the separator
footer + = " / " ;
}
2010-05-29 20:50:16 +00:00
footer + = linkWord ( lastGoodCompoundResult ) ;
lastGoodCompoundResult . clear ( ) ;
2010-03-30 13:41:14 +00:00
}
// Then, start a new search for the next word, if possible
if ( currentSplittedWordStart > = splittedWords . first . size ( ) - 2 )
{
// The last word was the last possible to start from
if ( firstCompoundWasFound )
footer + = " </span> " ;
2010-04-01 09:08:51 +00:00
// Now add links to all the individual words. They conclude the result.
2022-12-24 22:01:50 +00:00
footer + = R " (<div class= " gdstemmedsuggestion " ><span class= " gdstemmedsuggestion_head " >) " +
2010-04-01 09:08:51 +00:00
Html : : escape ( tr ( " Individual words: " ) . toUtf8 ( ) . data ( ) ) +
2013-07-16 13:59:56 +00:00
" </span><span class= \" gdstemmedsuggestion_body \" " ;
if ( splittedWords . first [ 0 ] . isRightToLeft ( ) )
footer + = " dir= \" rtl \" " ;
footer + = " > " ;
2010-04-01 09:08:51 +00:00
footer + = escapeSpacing ( splittedWords . second [ 0 ] ) ;
for ( int x = 0 ; x < splittedWords . first . size ( ) ; + + x )
{
footer + = linkWord ( splittedWords . first [ x ] ) ;
footer + = escapeSpacing ( splittedWords . second [ x + 1 ] ) ;
}
footer + = " </span> " ;
2010-03-30 13:41:14 +00:00
footer + = " </body></html> " ;
appendToData ( footer ) ;
finish ( ) ;
return ;
}
if ( footer . size ( ) )
{
appendToData ( footer ) ;
update ( ) ;
}
// Advance to the next word and start from looking up two words
+ + currentSplittedWordStart ;
currentSplittedWordEnd = currentSplittedWordStart + 1 ;
}
else
{
// Last lookup succeeded -- see if we can try the larger sequence
if ( currentSplittedWordEnd < splittedWords . first . size ( ) - 1 )
{
// We can, indeed.
+ + currentSplittedWordEnd ;
}
else
{
// We can't. Emit what we have and start over.
+ + currentSplittedWordEnd ; // So we could use the same code for result
// emitting
// Initiate new lookup
compoundSearchNextStep ( false ) ;
return ;
}
}
// Build the compound sequence
currentSplittedWordCompound = makeSplittedWordCompound ( ) ;
// Look it up
2022-01-15 07:29:20 +00:00
// GD_DPRINTF( "Looking up %s\n", qPrintable( currentSplittedWordCompound ) );
2010-03-30 13:41:14 +00:00
2015-02-25 17:03:08 +00:00
stemmedWordFinder - > expressionMatch ( currentSplittedWordCompound , activeDicts , 40 , // Would one be enough? Leave 40 to be safe.
Dictionary : : SuitableForCompoundSearching ) ;
2010-03-30 13:41:14 +00:00
}
QString ArticleRequest : : makeSplittedWordCompound ( )
{
QString result ;
result . clear ( ) ;
for ( int x = currentSplittedWordStart ; x < = currentSplittedWordEnd ; + + x )
{
result . append ( splittedWords . first [ x ] ) ;
if ( x < currentSplittedWordEnd )
{
wstring ws ( gd : : toWString ( splittedWords . second [ x + 1 ] ) ) ;
Folding : : normalizeWhitespace ( ws ) ;
result . append ( gd : : toQString ( ws ) ) ;
}
}
return result ;
2009-04-17 13:51:50 +00:00
}
2010-03-30 13:41:14 +00:00
void ArticleRequest : : individualWordFinished ( )
{
WordFinder : : SearchResults const & results = stemmedWordFinder - > getResults ( ) ;
if ( results . size ( ) )
{
wstring source = Folding : : applySimpleCaseOnly ( gd : : toWString ( currentSplittedWordCompound ) ) ;
2010-05-29 20:50:16 +00:00
bool hadSomething = false ;
2010-03-30 13:41:14 +00:00
for ( unsigned x = 0 ; x < results . size ( ) ; + + x )
2010-05-29 20:50:16 +00:00
{
if ( results [ x ] . second )
2015-02-25 17:03:08 +00:00
{
// Spelling suggestion match found. No need to continue.
hadSomething = true ;
lastGoodCompoundResult = currentSplittedWordCompound ;
break ;
}
// Prefix match found. Check if the aliases are acceptable.
2010-05-29 20:50:16 +00:00
wstring result ( Folding : : applySimpleCaseOnly ( gd : : toWString ( results [ x ] . first ) ) ) ;
if ( source . size ( ) < = result . size ( ) & & result . compare ( 0 , source . size ( ) , source ) = = 0 )
2010-03-30 13:41:14 +00:00
{
2010-05-29 20:50:16 +00:00
// The resulting string begins with the source one
hadSomething = true ;
if ( source . size ( ) = = result . size ( ) )
{
// Got the match. No need to continue.
lastGoodCompoundResult = currentSplittedWordCompound ;
break ;
}
2010-03-30 13:41:14 +00:00
}
2010-05-29 20:50:16 +00:00
}
if ( hadSomething )
{
compoundSearchNextStep ( true ) ;
return ;
}
2010-03-30 13:41:14 +00:00
}
compoundSearchNextStep ( false ) ;
}
void ArticleRequest : : appendToData ( std : : string const & str )
{
Mutex : : Lock _ ( dataMutex ) ;
size_t offset = data . size ( ) ;
data . resize ( data . size ( ) + str . size ( ) ) ;
memcpy ( & data . front ( ) + offset , str . data ( ) , str . size ( ) ) ;
}
QPair < ArticleRequest : : Words , ArticleRequest : : Spacings > ArticleRequest : : splitIntoWords ( QString const & input )
{
QPair < Words , Spacings > result ;
QChar const * ptr = input . data ( ) ;
for ( ; ; )
{
QString spacing ;
for ( ; ptr - > unicode ( ) & & ( Folding : : isPunct ( ptr - > unicode ( ) ) | | Folding : : isWhitespace ( ptr - > unicode ( ) ) ) ; + + ptr )
spacing . append ( * ptr ) ;
result . second . append ( spacing ) ;
QString word ;
for ( ; ptr - > unicode ( ) & & ! ( Folding : : isPunct ( ptr - > unicode ( ) ) | | Folding : : isWhitespace ( ptr - > unicode ( ) ) ) ; + + ptr )
word . append ( * ptr ) ;
if ( word . isEmpty ( ) )
break ;
result . first . append ( word ) ;
}
return result ;
}
string ArticleRequest : : linkWord ( QString const & str )
{
2013-05-31 04:20:25 +00:00
QUrl url ;
2010-03-30 13:41:14 +00:00
url . setScheme ( " gdlookup " ) ;
url . setHost ( " localhost " ) ;
2021-11-27 07:17:33 +00:00
url . setPath ( Utils : : Url : : ensureLeadingSlash ( str ) ) ;
2010-03-30 13:41:14 +00:00
string escapedResult = Html : : escape ( str . toUtf8 ( ) . data ( ) ) ;
return string ( " <a href= \" " ) + url . toEncoded ( ) . data ( ) + " \" > " + escapedResult + " </a> " ;
}
std : : string ArticleRequest : : escapeSpacing ( QString const & str )
{
QByteArray spacing = Html : : escape ( str . toUtf8 ( ) . data ( ) ) . c_str ( ) ;
spacing . replace ( " \n " , " <br> " ) ;
return spacing . data ( ) ;
}
2011-12-12 16:52:07 +00:00
void ArticleRequest : : cancel ( )
{
if ( isFinished ( ) )
return ;
if ( ! altSearches . empty ( ) )
{
for ( list < sptr < Dictionary : : WordSearchRequest > > : : iterator i =
altSearches . begin ( ) ; i ! = altSearches . end ( ) ; + + i )
{
( * i ) - > cancel ( ) ;
}
}
if ( ! bodyRequests . empty ( ) )
{
for ( list < sptr < Dictionary : : DataRequest > > : : iterator i =
bodyRequests . begin ( ) ; i ! = bodyRequests . end ( ) ; + + i )
{
( * i ) - > cancel ( ) ;
}
}
if ( stemmedWordFinder . get ( ) ) stemmedWordFinder - > cancel ( ) ;
finish ( ) ;
}