2013-05-08 13:39:47 +00:00
|
|
|
/* This file is (c) 2013 Timon Wong <timon86.wang AT gmail DOT com>
|
2013-04-23 12:07:05 +00:00
|
|
|
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
|
|
|
|
|
|
|
#include "mdx.hh"
|
|
|
|
#include "btreeidx.hh"
|
|
|
|
#include "folding.hh"
|
|
|
|
#include "utf8.hh"
|
|
|
|
#include "file.hh"
|
|
|
|
#include "wstring.hh"
|
|
|
|
#include "wstring_qt.hh"
|
|
|
|
#include "chunkedstorage.hh"
|
2013-11-16 18:34:09 +00:00
|
|
|
#include "gddebug.hh"
|
2013-04-23 12:07:05 +00:00
|
|
|
#include "langcoder.hh"
|
2023-04-28 16:09:45 +00:00
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
#include "audiolink.hh"
|
2013-04-28 08:26:04 +00:00
|
|
|
#include "ex.hh"
|
2013-04-23 12:07:05 +00:00
|
|
|
#include "mdictparser.hh"
|
2013-05-23 14:01:50 +00:00
|
|
|
#include "filetype.hh"
|
2014-04-16 16:18:28 +00:00
|
|
|
#include "ftshelpers.hh"
|
|
|
|
#include "htmlescape.hh"
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2013-06-17 15:30:20 +00:00
|
|
|
#include <algorithm>
|
2013-04-23 12:07:05 +00:00
|
|
|
#include <map>
|
|
|
|
#include <set>
|
|
|
|
#include <list>
|
|
|
|
#ifdef _MSC_VER
|
2023-05-28 16:01:21 +00:00
|
|
|
#include <stub_msvc.h>
|
2013-04-23 12:07:05 +00:00
|
|
|
#endif
|
|
|
|
|
2023-04-18 00:41:47 +00:00
|
|
|
#include "globalregex.hh"
|
|
|
|
#include "tiff.hh"
|
|
|
|
#include "utils.hh"
|
2013-04-23 12:07:05 +00:00
|
|
|
#include <QAtomicInt>
|
|
|
|
#include <QCryptographicHash>
|
2023-04-18 00:41:47 +00:00
|
|
|
#include <QDir>
|
2018-02-27 16:42:21 +00:00
|
|
|
#include <QRegularExpression>
|
2023-04-18 00:41:47 +00:00
|
|
|
#include <QString>
|
|
|
|
#include <QThreadPool>
|
2022-06-19 12:24:34 +00:00
|
|
|
#include <QtConcurrent>
|
2013-05-30 13:24:21 +00:00
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
namespace Mdx {
|
|
|
|
|
|
|
|
using std::map;
|
|
|
|
using std::multimap;
|
|
|
|
using std::set;
|
|
|
|
using gd::wstring;
|
|
|
|
using gd::wchar;
|
|
|
|
using std::list;
|
|
|
|
using std::pair;
|
|
|
|
using std::string;
|
|
|
|
|
|
|
|
using BtreeIndexing::WordArticleLink;
|
|
|
|
using BtreeIndexing::IndexedWords;
|
|
|
|
using BtreeIndexing::IndexInfo;
|
|
|
|
|
2013-04-28 08:26:04 +00:00
|
|
|
using namespace Mdict;
|
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
enum {
|
|
|
|
kSignature = 0x4349444d, // MDIC
|
2018-04-08 14:57:10 +00:00
|
|
|
kCurrentFormatVersion = 11 + BtreeIndexing::FormatVersion + Folding::Version
|
2013-04-23 12:07:05 +00:00
|
|
|
};
|
|
|
|
|
2013-05-22 03:15:47 +00:00
|
|
|
DEF_EX( exCorruptDictionary, "dictionary file was tampered or corrupted", std::exception )
|
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
struct IdxHeader
|
|
|
|
{
|
|
|
|
uint32_t signature; // First comes the signature, MDIC
|
|
|
|
uint32_t formatVersion; // File format version, currently 1.
|
|
|
|
uint32_t parserVersion; // Version of the parser used to parse the MDIC file.
|
|
|
|
// Version of the folding algorithm used when building
|
|
|
|
// index. If it's different from the current one,
|
|
|
|
// the file is to be rebuilt.
|
|
|
|
uint32_t foldingVersion;
|
|
|
|
|
|
|
|
uint32_t articleCount; // Total number of articles, for informative purposes only
|
|
|
|
uint32_t wordCount; // Total number of words, for informative purposes only
|
|
|
|
|
|
|
|
uint32_t isRightToLeft; // Right to left
|
|
|
|
uint32_t chunksOffset; // The offset to chunks' storage
|
|
|
|
|
|
|
|
uint32_t descriptionAddress; // Address of the dictionary description in the chunks' storage
|
|
|
|
uint32_t descriptionSize; // Size of the description in the chunks' storage, 0 = no description
|
|
|
|
|
|
|
|
uint32_t styleSheetAddress;
|
|
|
|
uint32_t styleSheetCount;
|
|
|
|
|
|
|
|
uint32_t indexBtreeMaxElements; // Two fields from IndexInfo
|
|
|
|
uint32_t indexRootOffset;
|
|
|
|
|
|
|
|
uint32_t langFrom; // Source language
|
|
|
|
uint32_t langTo; // Target language
|
|
|
|
|
2013-05-08 13:39:47 +00:00
|
|
|
uint32_t mddIndexInfosOffset; // address of IndexInfos for resource files (.mdd)
|
|
|
|
uint32_t mddIndexInfosCount; // count of IndexInfos for resource files
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
__attribute__( ( packed ) )
|
|
|
|
#endif
|
|
|
|
;
|
|
|
|
|
|
|
|
// A helper method to read resources from .mdd file
|
|
|
|
class IndexedMdd: public BtreeIndexing::BtreeIndex
|
|
|
|
{
|
2023-05-29 13:56:04 +00:00
|
|
|
QMutex & idxMutex;
|
|
|
|
QMutex fileMutex;
|
2013-04-23 12:07:05 +00:00
|
|
|
ChunkedStorage::Reader & chunks;
|
|
|
|
QFile mddFile;
|
|
|
|
bool isFileOpen;
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
2023-05-29 13:56:04 +00:00
|
|
|
IndexedMdd( QMutex & idxMutex, ChunkedStorage::Reader & chunks ):
|
2013-04-23 12:07:05 +00:00
|
|
|
idxMutex( idxMutex ),
|
|
|
|
chunks( chunks ),
|
|
|
|
isFileOpen( false )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Opens the index. The values are those previously returned by buildIndex().
|
|
|
|
using BtreeIndexing::BtreeIndex::openIndex;
|
|
|
|
|
|
|
|
/// Opens the mdd file itself. Returns true if succeeded, false otherwise.
|
|
|
|
bool open( const char * fileName )
|
|
|
|
{
|
|
|
|
mddFile.setFileName( QString::fromUtf8( fileName ) );
|
|
|
|
isFileOpen = mddFile.open( QFile::ReadOnly );
|
|
|
|
return isFileOpen;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns true if the mdd is open, false otherwise.
|
|
|
|
inline bool isOpen() const
|
|
|
|
{
|
|
|
|
return isFileOpen;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Checks whether the given file exists in the mdd file or not.
|
|
|
|
/// Note that this function is thread-safe, since it does not access mdd file.
|
|
|
|
bool hasFile( gd::wstring const & name )
|
|
|
|
{
|
|
|
|
if ( !isFileOpen )
|
|
|
|
return false;
|
|
|
|
vector< WordArticleLink > links = findArticles( name );
|
|
|
|
return !links.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempts loading the given file into the given vector. Returns true on
|
|
|
|
/// success, false otherwise.
|
|
|
|
bool loadFile( gd::wstring const & name, std::vector< char > & result )
|
|
|
|
{
|
|
|
|
if ( !isFileOpen )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
vector< WordArticleLink > links = findArticles( name );
|
|
|
|
if ( links.empty() )
|
|
|
|
return false;
|
|
|
|
|
2013-04-28 08:26:04 +00:00
|
|
|
MdictParser::RecordInfo indexEntry;
|
|
|
|
vector< char > chunk;
|
2023-05-29 13:56:04 +00:00
|
|
|
// QMutexLocker _( &idxMutex );
|
2013-04-28 08:26:04 +00:00
|
|
|
const char * indexEntryPtr = chunks.getBlock( links[ 0 ].articleOffset, chunk );
|
|
|
|
memcpy( &indexEntry, indexEntryPtr, sizeof( indexEntry ) );
|
|
|
|
|
2022-01-09 10:09:53 +00:00
|
|
|
//corrupted file or broken entry.
|
|
|
|
if ( indexEntry.decompressedBlockSize < indexEntry.recordOffset + indexEntry.recordSize ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
QByteArray decompressed;
|
2022-06-19 05:26:41 +00:00
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
2023-05-29 13:56:04 +00:00
|
|
|
QMutexLocker _( &idxMutex );
|
2022-06-19 05:26:41 +00:00
|
|
|
ScopedMemMap compressed( mddFile, indexEntry.compressedBlockPos, indexEntry.compressedBlockSize );
|
|
|
|
if ( !compressed.startAddress() ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !MdictParser::parseCompressedBlock( indexEntry.compressedBlockSize,
|
|
|
|
(char *)compressed.startAddress(),
|
|
|
|
indexEntry.decompressedBlockSize,
|
|
|
|
decompressed ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
2013-04-28 08:26:04 +00:00
|
|
|
result.resize( indexEntry.recordSize );
|
|
|
|
memcpy( &result.front(), decompressed.constData() + indexEntry.recordOffset, indexEntry.recordSize );
|
2013-04-23 12:07:05 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
class MdxDictionary: public BtreeIndexing::BtreeDictionary
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
2023-05-29 13:56:04 +00:00
|
|
|
QMutex idxMutex;
|
2013-04-23 12:07:05 +00:00
|
|
|
File::Class idx;
|
2023-05-28 16:01:21 +00:00
|
|
|
string idxFileName;
|
2013-04-23 12:07:05 +00:00
|
|
|
IdxHeader idxHeader;
|
2013-04-28 08:26:04 +00:00
|
|
|
string encoding;
|
2013-04-23 12:07:05 +00:00
|
|
|
ChunkedStorage::Reader chunks;
|
2013-04-28 08:26:04 +00:00
|
|
|
QFile dictFile;
|
2013-05-08 13:39:47 +00:00
|
|
|
vector< sptr< IndexedMdd > > mddResources;
|
2013-04-23 12:07:05 +00:00
|
|
|
MdictParser::StyleSheets styleSheets;
|
|
|
|
|
|
|
|
QAtomicInt deferredInitDone;
|
2023-05-29 13:56:04 +00:00
|
|
|
QMutex deferredInitMutex;
|
2013-04-23 12:07:05 +00:00
|
|
|
bool deferredInitRunnableStarted;
|
|
|
|
|
|
|
|
string initError;
|
2021-11-12 18:26:46 +00:00
|
|
|
QString cacheDirName;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
MdxDictionary( string const & id, string const & indexFile, vector< string > const & dictionaryFiles );
|
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
~MdxDictionary() override;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
void deferredInit() override;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
string getName() noexcept override
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
return dictionaryName;
|
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
map< Dictionary::Property, string > getProperties() noexcept override
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
2023-05-28 16:01:21 +00:00
|
|
|
return {};
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
unsigned long getArticleCount() noexcept override
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
return idxHeader.articleCount;
|
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
unsigned long getWordCount() noexcept override
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
return idxHeader.wordCount;
|
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
inline quint32 getLangFrom() const override
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
return idxHeader.langFrom;
|
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
inline quint32 getLangTo() const override
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
return idxHeader.langTo;
|
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
sptr< Dictionary::DataRequest >
|
|
|
|
getArticle( wstring const & word, vector< wstring > const & alts, wstring const &, bool ignoreDiacritics ) override;
|
|
|
|
sptr< Dictionary::DataRequest > getResource( string const & name ) override;
|
|
|
|
QString const & getDescription() override;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2023-05-30 23:42:31 +00:00
|
|
|
sptr< Dictionary::DataRequest >
|
|
|
|
getSearchResults( QString const & searchString, int searchMode, bool matchCase, bool ignoreDiacritics ) override;
|
2022-12-29 07:07:40 +00:00
|
|
|
void getArticleText( uint32_t articleAddress, QString & headword, QString & text ) override;
|
2014-04-16 16:18:28 +00:00
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
void makeFTSIndex( QAtomicInt & isCancelled, bool firstIteration ) override;
|
2014-04-16 16:18:28 +00:00
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
void setFTSParameters( Config::FullTextSearch const & fts ) override
|
2014-04-17 14:31:51 +00:00
|
|
|
{
|
2023-05-28 16:01:21 +00:00
|
|
|
if ( !ensureInitDone().empty() )
|
2022-05-19 12:42:46 +00:00
|
|
|
return;
|
2014-04-17 14:31:51 +00:00
|
|
|
|
2023-07-26 02:03:20 +00:00
|
|
|
can_FTS = enable_FTS && fts.enabled && !fts.disabledTypes.contains( "MDICT", Qt::CaseInsensitive )
|
2014-04-17 14:31:51 +00:00
|
|
|
&& ( fts.maxDictionarySize == 0 || getArticleCount() <= fts.maxDictionarySize );
|
|
|
|
}
|
2021-11-12 18:26:46 +00:00
|
|
|
|
2023-06-06 15:45:04 +00:00
|
|
|
QString getCachedFileName( QString name );
|
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
protected:
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
void loadIcon() noexcept override;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
private:
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
string const & ensureInitDone() override;
|
2013-04-23 12:07:05 +00:00
|
|
|
void doDeferredInit();
|
|
|
|
|
|
|
|
/// Loads an article with the given offset, filling the given strings.
|
2014-04-29 13:55:48 +00:00
|
|
|
void loadArticle( uint32_t offset, string & articleText, bool noFilter = false );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
/// Process resource links (images, audios, etc)
|
2023-04-09 03:48:21 +00:00
|
|
|
QString & filterResource( QString & article );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2023-04-09 03:48:21 +00:00
|
|
|
void replaceLinks( QString & id, QString & article );
|
2022-10-03 12:28:16 +00:00
|
|
|
//@font-face
|
2022-10-11 12:57:04 +00:00
|
|
|
void replaceStyleInHtml( QString & id, QString & article );
|
2022-10-03 12:28:16 +00:00
|
|
|
void replaceFontLinks( QString & id, QString & article );
|
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
friend class MdxArticleRequest;
|
|
|
|
friend class MddResourceRequest;
|
2023-03-25 12:35:50 +00:00
|
|
|
void loadResourceFile( const wstring & resourceName, vector< char > & data );
|
2013-04-23 12:07:05 +00:00
|
|
|
};
|
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
MdxDictionary::MdxDictionary( string const & id, string const & indexFile, vector< string > const & dictionaryFiles ):
|
2013-04-23 12:07:05 +00:00
|
|
|
BtreeDictionary( id, dictionaryFiles ),
|
|
|
|
idx( indexFile, "rb" ),
|
2023-05-28 16:01:21 +00:00
|
|
|
idxFileName( indexFile ),
|
2013-04-23 12:07:05 +00:00
|
|
|
idxHeader( idx.read< IdxHeader >() ),
|
|
|
|
chunks( idx, idxHeader.chunksOffset ),
|
2022-03-20 10:49:58 +00:00
|
|
|
deferredInitRunnableStarted( false )
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
// Read the dictionary's name
|
2013-04-28 08:26:04 +00:00
|
|
|
idx.seek( sizeof( idxHeader ) );
|
2013-04-23 12:07:05 +00:00
|
|
|
size_t len = idx.read< uint32_t >();
|
2013-04-28 08:26:04 +00:00
|
|
|
vector< char > buf( len );
|
2019-01-17 14:53:13 +00:00
|
|
|
if ( len > 0 ) {
|
|
|
|
idx.read( &buf.front(), len );
|
|
|
|
dictionaryName = string( &buf.front(), len );
|
|
|
|
}
|
2013-04-28 08:26:04 +00:00
|
|
|
|
|
|
|
// then read the dictionary's encoding
|
|
|
|
len = idx.read< uint32_t >();
|
2019-01-17 14:53:13 +00:00
|
|
|
if ( len > 0 ) {
|
|
|
|
buf.resize( len );
|
|
|
|
idx.read( &buf.front(), len );
|
|
|
|
encoding = string( &buf.front(), len );
|
|
|
|
}
|
2013-04-28 08:26:04 +00:00
|
|
|
|
|
|
|
dictFile.setFileName( QString::fromUtf8( dictionaryFiles[ 0 ].c_str() ) );
|
|
|
|
dictFile.open( QIODevice::ReadOnly );
|
2014-04-16 16:18:28 +00:00
|
|
|
|
|
|
|
// Full-text search parameters
|
|
|
|
|
2022-10-06 03:04:48 +00:00
|
|
|
ftsIdxName = indexFile + Dictionary::getFtsSuffix();
|
2014-04-16 16:18:28 +00:00
|
|
|
|
2021-11-12 18:26:46 +00:00
|
|
|
cacheDirName = QDir::tempPath() + QDir::separator() + QString::fromUtf8( getId().c_str() ) + ".cache";
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
MdxDictionary::~MdxDictionary()
|
|
|
|
{
|
2023-05-29 13:56:04 +00:00
|
|
|
QMutexLocker _( &deferredInitMutex );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2013-04-28 08:26:04 +00:00
|
|
|
dictFile.close();
|
2021-11-12 18:26:46 +00:00
|
|
|
|
2023-08-05 11:36:33 +00:00
|
|
|
Utils::Fs::removeDirectory( cacheDirName );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//////// MdxDictionary::deferredInit()
|
|
|
|
|
|
|
|
void MdxDictionary::deferredInit()
|
|
|
|
{
|
2021-11-27 07:17:33 +00:00
|
|
|
if ( !Utils::AtomicInt::loadAcquire( deferredInitDone ) ) {
|
2023-05-29 13:56:04 +00:00
|
|
|
QMutexLocker _( &deferredInitMutex );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2021-11-27 07:17:33 +00:00
|
|
|
if ( Utils::AtomicInt::loadAcquire( deferredInitDone ) )
|
2013-04-23 12:07:05 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
if ( !deferredInitRunnableStarted ) {
|
2022-04-09 07:41:33 +00:00
|
|
|
QThreadPool::globalInstance()->start(
|
|
|
|
[ this ]() {
|
|
|
|
this->doDeferredInit();
|
|
|
|
},
|
|
|
|
-1000 );
|
2013-04-23 12:07:05 +00:00
|
|
|
deferredInitRunnableStarted = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
string const & MdxDictionary::ensureInitDone()
|
|
|
|
{
|
|
|
|
doDeferredInit();
|
|
|
|
return initError;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MdxDictionary::doDeferredInit()
|
|
|
|
{
|
2021-11-27 07:17:33 +00:00
|
|
|
if ( !Utils::AtomicInt::loadAcquire( deferredInitDone ) ) {
|
2023-05-29 13:56:04 +00:00
|
|
|
QMutexLocker _( &deferredInitMutex );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2021-11-27 07:17:33 +00:00
|
|
|
if ( Utils::AtomicInt::loadAcquire( deferredInitDone ) )
|
2013-04-23 12:07:05 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// Do deferred init
|
|
|
|
|
|
|
|
try {
|
|
|
|
// Retrieve stylesheets
|
|
|
|
idx.seek( idxHeader.styleSheetAddress );
|
|
|
|
for ( uint32_t i = 0; i < idxHeader.styleSheetCount; i++ ) {
|
2013-06-04 16:17:55 +00:00
|
|
|
qint32 key = idx.read< qint32 >();
|
2013-04-23 12:07:05 +00:00
|
|
|
vector< char > buf;
|
2013-06-04 16:17:55 +00:00
|
|
|
quint32 sz;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2013-06-04 16:17:55 +00:00
|
|
|
sz = idx.read< quint32 >();
|
2013-04-23 12:07:05 +00:00
|
|
|
buf.resize( sz );
|
|
|
|
idx.read( &buf.front(), sz );
|
|
|
|
QString styleBegin = QString::fromUtf8( buf.data() );
|
|
|
|
|
2013-06-04 16:17:55 +00:00
|
|
|
sz = idx.read< quint32 >();
|
2013-04-23 12:07:05 +00:00
|
|
|
buf.resize( sz );
|
|
|
|
idx.read( &buf.front(), sz );
|
|
|
|
QString styleEnd = QString::fromUtf8( buf.data() );
|
|
|
|
|
|
|
|
styleSheets[ key ] = pair< QString, QString >( styleBegin, styleEnd );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the index
|
|
|
|
openIndex( IndexInfo( idxHeader.indexBtreeMaxElements, idxHeader.indexRootOffset ), idx, idxMutex );
|
|
|
|
|
2013-05-08 13:39:47 +00:00
|
|
|
vector< string > mddFileNames;
|
|
|
|
vector< IndexInfo > mddIndexInfos;
|
|
|
|
idx.seek( idxHeader.mddIndexInfosOffset );
|
|
|
|
for ( uint32_t i = 0; i < idxHeader.mddIndexInfosCount; i++ ) {
|
2013-06-04 16:17:55 +00:00
|
|
|
quint32 sz = idx.read< quint32 >();
|
2013-05-08 13:39:47 +00:00
|
|
|
vector< char > buf( sz );
|
|
|
|
idx.read( &buf.front(), sz );
|
|
|
|
uint32_t btreeMaxElements = idx.read< uint32_t >();
|
|
|
|
uint32_t rootOffset = idx.read< uint32_t >();
|
2022-08-09 12:47:10 +00:00
|
|
|
mddFileNames.emplace_back( &buf.front() );
|
|
|
|
mddIndexInfos.emplace_back( btreeMaxElements, rootOffset );
|
2013-05-08 13:39:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
vector< string > const dictFiles = getDictionaryFilenames();
|
|
|
|
for ( uint32_t i = 1; i < dictFiles.size() && i < mddFileNames.size() + 1; i++ ) {
|
2013-06-04 16:17:55 +00:00
|
|
|
QFileInfo fi( QString::fromUtf8( dictFiles[ i ].c_str() ) );
|
|
|
|
QString mddFileName = QString::fromUtf8( mddFileNames[ i - 1 ].c_str() );
|
|
|
|
|
|
|
|
if ( fi.fileName() != mddFileName || !fi.exists() )
|
2013-05-08 13:39:47 +00:00
|
|
|
continue;
|
|
|
|
|
2022-11-29 03:54:31 +00:00
|
|
|
sptr< IndexedMdd > mdd = std::make_shared< IndexedMdd >( idxMutex, chunks );
|
2013-05-08 13:39:47 +00:00
|
|
|
mdd->openIndex( mddIndexInfos[ i - 1 ], idx, idxMutex );
|
|
|
|
mdd->open( dictFiles[ i ].c_str() );
|
|
|
|
mddResources.push_back( mdd );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
catch ( std::exception & e ) {
|
|
|
|
initError = e.what();
|
|
|
|
}
|
|
|
|
catch ( ... ) {
|
|
|
|
initError = "Unknown error";
|
|
|
|
}
|
|
|
|
|
|
|
|
deferredInitDone.ref();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-16 16:18:28 +00:00
|
|
|
void MdxDictionary::makeFTSIndex( QAtomicInt & isCancelled, bool firstIteration )
|
|
|
|
{
|
2023-06-03 00:29:19 +00:00
|
|
|
if ( !( Dictionary::needToRebuildIndex( getDictionaryFilenames(), ftsIdxName )
|
|
|
|
|| FtsHelpers::ftsIndexIsOldOrBad( this ) ) )
|
2014-04-16 16:18:28 +00:00
|
|
|
FTS_index_completed.ref();
|
|
|
|
|
|
|
|
if ( haveFTSIndex() )
|
|
|
|
return;
|
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
// if( !ensureInitDone().empty() )
|
|
|
|
// return;
|
2014-04-16 16:18:28 +00:00
|
|
|
|
|
|
|
if ( firstIteration && getArticleCount() > FTS::MaxDictionarySizeForFastSearch )
|
|
|
|
return;
|
|
|
|
|
2022-05-15 04:41:24 +00:00
|
|
|
gdDebug( "MDict: Building the full-text index for dictionary: %s", getName().c_str() );
|
2014-04-16 16:18:28 +00:00
|
|
|
|
|
|
|
try {
|
2023-05-28 16:01:21 +00:00
|
|
|
auto _dict = std::make_shared< MdxDictionary >( this->getId(), idxFileName, this->getDictionaryFilenames() );
|
|
|
|
if ( !_dict->ensureInitDone().empty() )
|
|
|
|
return;
|
|
|
|
FtsHelpers::makeFTSIndex( _dict.get(), isCancelled );
|
2014-04-17 14:31:51 +00:00
|
|
|
FTS_index_completed.ref();
|
2014-04-16 16:18:28 +00:00
|
|
|
}
|
|
|
|
catch ( std::exception & ex ) {
|
2022-05-15 04:41:24 +00:00
|
|
|
gdWarning( "MDict: Failed building full-text search index for \"%s\", reason: %s", getName().c_str(), ex.what() );
|
2023-04-13 10:08:32 +00:00
|
|
|
QFile::remove( ftsIdxName.c_str() );
|
2014-04-16 16:18:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MdxDictionary::getArticleText( uint32_t articleAddress, QString & headword, QString & text )
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
headword.clear();
|
|
|
|
string articleText;
|
|
|
|
|
2014-04-29 13:55:48 +00:00
|
|
|
loadArticle( articleAddress, articleText, true );
|
2014-04-16 16:18:28 +00:00
|
|
|
text = Html::unescape( QString::fromUtf8( articleText.data(), articleText.size() ) );
|
|
|
|
}
|
|
|
|
catch ( std::exception & ex ) {
|
2022-05-15 04:41:24 +00:00
|
|
|
gdWarning( "MDict: Failed retrieving article from \"%s\", reason: %s", getName().c_str(), ex.what() );
|
2014-04-16 16:18:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sptr< Dictionary::DataRequest > MdxDictionary::getSearchResults( QString const & searchString,
|
2023-05-30 23:42:31 +00:00
|
|
|
int searchMode,
|
|
|
|
bool matchCase,
|
|
|
|
|
2018-04-10 14:49:52 +00:00
|
|
|
bool ignoreDiacritics )
|
2014-04-16 16:18:28 +00:00
|
|
|
{
|
2023-05-30 23:42:31 +00:00
|
|
|
return std::make_shared< FtsHelpers::FTSResultsRequest >( *this,
|
|
|
|
searchString,
|
|
|
|
searchMode,
|
|
|
|
matchCase,
|
|
|
|
ignoreDiacritics );
|
2014-04-16 16:18:28 +00:00
|
|
|
}
|
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
/// MdxDictionary::getArticle
|
|
|
|
|
|
|
|
class MdxArticleRequest: public Dictionary::DataRequest
|
|
|
|
{
|
|
|
|
wstring word;
|
|
|
|
vector< wstring > alts;
|
|
|
|
MdxDictionary & dict;
|
2018-06-13 16:00:42 +00:00
|
|
|
bool ignoreDiacritics;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
QAtomicInt isCancelled;
|
2022-06-19 12:24:34 +00:00
|
|
|
QFuture< void > f;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
MdxArticleRequest( wstring const & word_,
|
|
|
|
vector< wstring > const & alts_,
|
2018-06-13 16:00:42 +00:00
|
|
|
MdxDictionary & dict_,
|
|
|
|
bool ignoreDiacritics_ ):
|
2013-04-23 12:07:05 +00:00
|
|
|
word( word_ ),
|
|
|
|
alts( alts_ ),
|
2018-06-13 16:00:42 +00:00
|
|
|
dict( dict_ ),
|
|
|
|
ignoreDiacritics( ignoreDiacritics_ )
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
2022-06-19 12:24:34 +00:00
|
|
|
f = QtConcurrent::run( [ this ]() {
|
|
|
|
this->run();
|
|
|
|
} );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void run();
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
void cancel() override
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
isCancelled.ref();
|
|
|
|
}
|
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
~MdxArticleRequest() override
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
isCancelled.ref();
|
2022-06-19 12:24:34 +00:00
|
|
|
f.waitForFinished();
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void MdxArticleRequest::run()
|
|
|
|
{
|
2021-11-27 07:17:33 +00:00
|
|
|
if ( Utils::AtomicInt::loadAcquire( isCancelled ) ) {
|
2013-04-23 12:07:05 +00:00
|
|
|
finish();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
if ( !dict.ensureInitDone().empty() ) {
|
2013-04-23 12:07:05 +00:00
|
|
|
setErrorString( QString::fromUtf8( dict.ensureInitDone().c_str() ) );
|
|
|
|
finish();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-06-13 16:00:42 +00:00
|
|
|
vector< WordArticleLink > chain = dict.findArticles( word, ignoreDiacritics );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
for ( const auto & alt : alts ) {
|
2013-04-23 12:07:05 +00:00
|
|
|
/// Make an additional query for each alt
|
2023-05-28 16:01:21 +00:00
|
|
|
vector< WordArticleLink > altChain = dict.findArticles( alt, ignoreDiacritics );
|
2013-04-23 12:07:05 +00:00
|
|
|
chain.insert( chain.end(), altChain.begin(), altChain.end() );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Some synonims make it that the articles appear several times. We combat this
|
|
|
|
// by only allowing them to appear once.
|
|
|
|
set< uint32_t > articlesIncluded;
|
|
|
|
// Sometimes the articles are physically duplicated. We store hashes of
|
|
|
|
// the bodies to account for this.
|
|
|
|
set< QByteArray > articleBodiesIncluded;
|
|
|
|
string articleText;
|
|
|
|
|
|
|
|
for ( unsigned x = 0; x < chain.size(); ++x ) {
|
2021-11-27 07:17:33 +00:00
|
|
|
if ( Utils::AtomicInt::loadAcquire( isCancelled ) ) {
|
2013-04-23 12:07:05 +00:00
|
|
|
finish();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( articlesIncluded.find( chain[ x ].articleOffset ) != articlesIncluded.end() )
|
|
|
|
continue; // We already have this article in the body.
|
|
|
|
|
|
|
|
// Grab that article
|
|
|
|
string articleBody;
|
2013-05-22 03:15:47 +00:00
|
|
|
bool hasError = false;
|
|
|
|
QString errorMessage;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2013-05-22 03:15:47 +00:00
|
|
|
try {
|
|
|
|
dict.loadArticle( chain[ x ].articleOffset, articleBody );
|
|
|
|
}
|
|
|
|
catch ( exCorruptDictionary & ) {
|
2013-05-22 11:56:28 +00:00
|
|
|
errorMessage = tr( "Dictionary file was tampered or corrupted" );
|
2013-05-22 03:15:47 +00:00
|
|
|
hasError = true;
|
|
|
|
}
|
|
|
|
catch ( std::exception & e ) {
|
|
|
|
errorMessage = e.what();
|
|
|
|
hasError = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( hasError ) {
|
|
|
|
setErrorString( tr( "Failed loading article from %1, reason: %2" )
|
2022-04-11 11:10:40 +00:00
|
|
|
.arg( QString::fromUtf8( dict.getDictionaryFilenames()[ 0 ].c_str() ), errorMessage ) );
|
2013-05-22 03:15:47 +00:00
|
|
|
finish();
|
|
|
|
return;
|
|
|
|
}
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
if ( articlesIncluded.find( chain[ x ].articleOffset ) != articlesIncluded.end() )
|
|
|
|
continue; // We already have this article in the body.
|
|
|
|
|
|
|
|
QCryptographicHash hash( QCryptographicHash::Md5 );
|
|
|
|
hash.addData( articleBody.data(), articleBody.size() );
|
|
|
|
if ( !articleBodiesIncluded.insert( hash.result() ).second )
|
|
|
|
continue; // Already had this body
|
|
|
|
|
|
|
|
// Handle internal redirects
|
|
|
|
if ( strncmp( articleBody.c_str(), "@@@LINK=", 8 ) == 0 ) {
|
|
|
|
wstring target = Utf8::decode( articleBody.c_str() + 8 );
|
|
|
|
target = Folding::trimWhitespace( target );
|
|
|
|
// Make an additional query for this redirection
|
|
|
|
vector< WordArticleLink > altChain = dict.findArticles( target );
|
|
|
|
chain.insert( chain.end(), altChain.begin(), altChain.end() );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-04-25 20:41:10 +00:00
|
|
|
// See Issue #271: A mechanism to clean-up invalid HTML cards.
|
2023-04-15 07:39:49 +00:00
|
|
|
string cleaner = Utils::Html::getHtmlCleaner();
|
2013-04-25 20:41:10 +00:00
|
|
|
articleText += "<div class=\"mdict\">" + articleBody + cleaner + "</div>\n";
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
2019-11-19 16:20:44 +00:00
|
|
|
if ( !articleText.empty() ) {
|
2022-04-10 12:40:09 +00:00
|
|
|
articleText += "</div></div></div></div></div></div></div></div></div>";
|
|
|
|
|
2023-05-29 13:56:04 +00:00
|
|
|
QMutexLocker _( &dataMutex );
|
2013-04-23 12:07:05 +00:00
|
|
|
data.insert( data.end(), articleText.begin(), articleText.end() );
|
2019-11-19 16:20:44 +00:00
|
|
|
hasAnyData = true;
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
finish();
|
|
|
|
}
|
|
|
|
|
|
|
|
sptr< Dictionary::DataRequest > MdxDictionary::getArticle( const wstring & word,
|
|
|
|
const vector< wstring > & alts,
|
2022-01-09 08:35:07 +00:00
|
|
|
const wstring &,
|
|
|
|
bool ignoreDiacritics )
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
2022-11-29 03:54:31 +00:00
|
|
|
return std::make_shared< MdxArticleRequest >( word, alts, *this, ignoreDiacritics );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// MdxDictionary::getResource
|
|
|
|
class MddResourceRequest: public Dictionary::DataRequest
|
|
|
|
{
|
|
|
|
MdxDictionary & dict;
|
|
|
|
wstring resourceName;
|
|
|
|
QAtomicInt isCancelled;
|
2022-06-19 12:24:34 +00:00
|
|
|
QFuture< void > f;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
public:
|
|
|
|
|
2022-09-18 01:00:27 +00:00
|
|
|
MddResourceRequest( MdxDictionary & dict_, string const & resourceName_ ):
|
|
|
|
Dictionary::DataRequest( &dict_ ),
|
|
|
|
dict( dict_ ),
|
|
|
|
resourceName( Utf8::decode( resourceName_ ) )
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
2022-06-19 12:24:34 +00:00
|
|
|
f = QtConcurrent::run( [ this ]() {
|
|
|
|
this->run();
|
|
|
|
} );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
2023-07-17 13:51:56 +00:00
|
|
|
QByteArray isolate_css();
|
2022-05-08 08:44:59 +00:00
|
|
|
void run();
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
void cancel() override
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
isCancelled.ref();
|
|
|
|
}
|
|
|
|
|
|
|
|
~MddResourceRequest()
|
|
|
|
{
|
|
|
|
isCancelled.ref();
|
2022-06-19 12:24:34 +00:00
|
|
|
f.waitForFinished();
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-07-17 13:51:56 +00:00
|
|
|
QByteArray MddResourceRequest::isolate_css()
|
|
|
|
{
|
|
|
|
|
|
|
|
const QString id = QString::fromUtf8( dict.getId().c_str() );
|
|
|
|
|
|
|
|
QString css = QString::fromUtf8( data.data(), data.size() );
|
|
|
|
|
|
|
|
int pos = 0;
|
|
|
|
|
|
|
|
QString newCSS;
|
|
|
|
QRegularExpressionMatchIterator it = RX::Mdx::links.globalMatch( css );
|
|
|
|
while ( it.hasNext() ) {
|
|
|
|
QRegularExpressionMatch match = it.next();
|
|
|
|
newCSS += css.mid( pos, match.capturedStart() - pos );
|
|
|
|
pos = match.capturedEnd();
|
|
|
|
QString url = match.captured( 2 );
|
|
|
|
|
|
|
|
|
|
|
|
if ( url.indexOf( ":/" ) >= 0 || url.indexOf( "data:" ) >= 0 ) {
|
|
|
|
// External link or base64-encoded data
|
|
|
|
newCSS += match.captured();
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString newUrl = QString( "url(" ) + match.captured( 1 ) + "bres://" + id + "/" + url + match.captured( 3 ) + ")";
|
|
|
|
newCSS += newUrl;
|
|
|
|
}
|
|
|
|
if ( pos ) {
|
|
|
|
newCSS += css.mid( pos );
|
|
|
|
css = newCSS;
|
|
|
|
newCSS.clear();
|
|
|
|
}
|
|
|
|
dict.isolateCSS( css, ".mdict" );
|
|
|
|
auto bytes = css.toUtf8();
|
|
|
|
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
void MddResourceRequest::run()
|
|
|
|
{
|
2021-11-27 07:17:33 +00:00
|
|
|
if ( Utils::AtomicInt::loadAcquire( isCancelled ) ) {
|
2013-04-23 12:07:05 +00:00
|
|
|
finish();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( dict.ensureInitDone().size() ) {
|
|
|
|
setErrorString( QString::fromUtf8( dict.ensureInitDone().c_str() ) );
|
|
|
|
finish();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// In order to prevent recursive internal redirection...
|
2023-04-09 03:48:21 +00:00
|
|
|
set< wstring, std::less<> > resourceIncluded;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
for ( ;; ) {
|
|
|
|
// Some runnables linger enough that they are cancelled before they start
|
2021-11-27 07:17:33 +00:00
|
|
|
if ( Utils::AtomicInt::loadAcquire( isCancelled ) ) {
|
2013-04-23 12:07:05 +00:00
|
|
|
finish();
|
|
|
|
return;
|
|
|
|
}
|
2013-05-08 13:39:47 +00:00
|
|
|
string u8ResourceName = Utf8::encode( resourceName );
|
2023-03-27 01:49:20 +00:00
|
|
|
if ( !resourceIncluded.insert( resourceName ).second ) {
|
|
|
|
finish();
|
|
|
|
return;
|
|
|
|
}
|
2013-05-08 13:39:47 +00:00
|
|
|
|
2023-05-29 13:56:04 +00:00
|
|
|
QMutexLocker _( &dataMutex );
|
2013-04-23 12:07:05 +00:00
|
|
|
data.clear();
|
2013-05-08 13:39:47 +00:00
|
|
|
|
2023-07-17 13:51:56 +00:00
|
|
|
//check the global cache
|
|
|
|
//generate resource unique key
|
|
|
|
const QString id = QString::fromUtf8( dict.getId().c_str() );
|
|
|
|
|
|
|
|
const QString unique_key = id + QString::fromStdString( u8ResourceName );
|
|
|
|
if ( GlobalBroadcaster::instance()->cache.contains( unique_key ) ) {
|
|
|
|
//take first ,then insert again . the object() method may become null anytime.
|
|
|
|
auto bytes = GlobalBroadcaster::instance()->cache.take( unique_key );
|
|
|
|
if ( bytes ) {
|
|
|
|
hasAnyData = true;
|
|
|
|
data.resize( bytes->size() );
|
|
|
|
memcpy( &data.front(), bytes->constData(), bytes->size() );
|
2023-08-26 05:19:21 +00:00
|
|
|
GlobalBroadcaster::instance()->insertCache( unique_key, bytes );
|
2023-07-17 13:51:56 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-03-25 12:35:50 +00:00
|
|
|
dict.loadResourceFile( resourceName, data );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2013-05-08 13:39:47 +00:00
|
|
|
// Check if this file has a redirection
|
|
|
|
// Always encoded in UTF16-LE
|
|
|
|
// L"@@@LINK="
|
|
|
|
static const char pattern[ 16 ] =
|
|
|
|
{ '@', '\0', '@', '\0', '@', '\0', 'L', '\0', 'I', '\0', 'N', '\0', 'K', '\0', '=', '\0' };
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2013-05-08 13:39:47 +00:00
|
|
|
if ( data.size() > sizeof( pattern ) ) {
|
|
|
|
if ( memcmp( &data.front(), pattern, sizeof( pattern ) ) == 0 ) {
|
|
|
|
data.push_back( '\0' );
|
|
|
|
data.push_back( '\0' );
|
|
|
|
QString target =
|
|
|
|
MdictParser::toUtf16( "UTF-16LE", &data.front() + sizeof( pattern ), data.size() - sizeof( pattern ) );
|
|
|
|
resourceName = gd::toWString( target.trimmed() );
|
|
|
|
continue;
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-05-08 13:39:47 +00:00
|
|
|
if ( data.size() > 0 ) {
|
|
|
|
hasAnyData = true;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2013-06-04 16:17:55 +00:00
|
|
|
if ( Filetype::isNameOfCSS( u8ResourceName ) ) {
|
2023-07-17 13:51:56 +00:00
|
|
|
QByteArray bytes = isolate_css();
|
|
|
|
|
2013-05-23 14:01:50 +00:00
|
|
|
data.resize( bytes.size() );
|
|
|
|
memcpy( &data.front(), bytes.constData(), bytes.size() );
|
2023-07-17 13:51:56 +00:00
|
|
|
//cache the processed css result to avoid process again.
|
2023-08-26 05:19:21 +00:00
|
|
|
GlobalBroadcaster::instance()->insertCache( unique_key, new QByteArray( bytes ) );
|
2013-05-23 14:01:50 +00:00
|
|
|
}
|
2022-04-04 15:50:33 +00:00
|
|
|
if ( Filetype::isNameOfTiff( u8ResourceName ) ) {
|
|
|
|
// Convert it
|
2022-04-05 13:25:07 +00:00
|
|
|
GdTiff::tiff2img( data );
|
2022-04-04 15:50:33 +00:00
|
|
|
}
|
2013-05-22 17:42:41 +00:00
|
|
|
}
|
2013-04-23 12:07:05 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
finish();
|
|
|
|
}
|
|
|
|
|
2022-01-09 08:35:07 +00:00
|
|
|
sptr< Dictionary::DataRequest > MdxDictionary::getResource( const string & name )
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
2022-11-29 03:54:31 +00:00
|
|
|
return std::make_shared< MddResourceRequest >( *this, name );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const QString & MdxDictionary::getDescription()
|
|
|
|
{
|
|
|
|
if ( !dictionaryDescription.isEmpty() )
|
|
|
|
return dictionaryDescription;
|
|
|
|
|
|
|
|
if ( idxHeader.descriptionSize == 0 ) {
|
|
|
|
dictionaryDescription = "NONE";
|
|
|
|
}
|
|
|
|
else {
|
2023-05-29 13:56:04 +00:00
|
|
|
// QMutexLocker _( &idxMutex );
|
2013-04-23 12:07:05 +00:00
|
|
|
vector< char > chunk;
|
|
|
|
char * dictDescription = chunks.getBlock( idxHeader.descriptionAddress, chunk );
|
|
|
|
string str( dictDescription );
|
|
|
|
dictionaryDescription = QString::fromUtf8( str.c_str(), str.size() );
|
|
|
|
}
|
|
|
|
|
|
|
|
return dictionaryDescription;
|
|
|
|
}
|
|
|
|
|
2022-06-03 13:28:41 +00:00
|
|
|
void MdxDictionary::loadIcon() noexcept
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
if ( dictionaryIconLoaded )
|
|
|
|
return;
|
|
|
|
|
2023-04-13 10:08:32 +00:00
|
|
|
QString fileName = QDir::fromNativeSeparators( getDictionaryFilenames()[ 0 ].c_str() );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
// Remove the extension
|
|
|
|
fileName.chop( 3 );
|
2022-07-31 08:17:57 +00:00
|
|
|
QString text = QString::fromStdString( dictionaryName );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2022-09-07 13:53:19 +00:00
|
|
|
if ( !loadIconFromFile( fileName ) && !loadIconFromText( ":/icons/mdict-bg.png", text ) ) {
|
2013-04-23 12:07:05 +00:00
|
|
|
// Use default icons
|
2023-06-19 02:34:08 +00:00
|
|
|
dictionaryIcon = QIcon( ":/icons/mdict.png" );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dictionaryIconLoaded = true;
|
|
|
|
}
|
|
|
|
|
2014-04-29 13:55:48 +00:00
|
|
|
void MdxDictionary::loadArticle( uint32_t offset, string & articleText, bool noFilter )
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
vector< char > chunk;
|
2023-05-29 13:56:04 +00:00
|
|
|
// QMutexLocker _( &idxMutex );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2013-04-28 08:26:04 +00:00
|
|
|
// Load record info from index
|
|
|
|
MdictParser::RecordInfo recordInfo;
|
2023-04-09 03:48:21 +00:00
|
|
|
const char * pRecordInfo = chunks.getBlock( offset, chunk );
|
2013-04-28 08:26:04 +00:00
|
|
|
memcpy( &recordInfo, pRecordInfo, sizeof( recordInfo ) );
|
2013-04-24 11:35:03 +00:00
|
|
|
|
2013-05-22 03:15:47 +00:00
|
|
|
QByteArray decompressed;
|
2022-06-18 08:24:30 +00:00
|
|
|
|
|
|
|
{
|
2023-05-29 13:56:04 +00:00
|
|
|
QMutexLocker _( &idxMutex );
|
2022-06-18 08:24:30 +00:00
|
|
|
ScopedMemMap compressed( dictFile, recordInfo.compressedBlockPos, recordInfo.compressedBlockSize );
|
|
|
|
if ( !compressed.startAddress() )
|
|
|
|
throw exCorruptDictionary();
|
|
|
|
|
|
|
|
if ( !MdictParser::parseCompressedBlock( recordInfo.compressedBlockSize,
|
|
|
|
(char *)compressed.startAddress(),
|
|
|
|
recordInfo.decompressedBlockSize,
|
|
|
|
decompressed ) )
|
|
|
|
throw exCorruptDictionary();
|
|
|
|
}
|
2013-04-28 08:26:04 +00:00
|
|
|
|
2013-05-22 03:15:47 +00:00
|
|
|
QString article =
|
|
|
|
MdictParser::toUtf16( encoding.c_str(), decompressed.constData() + recordInfo.recordOffset, recordInfo.recordSize );
|
2013-04-28 08:26:04 +00:00
|
|
|
|
2014-04-29 13:55:48 +00:00
|
|
|
if ( !noFilter ) {
|
2022-06-11 02:29:21 +00:00
|
|
|
article = MdictParser::substituteStylesheet( article, styleSheets );
|
2023-04-09 03:48:21 +00:00
|
|
|
article = filterResource( article );
|
2022-06-11 02:29:21 +00:00
|
|
|
}
|
2016-05-11 15:56:37 +00:00
|
|
|
|
2023-04-30 05:05:02 +00:00
|
|
|
articleText = Utils::c_string( article );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
2023-04-09 03:48:21 +00:00
|
|
|
QString & MdxDictionary::filterResource( QString & article )
|
2018-02-27 16:42:21 +00:00
|
|
|
{
|
|
|
|
QString id = QString::fromStdString( getId() );
|
2023-04-09 03:48:21 +00:00
|
|
|
replaceLinks( id, article );
|
2022-10-11 12:57:04 +00:00
|
|
|
replaceStyleInHtml( id, article );
|
2022-10-03 12:28:16 +00:00
|
|
|
return article;
|
|
|
|
}
|
|
|
|
|
2023-04-09 03:48:21 +00:00
|
|
|
void MdxDictionary::replaceLinks( QString & id, QString & article )
|
2022-10-03 12:28:16 +00:00
|
|
|
{
|
2018-02-27 16:42:21 +00:00
|
|
|
QString articleNewText;
|
2022-10-03 12:28:16 +00:00
|
|
|
int linkPos = 0;
|
2022-06-05 02:44:40 +00:00
|
|
|
QRegularExpressionMatchIterator it = RX::Mdx::allLinksRe.globalMatch( article );
|
2018-02-27 16:42:21 +00:00
|
|
|
while ( it.hasNext() ) {
|
|
|
|
QRegularExpressionMatch allLinksMatch = it.next();
|
|
|
|
|
|
|
|
if ( allLinksMatch.capturedEnd() < linkPos )
|
|
|
|
continue;
|
|
|
|
|
2022-02-27 05:17:37 +00:00
|
|
|
articleNewText += article.mid( linkPos, allLinksMatch.capturedStart() - linkPos );
|
2018-02-27 16:42:21 +00:00
|
|
|
linkPos = allLinksMatch.capturedEnd();
|
|
|
|
|
2022-10-03 12:28:16 +00:00
|
|
|
QString linkTxt = allLinksMatch.captured();
|
2018-02-27 16:42:21 +00:00
|
|
|
QString linkType = allLinksMatch.captured( 1 ).toLower();
|
|
|
|
QString newLink;
|
|
|
|
|
2023-10-08 14:23:17 +00:00
|
|
|
if ( linkType.compare( "a" ) == 0 || linkType.compare( "area" ) == 0 ) {
|
2023-03-11 03:10:14 +00:00
|
|
|
newLink = linkTxt;
|
2018-02-27 16:42:21 +00:00
|
|
|
|
2023-03-11 03:10:14 +00:00
|
|
|
QRegularExpressionMatch match = RX::Mdx::audioRe.match( newLink );
|
2018-02-27 16:42:21 +00:00
|
|
|
if ( match.hasMatch() ) {
|
|
|
|
// sounds and audio link script
|
2022-10-03 12:28:16 +00:00
|
|
|
QString newTxt =
|
|
|
|
match.captured( 1 ) + match.captured( 2 ) + "gdau://" + id + "/" + match.captured( 3 ) + match.captured( 2 );
|
|
|
|
newLink =
|
|
|
|
QString::fromUtf8(
|
|
|
|
addAudioLink( "\"gdau://" + getId() + "/" + match.captured( 3 ).toUtf8().data() + "\"", getId() ).c_str() )
|
|
|
|
+ newLink.replace( match.capturedStart(), match.capturedLength(), newTxt );
|
2018-02-27 16:42:21 +00:00
|
|
|
}
|
|
|
|
|
2022-06-05 02:44:40 +00:00
|
|
|
match = RX::Mdx::wordCrossLink.match( newLink );
|
2018-02-27 16:42:21 +00:00
|
|
|
if ( match.hasMatch() ) {
|
2023-04-09 03:21:32 +00:00
|
|
|
if ( !match.captured( 3 ).isEmpty() ) {
|
|
|
|
QString newTxt = match.captured( 1 ) + match.captured( 2 ) + "gdlookup://localhost/" + match.captured( 3 );
|
|
|
|
|
|
|
|
if ( match.lastCapturedIndex() >= 4 && !match.captured( 4 ).isEmpty() )
|
|
|
|
newTxt += QString( "?gdanchor=" ) + match.captured( 4 ).mid( 1 );
|
2018-02-27 16:42:21 +00:00
|
|
|
|
2023-04-09 03:21:32 +00:00
|
|
|
newTxt += match.captured( 2 );
|
|
|
|
newLink.replace( match.capturedStart(), match.capturedLength(), newTxt );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
//links like entry://#abc,just remove the prefix entry://
|
|
|
|
QString newTxt = match.captured( 1 ) + match.captured( 2 );
|
2018-02-27 16:42:21 +00:00
|
|
|
|
2023-04-09 03:21:32 +00:00
|
|
|
if ( match.lastCapturedIndex() >= 4 && !match.captured( 4 ).isEmpty() )
|
|
|
|
newTxt += match.captured( 4 );
|
|
|
|
|
|
|
|
newTxt += match.captured( 2 );
|
|
|
|
newLink.replace( match.capturedStart(), match.capturedLength(), newTxt );
|
|
|
|
}
|
2018-02-27 16:42:21 +00:00
|
|
|
}
|
|
|
|
}
|
2022-10-03 12:28:16 +00:00
|
|
|
else if ( linkType.compare( "link" ) == 0 ) {
|
2018-02-27 16:42:21 +00:00
|
|
|
// stylesheets
|
2022-06-05 02:44:40 +00:00
|
|
|
QRegularExpressionMatch match = RX::Mdx::stylesRe.match( linkTxt );
|
2018-02-27 16:42:21 +00:00
|
|
|
if ( match.hasMatch() ) {
|
2022-10-03 12:28:16 +00:00
|
|
|
QString newText =
|
|
|
|
match.captured( 1 ) + match.captured( 2 ) + "bres://" + id + "/" + match.captured( 3 ) + match.captured( 2 );
|
2018-02-27 16:42:21 +00:00
|
|
|
newLink = linkTxt.replace( match.capturedStart(), match.capturedLength(), newText );
|
|
|
|
}
|
|
|
|
else
|
2023-03-11 03:36:34 +00:00
|
|
|
newLink = linkTxt.replace( RX::Mdx::stylesRe2, R"(\1"bres://)" + id + R"(/\2")" );
|
2018-02-27 16:42:21 +00:00
|
|
|
}
|
2023-10-08 14:23:17 +00:00
|
|
|
else {
|
|
|
|
//linkType in ("script","img","source","audio","video")
|
2018-02-27 16:42:21 +00:00
|
|
|
// javascripts and images
|
2022-06-05 02:44:40 +00:00
|
|
|
QRegularExpressionMatch match = RX::Mdx::inlineScriptRe.match( linkTxt );
|
2023-10-08 15:34:08 +00:00
|
|
|
// "script" tag
|
2023-10-08 15:36:55 +00:00
|
|
|
if ( linkType.compare( "script" ) == 0 && match.hasMatch() && match.capturedLength() == linkTxt.length() ) {
|
2018-02-27 16:42:21 +00:00
|
|
|
// skip inline scripts
|
|
|
|
articleNewText += linkTxt;
|
2022-06-05 02:44:40 +00:00
|
|
|
match = RX::Mdx::closeScriptTagRe.match( article, linkPos );
|
2018-04-08 14:48:46 +00:00
|
|
|
if ( match.hasMatch() ) {
|
2022-02-27 05:17:37 +00:00
|
|
|
articleNewText += article.mid( linkPos, match.capturedEnd() - linkPos );
|
2018-04-08 14:48:46 +00:00
|
|
|
linkPos = match.capturedEnd();
|
2018-02-27 16:42:21 +00:00
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
2023-06-06 15:45:04 +00:00
|
|
|
else {
|
2023-10-08 14:23:17 +00:00
|
|
|
//audio ,video ,html5 tags fall here.
|
2022-06-05 02:44:40 +00:00
|
|
|
match = RX::Mdx::srcRe.match( linkTxt );
|
2023-06-06 15:45:04 +00:00
|
|
|
if ( match.hasMatch() ) {
|
|
|
|
QString newText;
|
2023-10-09 12:37:09 +00:00
|
|
|
QString scheme;
|
|
|
|
// "source" tag
|
|
|
|
if ( linkType.compare( "source" ) == 0 ) {
|
|
|
|
scheme = "gdvideo://";
|
2023-06-06 15:45:04 +00:00
|
|
|
}
|
|
|
|
else {
|
2023-10-09 12:37:09 +00:00
|
|
|
scheme = "bres://";
|
2023-06-06 15:45:04 +00:00
|
|
|
}
|
2023-10-09 12:37:09 +00:00
|
|
|
newText =
|
|
|
|
match.captured( 1 ) + match.captured( 2 ) + scheme + id + "/" + match.captured( 3 ) + match.captured( 2 );
|
|
|
|
|
2018-02-27 16:42:21 +00:00
|
|
|
newLink = linkTxt.replace( match.capturedStart(), match.capturedLength(), newText );
|
|
|
|
}
|
|
|
|
else
|
2023-03-11 03:36:34 +00:00
|
|
|
newLink = linkTxt.replace( RX::Mdx::srcRe2, R"(\1"bres://)" + id + R"(/\2")" );
|
2018-02-27 16:42:21 +00:00
|
|
|
}
|
|
|
|
}
|
2023-10-08 14:23:17 +00:00
|
|
|
|
2018-02-27 16:42:21 +00:00
|
|
|
if ( !newLink.isEmpty() ) {
|
|
|
|
articleNewText += newLink;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
articleNewText += allLinksMatch.captured();
|
|
|
|
}
|
|
|
|
if ( linkPos ) {
|
2022-02-27 05:17:37 +00:00
|
|
|
articleNewText += article.mid( linkPos );
|
2018-02-27 16:42:21 +00:00
|
|
|
article = articleNewText;
|
|
|
|
}
|
2022-10-03 12:28:16 +00:00
|
|
|
}
|
2022-04-16 12:30:46 +00:00
|
|
|
|
2022-10-11 12:57:04 +00:00
|
|
|
void MdxDictionary::replaceStyleInHtml( QString & id, QString & article )
|
|
|
|
{
|
|
|
|
//article = article.replace( RX::Mdx::fontFace, "src:url(\"bres://" + id + "/" + "\\1\")" );
|
|
|
|
QString articleNewText;
|
|
|
|
int linkPos = 0;
|
2023-05-26 16:27:50 +00:00
|
|
|
QRegularExpressionMatchIterator it = RX::Mdx::styleElement.globalMatch( article );
|
2022-10-11 12:57:04 +00:00
|
|
|
while ( it.hasNext() ) {
|
|
|
|
QRegularExpressionMatch allLinksMatch = it.next();
|
|
|
|
|
|
|
|
if ( allLinksMatch.capturedEnd() < linkPos )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
articleNewText += article.mid( linkPos, allLinksMatch.capturedStart() - linkPos );
|
|
|
|
linkPos = allLinksMatch.capturedEnd();
|
|
|
|
|
|
|
|
articleNewText += allLinksMatch.captured( 1 );
|
|
|
|
|
|
|
|
// the style
|
|
|
|
auto style = allLinksMatch.captured( 2 );
|
|
|
|
replaceFontLinks( id, style );
|
|
|
|
articleNewText += style;
|
|
|
|
articleNewText += allLinksMatch.captured( 3 );
|
|
|
|
}
|
|
|
|
if ( linkPos ) {
|
|
|
|
articleNewText += article.mid( linkPos );
|
|
|
|
article = articleNewText;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-03 12:28:16 +00:00
|
|
|
void MdxDictionary::replaceFontLinks( QString & id, QString & article )
|
|
|
|
{
|
|
|
|
//article = article.replace( RX::Mdx::fontFace, "src:url(\"bres://" + id + "/" + "\\1\")" );
|
|
|
|
QString articleNewText;
|
|
|
|
int linkPos = 0;
|
|
|
|
QRegularExpressionMatchIterator it = RX::Mdx::fontFace.globalMatch( article );
|
|
|
|
while ( it.hasNext() ) {
|
|
|
|
QRegularExpressionMatch allLinksMatch = it.next();
|
|
|
|
|
|
|
|
if ( allLinksMatch.capturedEnd() < linkPos )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
articleNewText += article.mid( linkPos, allLinksMatch.capturedStart() - linkPos );
|
|
|
|
linkPos = allLinksMatch.capturedEnd();
|
|
|
|
QString linkTxt = allLinksMatch.captured();
|
|
|
|
QString linkType = allLinksMatch.captured( 1 );
|
|
|
|
QString newLink = linkTxt;
|
|
|
|
|
|
|
|
//skip remote url
|
|
|
|
if ( !linkType.contains( ":" ) ) {
|
|
|
|
newLink = QString( "url(\"bres://%1/%2\")" ).arg( id, linkType );
|
|
|
|
}
|
|
|
|
articleNewText += newLink;
|
|
|
|
}
|
|
|
|
if ( linkPos ) {
|
|
|
|
articleNewText += article.mid( linkPos );
|
|
|
|
article = articleNewText;
|
|
|
|
}
|
2018-02-27 16:42:21 +00:00
|
|
|
}
|
2015-10-28 19:56:58 +00:00
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2023-06-06 15:45:04 +00:00
|
|
|
QString MdxDictionary::getCachedFileName( QString filename )
|
|
|
|
{
|
|
|
|
QDir dir;
|
|
|
|
QFileInfo info( cacheDirName );
|
|
|
|
if ( !info.exists() || !info.isDir() ) {
|
|
|
|
if ( !dir.mkdir( cacheDirName ) ) {
|
|
|
|
gdWarning( "Mdx: can't create cache directory \"%s\"", cacheDirName.toUtf8().data() );
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create subfolders if needed
|
|
|
|
|
|
|
|
QString name = filename;
|
|
|
|
name.replace( '/', '\\' );
|
|
|
|
QStringList list = name.split( '\\' );
|
|
|
|
int subFolders = list.size() - 1;
|
|
|
|
if ( subFolders > 0 ) {
|
|
|
|
QString dirName = cacheDirName;
|
|
|
|
for ( int i = 0; i < subFolders; i++ ) {
|
|
|
|
dirName += QDir::separator() + list.at( i );
|
|
|
|
QFileInfo dirInfo( dirName );
|
|
|
|
if ( !dirInfo.exists() ) {
|
|
|
|
if ( !dir.mkdir( dirName ) ) {
|
|
|
|
gdWarning( "Mdx: can't create cache directory \"%s\"", dirName.toUtf8().data() );
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString fullName = cacheDirName + QDir::separator() + filename;
|
|
|
|
|
|
|
|
info.setFile( fullName );
|
|
|
|
if ( info.exists() )
|
|
|
|
return fullName;
|
|
|
|
QFile f( fullName );
|
|
|
|
if ( !f.open( QFile::WriteOnly ) ) {
|
|
|
|
gdWarning( R"(Mdx: file "%s" creating error: "%s")", fullName.toUtf8().data(), f.errorString().toUtf8().data() );
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
gd::wstring resourceName = filename.toStdU32String();
|
|
|
|
vector< char > data;
|
|
|
|
|
|
|
|
// In order to prevent recursive internal redirection...
|
|
|
|
set< wstring, std::less<> > resourceIncluded;
|
|
|
|
|
|
|
|
for ( ;; ) {
|
|
|
|
if ( !resourceIncluded.insert( resourceName ).second )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
loadResourceFile( resourceName, data );
|
|
|
|
|
|
|
|
// Check if this file has a redirection
|
|
|
|
// Always encoded in UTF16-LE
|
|
|
|
// L"@@@LINK="
|
|
|
|
if ( static const char pattern[ 16 ] =
|
|
|
|
{ '@', '\0', '@', '\0', '@', '\0', 'L', '\0', 'I', '\0', 'N', '\0', 'K', '\0', '=', '\0' };
|
|
|
|
data.size() > sizeof( pattern ) && memcmp( &data.front(), pattern, sizeof( pattern ) ) == 0 ) {
|
|
|
|
data.push_back( '\0' );
|
|
|
|
data.push_back( '\0' );
|
|
|
|
QString target =
|
|
|
|
MdictParser::toUtf16( "UTF-16LE", &data.front() + sizeof( pattern ), data.size() - sizeof( pattern ) );
|
|
|
|
resourceName = gd::toWString( target.trimmed() );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
qint64 n = 0;
|
|
|
|
if ( !data.empty() )
|
|
|
|
n = f.write( data.data(), data.size() );
|
|
|
|
|
|
|
|
f.close();
|
|
|
|
|
|
|
|
if ( n < (qint64)data.size() ) {
|
|
|
|
gdWarning( R"(Mdx: file "%s" writing error: "%s")", fullName.toUtf8().data(), f.errorString().toUtf8().data() );
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
return fullName;
|
|
|
|
}
|
|
|
|
|
2023-03-25 12:35:50 +00:00
|
|
|
void MdxDictionary::loadResourceFile( const wstring & resourceName, vector< char > & data )
|
|
|
|
{
|
|
|
|
wstring newResourceName = resourceName;
|
|
|
|
string u8ResourceName = Utf8::encode( resourceName );
|
|
|
|
|
|
|
|
// Convert to the Windows separator
|
|
|
|
std::replace( newResourceName.begin(), newResourceName.end(), '/', '\\' );
|
|
|
|
if ( newResourceName[ 0 ] == '.' ) {
|
|
|
|
newResourceName.erase( 0, 1 );
|
|
|
|
}
|
|
|
|
if ( newResourceName[ 0 ] != '\\' ) {
|
|
|
|
newResourceName.insert( 0, 1, '\\' );
|
|
|
|
}
|
|
|
|
// local file takes precedence
|
2023-04-28 16:09:45 +00:00
|
|
|
if ( string fn = getContainingFolder().toStdString() + Utils::Fs::separator() + u8ResourceName; File::exists( fn ) ) {
|
2023-03-25 12:35:50 +00:00
|
|
|
File::loadFromFile( fn, data );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for ( auto mddResource : mddResources ) {
|
|
|
|
if ( mddResource->loadFile( newResourceName, data ) )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
static void addEntryToIndex( QString const & word, uint32_t offset, IndexedWords & indexedWords )
|
|
|
|
{
|
|
|
|
// Strip any leading or trailing whitespaces
|
|
|
|
QString wordTrimmed = word.trimmed();
|
|
|
|
indexedWords.addWord( gd::toWString( wordTrimmed ), offset );
|
|
|
|
}
|
|
|
|
|
|
|
|
static void addEntryToIndexSingle( QString const & word, uint32_t offset, IndexedWords & indexedWords )
|
|
|
|
{
|
|
|
|
// Strip any leading or trailing whitespaces
|
|
|
|
QString wordTrimmed = word.trimmed();
|
|
|
|
indexedWords.addSingleWord( gd::toWString( wordTrimmed ), offset );
|
|
|
|
}
|
|
|
|
|
2013-04-28 08:26:04 +00:00
|
|
|
class ArticleHandler: public MdictParser::RecordHandler
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
ArticleHandler( ChunkedStorage::Writer & chunks, IndexedWords & indexedWords ):
|
|
|
|
chunks( chunks ),
|
2013-04-28 08:26:04 +00:00
|
|
|
indexedWords( indexedWords )
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
void handleRecord( QString const & headWord, MdictParser::RecordInfo const & recordInfo ) override
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
2013-04-28 08:26:04 +00:00
|
|
|
// Save the article's record info
|
2013-04-23 12:07:05 +00:00
|
|
|
uint32_t articleAddress = chunks.startNewBlock();
|
2013-04-28 08:26:04 +00:00
|
|
|
chunks.addToBlock( &recordInfo, sizeof( recordInfo ) );
|
2013-04-23 12:07:05 +00:00
|
|
|
// Add entries to the index
|
|
|
|
addEntryToIndex( headWord, articleAddress, indexedWords );
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
ChunkedStorage::Writer & chunks;
|
|
|
|
IndexedWords & indexedWords;
|
|
|
|
};
|
|
|
|
|
2013-04-28 08:26:04 +00:00
|
|
|
class ResourceHandler: public MdictParser::RecordHandler
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
ResourceHandler( ChunkedStorage::Writer & chunks, IndexedWords & indexedWords ):
|
|
|
|
chunks( chunks ),
|
|
|
|
indexedWords( indexedWords )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
void handleRecord( QString const & fileName, MdictParser::RecordInfo const & recordInfo ) override
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
uint32_t resourceInfoAddress = chunks.startNewBlock();
|
2013-04-28 08:26:04 +00:00
|
|
|
chunks.addToBlock( &recordInfo, sizeof( recordInfo ) );
|
2013-04-23 12:07:05 +00:00
|
|
|
// Add entries to the index
|
|
|
|
addEntryToIndexSingle( fileName, resourceInfoAddress, indexedWords );
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
ChunkedStorage::Writer & chunks;
|
|
|
|
IndexedWords & indexedWords;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2013-05-08 13:39:47 +00:00
|
|
|
static bool indexIsOldOrBad( vector< string > const & dictFiles, string const & indexFile )
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
File::Class idx( indexFile, "rb" );
|
|
|
|
IdxHeader header;
|
|
|
|
|
|
|
|
return idx.readRecords( &header, sizeof( header ), 1 ) != 1 || header.signature != kSignature
|
|
|
|
|| header.formatVersion != kCurrentFormatVersion || header.parserVersion != MdictParser::kParserVersion
|
2013-05-08 13:39:47 +00:00
|
|
|
|| header.foldingVersion != Folding::Version || header.mddIndexInfosCount != dictFiles.size() - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void findResourceFiles( string const & mdx, vector< string > & dictFiles )
|
|
|
|
{
|
|
|
|
string base( mdx, 0, mdx.size() - 4 );
|
|
|
|
// Check if there' is any file end with .mdd, which is the resource file for the dictionary
|
|
|
|
string resFile;
|
|
|
|
if ( File::tryPossibleName( base + ".mdd", resFile ) ) {
|
|
|
|
dictFiles.push_back( resFile );
|
|
|
|
// Find complementary .mdd file (volumes), like follows:
|
|
|
|
// demo.mdx <- main dictionary file
|
|
|
|
// demo.mdd <- main resource file ( 1st volume )
|
|
|
|
// demo.1.mdd <- 2nd volume
|
|
|
|
// ...
|
|
|
|
// demo.n.mdd <- nth volume
|
|
|
|
QString baseU8 = QString::fromUtf8( base.c_str() );
|
|
|
|
int vol = 1;
|
|
|
|
while ( File::tryPossibleName( string( QString( "%1.%2.mdd" ).arg( baseU8 ).arg( vol ).toUtf8().constBegin() ),
|
|
|
|
resFile ) ) {
|
|
|
|
dictFiles.push_back( resFile );
|
|
|
|
vol++;
|
|
|
|
}
|
|
|
|
}
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
2022-04-13 15:29:36 +00:00
|
|
|
vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & fileNames,
|
2013-04-23 12:07:05 +00:00
|
|
|
string const & indicesDir,
|
2022-01-09 08:35:07 +00:00
|
|
|
Dictionary::Initializing & initializing )
|
2013-04-23 12:07:05 +00:00
|
|
|
{
|
|
|
|
vector< sptr< Dictionary::Class > > dictionaries;
|
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
for ( const auto & fileName : fileNames ) {
|
2013-04-23 12:07:05 +00:00
|
|
|
// Skip files with the extensions different to .mdx to speed up the
|
|
|
|
// scanning
|
2023-09-02 04:24:55 +00:00
|
|
|
if ( !Utils::endsWithIgnoreCase( fileName, ".mdx" ) )
|
2013-04-23 12:07:05 +00:00
|
|
|
continue;
|
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
vector< string > dictFiles( 1, fileName );
|
|
|
|
findResourceFiles( fileName, dictFiles );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2023-07-29 13:35:26 +00:00
|
|
|
initializing.loadingDictionary( fileName );
|
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
string dictId = Dictionary::makeDictionaryId( dictFiles );
|
2013-04-23 12:07:05 +00:00
|
|
|
string indexFile = indicesDir + dictId;
|
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( dictFiles, indexFile ) ) {
|
2013-04-23 12:07:05 +00:00
|
|
|
// Building the index
|
2013-09-20 14:25:44 +00:00
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
gdDebug( "MDict: Building the index for dictionary: %s\n", fileName.c_str() );
|
2013-09-20 14:25:44 +00:00
|
|
|
|
2013-05-11 05:41:26 +00:00
|
|
|
MdictParser parser;
|
2013-05-08 13:39:47 +00:00
|
|
|
list< sptr< MdictParser > > mddParsers;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
if ( !parser.open( fileName.c_str() ) )
|
2013-04-23 12:07:05 +00:00
|
|
|
continue;
|
|
|
|
|
2022-06-06 12:16:13 +00:00
|
|
|
string title = parser.title().toStdString();
|
2013-05-08 13:39:47 +00:00
|
|
|
initializing.indexingDictionary( title );
|
|
|
|
|
|
|
|
for ( vector< string >::const_iterator mddIter = dictFiles.begin() + 1; mddIter != dictFiles.end(); ++mddIter ) {
|
|
|
|
if ( File::exists( *mddIter ) ) {
|
2022-11-29 03:54:31 +00:00
|
|
|
sptr< MdictParser > mddParser = std::make_shared< MdictParser >();
|
2013-05-11 05:41:26 +00:00
|
|
|
if ( !mddParser->open( mddIter->c_str() ) ) {
|
2017-06-22 15:02:04 +00:00
|
|
|
gdWarning( "Broken mdd (resource) file: %s\n", mddIter->c_str() );
|
2013-05-08 13:39:47 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
mddParsers.push_back( mddParser );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
File::Class idx( indexFile, "wb" );
|
|
|
|
IdxHeader idxHeader;
|
|
|
|
memset( &idxHeader, 0, sizeof( idxHeader ) );
|
|
|
|
// We write a dummy header first. At the end of the process the header
|
|
|
|
// will be rewritten with the right values.
|
|
|
|
idx.write( idxHeader );
|
2013-04-28 08:26:04 +00:00
|
|
|
|
|
|
|
// Write the title first
|
2013-04-23 12:07:05 +00:00
|
|
|
idx.write< uint32_t >( title.size() );
|
|
|
|
idx.write( title.data(), title.size() );
|
|
|
|
|
2013-04-28 08:26:04 +00:00
|
|
|
// then the encoding
|
|
|
|
{
|
2022-06-06 12:16:13 +00:00
|
|
|
string encoding = parser.encoding().toStdString();
|
2013-04-28 08:26:04 +00:00
|
|
|
idx.write< uint32_t >( encoding.size() );
|
|
|
|
idx.write( encoding.data(), encoding.size() );
|
|
|
|
}
|
|
|
|
|
2013-04-23 12:07:05 +00:00
|
|
|
// This is our index data that we accumulate during the loading process.
|
|
|
|
// For each new word encountered, we emit the article's body to the file
|
|
|
|
// immediately, inserting the word itself and its offset in this map.
|
|
|
|
// This map maps folded words to the original words and the corresponding
|
|
|
|
// articles' offsets.
|
|
|
|
IndexedWords indexedWords;
|
|
|
|
ChunkedStorage::Writer chunks( idx );
|
|
|
|
|
|
|
|
idxHeader.isRightToLeft = parser.isRightToLeft();
|
|
|
|
|
|
|
|
// Save dictionary description if there's one
|
|
|
|
{
|
2022-06-06 12:16:13 +00:00
|
|
|
string description = parser.description().toStdString();
|
2013-04-23 12:07:05 +00:00
|
|
|
idxHeader.descriptionAddress = chunks.startNewBlock();
|
|
|
|
chunks.addToBlock( description.c_str(), description.size() + 1 );
|
2013-04-28 08:26:04 +00:00
|
|
|
idxHeader.descriptionSize = description.size() + 1;
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ArticleHandler articleHandler( chunks, indexedWords );
|
|
|
|
MdictParser::HeadWordIndex headWordIndex;
|
|
|
|
|
|
|
|
// enumerating word and its definition
|
2022-08-07 03:03:46 +00:00
|
|
|
while ( parser.readNextHeadWordIndex( headWordIndex ) ) {
|
|
|
|
parser.readRecordBlock( headWordIndex, articleHandler );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// enumerating resources if there's any
|
2013-05-08 13:39:47 +00:00
|
|
|
vector< sptr< IndexedWords > > mddIndices;
|
|
|
|
vector< string > mddFileNames;
|
|
|
|
while ( !mddParsers.empty() ) {
|
|
|
|
sptr< MdictParser > mddParser = mddParsers.front();
|
2022-11-29 03:54:31 +00:00
|
|
|
sptr< IndexedWords > mddIndexedWords = std::make_shared< IndexedWords >();
|
2018-04-08 14:57:10 +00:00
|
|
|
MdictParser::HeadWordIndex resourcesIndex;
|
2013-04-23 12:07:05 +00:00
|
|
|
ResourceHandler resourceHandler( chunks, *mddIndexedWords );
|
|
|
|
|
|
|
|
while ( mddParser->readNextHeadWordIndex( headWordIndex ) ) {
|
2018-04-08 14:57:10 +00:00
|
|
|
resourcesIndex.insert( resourcesIndex.end(), headWordIndex.begin(), headWordIndex.end() );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
2018-04-08 14:57:10 +00:00
|
|
|
mddParser->readRecordBlock( resourcesIndex, resourceHandler );
|
2013-05-08 13:39:47 +00:00
|
|
|
|
|
|
|
mddIndices.push_back( mddIndexedWords );
|
2013-06-04 16:17:55 +00:00
|
|
|
// Save filename for .mdd files only
|
|
|
|
QFileInfo fi( mddParser->filename() );
|
2022-06-06 12:16:13 +00:00
|
|
|
mddFileNames.push_back( fi.fileName().toStdString() );
|
2013-05-08 13:39:47 +00:00
|
|
|
mddParsers.pop_front();
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Finish with the chunks
|
|
|
|
idxHeader.chunksOffset = chunks.finish();
|
|
|
|
|
2014-05-10 21:02:31 +00:00
|
|
|
GD_DPRINTF( "Writing index...\n" );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
// Good. Now build the index
|
|
|
|
IndexInfo idxInfo = BtreeIndexing::buildIndex( indexedWords, idx );
|
|
|
|
idxHeader.indexBtreeMaxElements = idxInfo.btreeMaxElements;
|
|
|
|
idxHeader.indexRootOffset = idxInfo.rootOffset;
|
|
|
|
|
|
|
|
// Save dictionary stylesheets
|
|
|
|
{
|
|
|
|
MdictParser::StyleSheets const & styleSheets = parser.styleSheets();
|
2023-05-28 16:01:21 +00:00
|
|
|
idxHeader.styleSheetAddress = idx.tell();
|
|
|
|
idxHeader.styleSheetCount = styleSheets.size();
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2023-05-28 16:01:21 +00:00
|
|
|
for ( auto const & [ key, value ] : styleSheets ) {
|
|
|
|
string const styleBegin( value.first.toStdString() );
|
|
|
|
string const styleEnd( value.second.toStdString() );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
// key
|
2023-05-28 16:01:21 +00:00
|
|
|
idx.write< qint32 >( key );
|
2013-04-23 12:07:05 +00:00
|
|
|
// styleBegin
|
2023-05-28 16:01:21 +00:00
|
|
|
idx.write< quint32 >( (quint32)styleBegin.size() + 1 );
|
2013-04-23 12:07:05 +00:00
|
|
|
idx.write( styleBegin.c_str(), styleBegin.size() + 1 );
|
|
|
|
// styleEnd
|
2023-05-28 16:01:21 +00:00
|
|
|
idx.write< quint32 >( (quint32)styleEnd.size() + 1 );
|
2013-04-23 12:07:05 +00:00
|
|
|
idx.write( styleEnd.c_str(), styleEnd.size() + 1 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// read languages
|
2023-05-28 16:01:21 +00:00
|
|
|
QPair< quint32, quint32 > langs = LangCoder::findIdsForFilename( QString::fromStdString( fileName ) );
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
// if no languages found, try dictionary's name
|
2023-05-28 16:01:21 +00:00
|
|
|
if ( langs.first == 0 || langs.second == 0 ) {
|
2013-04-23 12:07:05 +00:00
|
|
|
langs = LangCoder::findIdsForFilename( parser.title() );
|
|
|
|
}
|
|
|
|
|
|
|
|
idxHeader.langFrom = langs.first;
|
2023-05-28 16:01:21 +00:00
|
|
|
idxHeader.langTo = langs.second;
|
2013-04-23 12:07:05 +00:00
|
|
|
|
2013-05-08 13:39:47 +00:00
|
|
|
// Build index info for each mdd file
|
|
|
|
vector< IndexInfo > mddIndexInfos;
|
2023-05-28 16:01:21 +00:00
|
|
|
for ( const auto & mddIndice : mddIndices ) {
|
|
|
|
IndexInfo const resourceIdxInfo = BtreeIndexing::buildIndex( *mddIndice, idx );
|
2013-05-08 13:39:47 +00:00
|
|
|
mddIndexInfos.push_back( resourceIdxInfo );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save address of IndexInfos for resource files
|
|
|
|
idxHeader.mddIndexInfosOffset = idx.tell();
|
2023-05-28 16:01:21 +00:00
|
|
|
idxHeader.mddIndexInfosCount = mddIndexInfos.size();
|
|
|
|
for ( uint32_t mi = 0; mi < mddIndexInfos.size(); mi++ ) {
|
2013-05-08 13:39:47 +00:00
|
|
|
const string & mddfile = mddFileNames[ mi ];
|
|
|
|
|
2013-06-04 16:17:55 +00:00
|
|
|
idx.write< quint32 >( (quint32)mddfile.size() + 1 );
|
2013-05-08 13:39:47 +00:00
|
|
|
idx.write( mddfile.c_str(), mddfile.size() + 1 );
|
|
|
|
idx.write< uint32_t >( mddIndexInfos[ mi ].btreeMaxElements );
|
|
|
|
idx.write< uint32_t >( mddIndexInfos[ mi ].rootOffset );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// That concludes it. Update the header.
|
|
|
|
idxHeader.signature = kSignature;
|
|
|
|
idxHeader.formatVersion = kCurrentFormatVersion;
|
|
|
|
idxHeader.parserVersion = MdictParser::kParserVersion;
|
|
|
|
idxHeader.foldingVersion = Folding::Version;
|
2023-02-05 05:45:48 +00:00
|
|
|
idxHeader.articleCount = parser.wordCount();
|
|
|
|
idxHeader.wordCount = parser.wordCount();
|
2013-04-23 12:07:05 +00:00
|
|
|
|
|
|
|
idx.rewind();
|
|
|
|
idx.write( &idxHeader, sizeof( idxHeader ) );
|
|
|
|
}
|
|
|
|
|
2022-11-29 03:54:31 +00:00
|
|
|
dictionaries.push_back( std::make_shared< MdxDictionary >( dictId, indexFile, dictFiles ) );
|
2013-04-23 12:07:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return dictionaries;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Mdx
|