From 1ba71443f51053e7615b1777f66a3b257cb65fbf Mon Sep 17 00:00:00 2001 From: Simon Rozman Date: Fri, 14 Nov 2025 08:18:36 +0100 Subject: [PATCH] Add support for Unicode character blocks Signed-off-by: Simon Rozman --- ZRCola/zrcolaapp.cpp | 6 + ZRCola/zrcolaapp.h | 3 +- ZRColaCompile/dbsource.cpp | 3072 +++++++++++----------- ZRColaCompile/dbsource.h | 2235 ++++++++-------- ZRColaCompile/main.cpp | 96 +- lib/libZRCola/include/zrcola/character.h | 34 + lib/libZRCola/include/zrcola/idrec.h | 1 + output/data/ZRCola.zrcdb | Bin 3847410 -> 3864846 bytes output/locale/ZRCola-zrcdb.pot | 138 + 9 files changed, 2974 insertions(+), 2611 deletions(-) diff --git a/ZRCola/zrcolaapp.cpp b/ZRCola/zrcolaapp.cpp index 0083546..22343c1 100644 --- a/ZRCola/zrcolaapp.cpp +++ b/ZRCola/zrcolaapp.cpp @@ -132,6 +132,12 @@ bool ZRColaApp::OnInit() wxFAIL_MSG(wxT("Error reading character category data from ZRCola.zrcdb.")); m_cc_db.clear(); } + } else if (id == ZRCola::chrblk_rec::id()) { + dat >> ZRCola::chrblk_rec(m_cb_db); + if (!dat.good()) { + wxFAIL_MSG(wxT("Error reading character block data from ZRCola.zrcdb.")); + m_cb_db.clear(); + } } else if (id == ZRCola::chrtag_rec::id()) { dat >> ZRCola::chrtag_rec(m_ct_db); if (!dat.good()) { diff --git a/ZRCola/zrcolaapp.h b/ZRCola/zrcolaapp.h index 4adc1ce..5b32128 100644 --- a/ZRCola/zrcolaapp.h +++ b/ZRCola/zrcolaapp.h @@ -70,7 +70,8 @@ public: ZRCola::language_db m_lang_db; ///< Language database ZRCola::keyseq_db m_ks_db; ///< Key sequence database ZRCola::character_db m_chr_db; ///< Character database - ZRCola::chrcat_db m_cc_db; ///< Characted category database + ZRCola::chrcat_db m_cc_db; ///< Character category database + ZRCola::chrblk_db m_cb_db; ///< Character block database ZRCola::chrtag_db m_ct_db; ///< Character tag database ZRCola::tagname_db m_tn_db; ///< Tag name database ZRCola::highlight_db m_h_db; ///< Highlight database diff --git a/ZRColaCompile/dbsource.cpp b/ZRColaCompile/dbsource.cpp index 41e56de..f8eece1 100644 --- a/ZRColaCompile/dbsource.cpp +++ b/ZRColaCompile/dbsource.cpp @@ -1,1498 +1,1574 @@ -/* - SPDX-License-Identifier: GPL-3.0-or-later - Copyright © 2015-2022 Amebis -*/ - -#include "pch.h" - -using namespace std; -using namespace stdex; -using namespace winstd; - - -////////////////////////////////////////////////////////////////////////// -// ZRCola::DBSource::character_bank -////////////////////////////////////////////////////////////////////////// - -void ZRCola::DBSource::character_bank::build_related() -{ - SYSTEM_INFO si; - GetSystemInfo(&si); - - // Launch workers. - build_related_worker **workers = new build_related_worker*[si.dwNumberOfProcessors]; - size_type from = 0, total = size(); - iterator chr_from = begin(), chr_to; - for (DWORD i = 0; i < si.dwNumberOfProcessors; i++, chr_from = chr_to) { - size_type to = MulDiv(i + 1, total, si.dwNumberOfProcessors); - for (chr_to = chr_from; from < to; from++, ++chr_to); - workers[i] = new build_related_worker(this, chr_from, chr_to); - } - - // Wait for workers. - for (DWORD i = 0; i < si.dwNumberOfProcessors; i++) { - if (workers[i]) { - workers[i]->join(); - delete workers[i]; - } - } - - delete [] workers; // This line of code sounds horrible, I know. -} - - -ZRCola::DBSource::character_bank::build_related_worker::build_related_worker(_In_ const character_bank *cb, _In_ iterator from, _In_ iterator to) : - winstd::thread((HANDLE)_beginthreadex(NULL, 0, process, this, CREATE_SUSPENDED, NULL)), - m_heap(HeapCreate(0, 0, 0)), - m_cb(cb), - m_from(from), - m_to(to) -{ - // Now that members of this class are surely initialized, proceed. - ResumeThread(m_h); -} - - -unsigned int ZRCola::DBSource::character_bank::build_related_worker::process() -{ - heap_allocator al(m_heap); - vector > rel(al); - set, heap_allocator > matching(less(), al); - - for (auto c = m_from; c != m_to; c++) { - rel.clear(); - - // Skip all unexisting, or self related characters. - auto m_cb_end = m_cb->cend(); - for (std::vector::const_pointer c_rel = c->second.rel.data(), c_rel_end = c_rel + c->second.rel.size(), c_rel_next = c_rel_end; c_rel < c_rel_end; c_rel = c_rel_next) { - c_rel_next = c_rel + wcsnlen(c_rel, c_rel_end - c_rel) + 1; - if (m_cb->find(c_rel) != m_cb_end && c->first.compare(c_rel) != 0) - rel.insert(rel.end(), c_rel, c_rel_next); - } - - // Add all characters that share enough keywords. - for (auto c2 = m_cb->cbegin(), c2_end = m_cb->cend(); c2 != c2_end; ++c2) { - if (c == c2) - continue; - bool already_present = false; - for (std::vector::const_pointer c_rel = rel.data(), c_rel_end = c_rel + rel.size(), c_rel_next = c_rel_end; c_rel < c_rel_end; c_rel = c_rel_next) { - c_rel_next = c_rel + wcsnlen(c_rel, c_rel_end - c_rel) + 1; - if (c2->first.compare(c_rel) == 0) { - already_present = true; - break; - } - } - if (already_present) - continue; - - set::size_type comparisons = 0; - matching.clear(); - for (auto term = c->second.terms_rel.cbegin(), term_end = c->second.terms_rel.cend(); term != term_end; ++term) { - for (auto term2 = c2->second.terms_rel.cbegin(), term2_end = c2->second.terms_rel.cend(); term2 != term2_end; ++term2) { - comparisons++; - if (*term == *term2) - matching.insert(*term); - } - } - - if (comparisons) { - // If 1/2 terms match, assume related. - auto hits = matching.size(); - if (hits*hits*2 >= comparisons) - rel.insert(rel.end(), c2->first.data(), c2->first.data() + c2->first.length() + 1); - } - } - - c->second.rel.assign(rel.cbegin(), rel.cend()); - } - - return 0; -} - - -unsigned int __stdcall ZRCola::DBSource::character_bank::build_related_worker::process(_In_ void *param) -{ - return ((ZRCola::DBSource::character_bank::build_related_worker*)param)->process(); -} - - -////////////////////////////////////////////////////////////////////////// -// ZRCola::DBSource::character_desc_idx -////////////////////////////////////////////////////////////////////////// - -void ZRCola::DBSource::character_desc_idx::parse_keywords(_In_ const wchar_t *str, _Inout_ set &terms) -{ - wxASSERT_MSG(str, wxT("string is NULL")); - - while (*str) { - // Skip white space. - for (;;) { - if (*str == 0) - return; - else if (!iswspace(*str)) - break; - else - str++; - } - - // Get term. - wstring term; - if (*str == L'"') { - const wchar_t *str_end = ++str; - for (;;) { - if (*str_end == 0) { - term.assign(str, str_end); - break; - } else if (*str_end == L'"') { - term.assign(str, str_end); - str_end++; - break; - } else - str_end++; - } - str = str_end; - } else { - const wchar_t *str_end = str + 1; - for (; *str_end && !iswspace(*str_end); str_end++); - term.assign(str, str_end); - str = str_end; - } - - if (!term.empty()) { - transform(term.begin(), term.end(), term.begin(), towlower); - terms.insert(term); - } - } -} - - -void ZRCola::DBSource::character_desc_idx::add_keywords(const set &terms, const wstring &chr, size_t sub) -{ - for (auto term = terms.cbegin(), term_end = terms.cend(); term != term_end; ++term) { - if (sub) { - wstring::size_type j_end = term->size(); - if (j_end >= sub) { - // Insert all keyword substrings "sub" or more characters long. - for (wstring::size_type i = 0, i_end = j_end - sub; i <= i_end; ++i) { - for (wstring::size_type j = i + sub; j <= j_end; ++j) - add_keyword(term->substr(i, j - i), chr); - } - } - } else { - // Insert exact keyword only. - add_keyword(*term, chr); - } - } -} - - -void ZRCola::DBSource::character_desc_idx::save(ZRCola::textindex &idx) const -{ - idx .clear(); - idx.keys .clear(); - idx.values.clear(); - - // Pre-allocate memory. - vector::size_type size_keys = 0; - vector::size_type size_values = 0; - for (const_iterator i = cbegin(), i_end = cend(); i != i_end; ++i) { - size_keys += i->first.size(); - size_values += i->second.size(); - } - idx .reserve(size() ); - idx.keys .reserve(size_keys ); - idx.values.reserve(size_values); - - // Convert the index. - for (const_iterator i = cbegin(), i_end = cend(); i != i_end; ++i) { - ZRCola::mappair_t p = { idx.keys.size(), idx.values.size() }; - idx.push_back(p); - idx.keys.insert(idx.keys.end(), i->first.cbegin(), i->first.cend()); - idx.values.insert(idx.values.end(), i->second.cbegin(), i->second.cend()); - } -} - - -////////////////////////////////////////////////////////////////////////// -// ZRCola::DBSource -////////////////////////////////////////////////////////////////////////// - -ZRCola::DBSource::DBSource() : - m_locale(nullptr) -{ - // Initialize ignore list. - m_terms_ignore.insert(L"letter"); - m_terms_ignore.insert(L"modifier"); - m_terms_ignore.insert(L"symbol"); - m_terms_ignore.insert(L"accent"); - m_terms_ignore.insert(L"with"); - m_terms_ignore.insert(L"and"); - m_terms_ignore.insert(L"capital"); - m_terms_ignore.insert(L"small"); - m_terms_ignore.insert(L"combining"); -} - - -ZRCola::DBSource::~DBSource() -{ - // Manually release all COM objects related to the database before we close the database. - m_pTranslationSets1.free(); - m_comTranslationSets.free(); - m_pTranslation1.free(); - m_comTranslation.free(); - m_pCharacterGroup1.free(); - m_comCharacterGroup.free(); - m_pHighlight1.free(); - m_comHighlight.free(); - - if (m_db.valid()) - m_db->Close(); - - if (m_locale) - _free_locale(m_locale); -} - - -bool ZRCola::DBSource::Open(LPCTSTR filename) -{ - wxASSERT_MSG(!m_db.valid(), wxT("database already open")); - - // Create COM object. - HRESULT hr = ::CoCreateInstance(CLSID_CADOConnection, NULL, CLSCTX_ALL, IID_IADOConnection, (LPVOID*)&m_db); - if (SUCCEEDED(hr)) { - // Open the database. - wstring cn; - cn = L"Driver={Microsoft Access Driver (*.mdb)};"; - cn += L"Dbq="; - cn += filename; - cn += L";Uid=;Pwd=;"; -#pragma warning(push) -#pragma warning(disable: 6387) // Connection15::Open() declaration is wrong: it defaults username and password parameters to NULL, but annotates them as required non-NULL. - hr = m_db->Open(bstr(cn.c_str())); -#pragma warning(pop) - if (SUCCEEDED(hr)) { - // Database open and ready. - m_filename = filename; - m_locale = _create_locale(LC_ALL, "Slovenian_Slovenia.1250"); - - // Create ADO command(s). - wxASSERT_MSG(!m_comCharacterGroup.valid(), wxT("ADO command already created")); - wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADOCommand, NULL, CLSCTX_ALL, IID_IADOCommand, (LPVOID*)&m_comCharacterGroup))); - wxVERIFY(SUCCEEDED(m_comCharacterGroup->put_ActiveConnection(variant((IDispatch*)m_db)))); - wxVERIFY(SUCCEEDED(m_comCharacterGroup->put_CommandType(adCmdText))); - wxVERIFY(SUCCEEDED(m_comCharacterGroup->put_CommandText(bstr(L"SELECT [VRS_SkupineZnakov].[Znak], [VRS_SkupineZnakov].[pogost] " - L"FROM [VRS_SkupineZnakov] " - L"LEFT JOIN [VRS_CharList] ON [VRS_SkupineZnakov].[Znak]=[VRS_CharList].[znak] " - L"WHERE [VRS_CharList].[aktiven]=1 AND [VRS_SkupineZnakov].[Skupina]=? " - L"ORDER BY [VRS_SkupineZnakov].[Rang] ASC, [VRS_SkupineZnakov].[Znak] ASC")))); - { - // Create and add command parameters. - com_obj params; - wxVERIFY(SUCCEEDED(m_comCharacterGroup->get_Parameters(¶ms))); - wxASSERT_MSG(!m_pCharacterGroup1.valid(), wxT("ADO command parameter already created")); - wxVERIFY(SUCCEEDED(m_comCharacterGroup->CreateParameter(bstr(L"@Skupina"), adVarWChar, adParamInput, 50, variant(DISP_E_PARAMNOTFOUND, VT_ERROR), &m_pCharacterGroup1))); - wxVERIFY(SUCCEEDED(params->Append(m_pCharacterGroup1))); - } - - wxASSERT_MSG(!m_comTranslation.valid(), wxT("ADO command already created")); - wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADOCommand, NULL, CLSCTX_ALL, IID_IADOCommand, (LPVOID*)&m_comTranslation))); - wxVERIFY(SUCCEEDED(m_comTranslation->put_ActiveConnection(variant((IDispatch*)m_db)))); - wxVERIFY(SUCCEEDED(m_comTranslation->put_CommandType(adCmdText))); - wxVERIFY(SUCCEEDED(m_comTranslation->put_CommandText(bstr(L"SELECT [Komb1] AS [komb], [rang_komb1] AS [rang_komb], '' AS [Kano], 0 AS [Kanoniziraj], [Komb2] AS [znak], [rang_komb2] AS [rang_znak] " - L"FROM [VRS_ScriptRepl2] " - L"WHERE [Script]=? " - L"ORDER BY [Komb2], [rang_komb2], [rang_komb1], [Komb1]")))); - { - // Create and add command parameters. - com_obj params; - wxVERIFY(SUCCEEDED(m_comTranslation->get_Parameters(¶ms))); - wxASSERT_MSG(!m_pTranslation1.valid(), wxT("ADO command parameter already created")); - wxVERIFY(SUCCEEDED(m_comTranslation->CreateParameter(bstr(L"@Script"), adSmallInt, adParamInput, 0, variant(DISP_E_PARAMNOTFOUND, VT_ERROR), &m_pTranslation1))); - wxVERIFY(SUCCEEDED(params->Append(m_pTranslation1))); - } - - wxASSERT_MSG(!m_comTranslationSets.valid(), wxT("ADO command already created")); - wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADOCommand, NULL, CLSCTX_ALL, IID_IADOCommand, (LPVOID*)&m_comTranslationSets))); - wxVERIFY(SUCCEEDED(m_comTranslationSets->put_ActiveConnection(variant((IDispatch*)m_db)))); - wxVERIFY(SUCCEEDED(m_comTranslationSets->put_CommandType(adCmdText))); - wxVERIFY(SUCCEEDED(m_comTranslationSets->put_CommandText(bstr(L"SELECT [Script] " - L"FROM [VRS_Script2SeqScr] " - L"WHERE [ID]=? " - L"ORDER BY [Rank] ASC")))); - { - // Create and add command parameters. - com_obj params; - wxVERIFY(SUCCEEDED(m_comTranslationSets->get_Parameters(¶ms))); - wxASSERT_MSG(!m_pTranslationSets1.valid(), wxT("ADO command parameter already created")); - wxVERIFY(SUCCEEDED(m_comTranslationSets->CreateParameter(bstr(L"@ID"), adSmallInt, adParamInput, 0, variant(DISP_E_PARAMNOTFOUND, VT_ERROR), &m_pTranslationSets1))); - wxVERIFY(SUCCEEDED(params->Append(m_pTranslationSets1))); - } - - wxASSERT_MSG(!m_comHighlight.valid(), wxT("ADO command already created")); - wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADOCommand, NULL, CLSCTX_ALL, IID_IADOCommand, (LPVOID*)&m_comHighlight))); - wxVERIFY(SUCCEEDED(m_comHighlight->put_ActiveConnection(variant((IDispatch*)m_db)))); - wxVERIFY(SUCCEEDED(m_comHighlight->put_CommandType(adCmdText))); - wxVERIFY(SUCCEEDED(m_comHighlight->put_CommandText(bstr(L"SELECT [komb] " - L"FROM [VRS_HighlightChars2] " - L"WHERE [group]=? " - L"ORDER BY [komb]")))); - { - // Create and add command parameters. - com_obj params; - wxVERIFY(SUCCEEDED(m_comHighlight->get_Parameters(¶ms))); - wxASSERT_MSG(!m_pHighlight1.valid(), wxT("ADO command parameter already created")); - wxVERIFY(SUCCEEDED(m_comHighlight->CreateParameter(bstr(L"@group"), adSmallInt, adParamInput, 0, variant(DISP_E_PARAMNOTFOUND, VT_ERROR), &m_pHighlight1))); - wxVERIFY(SUCCEEDED(params->Append(m_pHighlight1))); - } - - return true; - } else { - _ftprintf(stderr, wxT("%s: error ZCC0011: Could not open database (0x%x).\n"), (LPCTSTR)filename, hr); - LogErrors(); - } - m_db.free(); - } else - _ftprintf(stderr, wxT("%s: error ZCC0012: Creating ADOConnection object failed (0x%x).\n"), (LPCTSTR)filename, hr); - - return false; -} - - -void ZRCola::DBSource::LogErrors() const -{ - wxASSERT_MSG(m_db.valid(), wxT("database does not exist")); - - // Get array of errors. - ADOErrors *errors = NULL; - if (SUCCEEDED(m_db->get_Errors(&errors))) { - // Get number of errors. - long n = 0; - wxVERIFY(SUCCEEDED(errors->get_Count(&n))); - - // Iterate the errors. - for (long i = 0; i < n; i++) { - ADOError *err = NULL; - if (SUCCEEDED(errors->get_Item(variant(i), &err))) { - // Write error number and description to the log. - long num = 0; - wxVERIFY(SUCCEEDED(err->get_Number(&num))); - - bstr desc; - wxVERIFY(SUCCEEDED(err->get_Description(&desc))); - - _ftprintf(stderr, wxT(" error ADO%x: %ls\n"), num, (BSTR)desc); - - err->Release(); - } - } - - errors->Release(); - } -} - - -bool ZRCola::DBSource::GetValue(const com_obj& f, bool& val) const -{ - wxASSERT_MSG(f.valid(), wxT("field is empty")); - - variant v; - wxVERIFY(SUCCEEDED(f->get_Value(&v))); - wxCHECK(SUCCEEDED(v.change_type(VT_BOOL)), false); - - val = V_BOOL(&v) ? true : false; - - return true; -} - - -bool ZRCola::DBSource::GetValue(const com_obj& f, short& val) const -{ - wxASSERT_MSG(f.valid(), wxT("field is empty")); - - variant v; - wxVERIFY(SUCCEEDED(f->get_Value(&v))); - wxCHECK(SUCCEEDED(v.change_type(VT_I2)), false); - - val = V_I2(&v); - - return true; -} - - -bool ZRCola::DBSource::GetValue(const com_obj& f, string& val) const -{ - wxASSERT_MSG(f.valid(), wxT("field is empty")); - - variant v; - wxVERIFY(SUCCEEDED(f->get_Value(&v))); - if (V_VT(&v) != VT_NULL) { - wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); - - WideCharToMultiByte(CP_ACP, 0, V_BSTR(&v), ::SysStringLen(V_BSTR(&v)), val, NULL, NULL); - } else - val.clear(); - - return true; -} - - -bool ZRCola::DBSource::GetValue(const com_obj& f, wstring& val) const -{ - wxASSERT_MSG(f.valid(), wxT("field is empty")); - - variant v; - wxVERIFY(SUCCEEDED(f->get_Value(&v))); - if (V_VT(&v) != VT_NULL) { - wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); - - val.reserve(::SysStringLen(V_BSTR(&v))); - val = V_BSTR(&v); - } else - val.clear(); - - return true; -} - - -bool ZRCola::DBSource::GetUnicodeCharacter(const com_obj& f, wchar_t& chr) const -{ - wxASSERT_MSG(f.valid(), wxT("field is empty")); - - variant v; - wxVERIFY(SUCCEEDED(f->get_Value(&v))); - if (V_VT(&v) != VT_NULL) { - wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); - - // Parse the field. Must be exactly one Unicode code. - UINT i = 0, n = ::SysStringLen(V_BSTR(&v)); - chr = 0; - for (; i < n && V_BSTR(&v)[i]; i++) { - if (L'0' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'9') chr = chr*0x10 + (V_BSTR(&v)[i] - L'0'); - else if (L'A' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'F') chr = chr*0x10 + (V_BSTR(&v)[i] - L'A' + 10); - else if (L'a' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'f') chr = chr*0x10 + (V_BSTR(&v)[i] - L'a' + 10); - else break; - } - if (i <= 0 && 4 < i) { - bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); - _ftprintf(stderr, wxT("%s: error ZCC0030: Syntax error in \"%.*ls\" field (\"%.*ls\"). Unicode code must be one to four hexadecimal characters long.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); - return false; - } else if (i != n) { - bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); - _ftprintf(stderr, wxT("%s: error ZCC0031: Syntax error in \"%.*ls\" field (\"%.*ls\"). Extra trailing characters.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); - return false; - } - } else - chr = 0; - - return true; -} - - -bool ZRCola::DBSource::GetUnicodeString(const com_obj& f, wstring& str) const -{ - wxASSERT_MSG(f.valid(), wxT("field is empty")); - - variant v; - wxVERIFY(SUCCEEDED(f->get_Value(&v))); - str.clear(); - if (V_VT(&v) != VT_NULL) { - wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); - - // Parse the field. Must be "xxxx+xxxx+xxxx..." sequence. - for (UINT i = 0, n = ::SysStringLen(V_BSTR(&v)); i < n && V_BSTR(&v)[i];) { - // Parse Unicode code. - UINT j = 0; - wchar_t c = 0; - for (; i < n && V_BSTR(&v)[i]; i++, j++) { - if (L'0' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'9') c = c*0x10 + (V_BSTR(&v)[i] - L'0'); - else if (L'A' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'F') c = c*0x10 + (V_BSTR(&v)[i] - L'A' + 10); - else if (L'a' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'f') c = c*0x10 + (V_BSTR(&v)[i] - L'a' + 10); - else break; - } - if (j <= 0 || 4 < j) { - bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); - _ftprintf(stderr, wxT("%s: error ZCC0020: Syntax error in \"%.*ls\" field (\"%.*ls\"). Unicode code must be one to four hexadecimal characters long.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); - return false; - } - str += c; - - // Skip delimiter(s) and whitespace. - for (; i < n && V_BSTR(&v)[i] && (V_BSTR(&v)[i] == L'+' || _iswspace_l(V_BSTR(&v)[i], m_locale)); i++); - } - } - - return true; -} - - -bool ZRCola::DBSource::GetNormPerm(const winstd::com_obj& f, normperm& np) const -{ - wxASSERT_MSG(f.valid(), wxT("field is empty")); - - variant v; - wxVERIFY(SUCCEEDED(f->get_Value(&v))); - np.clear(); - if (V_VT(&v) != VT_NULL) { - wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); - - // Parse the field. Must be "nnnn,nnnn,nnnn..." sequence. - for (UINT i = 0, n = ::SysStringLen(V_BSTR(&v)); i < n && V_BSTR(&v)[i];) { - // Parse Unicode code. - UINT j = 0; - std::vector p; - for (; i < n && V_BSTR(&v)[i]; i++, j++) { - if (L'0' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'9') p.push_back(V_BSTR(&v)[i] - L'0'); - else break; - } - if (j <= 0) { - bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); - _ftprintf(stderr, wxT("%s: error ZCC0150: Syntax error in \"%.*ls\" field (\"%.*ls\"). Permutation sequence must be at least one decimal digit long.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); - return false; - } - np.insert(std::move(p)); - - // Skip delimiter(s) and whitespace. - for (; i < n && V_BSTR(&v)[i] && (V_BSTR(&v)[i] == L',' || _iswspace_l(V_BSTR(&v)[i], m_locale)); i++); - } - } - - return true; -} - - - -bool ZRCola::DBSource::GetLanguage(const com_obj& f, ZRCola::langid_t& lang) const -{ - wxASSERT_MSG(f.valid(), wxT("field is empty")); - - variant v; - wxVERIFY(SUCCEEDED(f->get_Value(&v))); - wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); - - // Convert to lowercase. - _wcslwr_l(V_BSTR(&v), m_locale); - - // Parse the field. - size_t n = wcsnlen(V_BSTR(&v), ::SysStringLen(V_BSTR(&v))); - if (n != 3) { - bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); - _ftprintf(stderr, wxT("%s: error ZCC0080: Syntax error in \"%.*ls\" field (\"%.*ls\"). Language ID must be exactly three (3) characters long.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); - return false; - } - for (size_t i = 0;; i++) { - if (i < sizeof(lang)) { - if (i < n) { - wchar_t c = V_BSTR(&v)[i]; - if ((unsigned short)c > 0x7f) { - bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); - _ftprintf(stderr, wxT("%s: error ZCC0081: Syntax error in \"%.*ls\" field (\"%.*ls\"). Language ID must contain ASCII characters only.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); - return false; - } - lang.data[i] = (char)c; - } else - lang.data[i] = 0; - } else - break; - } - - return true; -} - - -bool ZRCola::DBSource::GetChrCat(const com_obj& f, chrcatid_t& cc) const -{ - wxASSERT_MSG(f.valid(), wxT("field is empty")); - - variant v; - wxVERIFY(SUCCEEDED(f->get_Value(&v))); - if (V_VT(&v) != VT_NULL) { - wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); - - // Parse the field. - size_t n = wcsnlen(V_BSTR(&v), ::SysStringLen(V_BSTR(&v))); - if (n < 1 || 2 < n) { - bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); - _ftprintf(stderr, wxT("%s: error ZCC0110: Syntax error in \"%.*ls\" field (\"%.*ls\"). Character category ID must be one (1) or two (2) characters long.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); - return false; - } - for (size_t i = 0;; i++) { - if (i < sizeof(cc)) { - if (i < n) { - wchar_t c = V_BSTR(&v)[i]; - if ((unsigned short)c > 0x7f) { - bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); - _ftprintf(stderr, wxT("%s: error ZCC0111: Syntax error in \"%.*ls\" field (\"%.*ls\"). Character category ID must contain ASCII characters only.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); - return false; - } - cc.data[i] = (char)c; - } else - cc.data[i] = 0; - } else - break; - } - } else - memset(cc.data, 0, sizeof(cc)); - - return true; -} - - -bool ZRCola::DBSource::GetTagNames(const winstd::com_obj& f, LCID lcid, list& names) const -{ - wxASSERT_MSG(f.valid(), wxT("field is empty")); - - variant v; - wxVERIFY(SUCCEEDED(f->get_Value(&v))); - wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); - - // Parse the field. Must be "name, name, name..." sequence. - names.clear(); - for (UINT i = 0, i_end = ::SysStringLen(V_BSTR(&v)); i < i_end && V_BSTR(&v)[i];) { - if (iswspace(V_BSTR(&v)[i])) { - // Skip leading white space. - i++; continue; - } - - // Parse name. - UINT j = i, j_end = i; - for (; i < i_end && V_BSTR(&v)[i]; i++) { - if (V_BSTR(&v)[i] == L',' || V_BSTR(&v)[i] == L';') { - // Delimiter found. - i++; break; - } else if (!iswspace(V_BSTR(&v)[i])) { - // Remember last non-white space character. - j_end = i + 1; - } - } - wstring name(V_BSTR(&v) + j, V_BSTR(&v) + j_end); - for (auto n = names.cbegin(), n_end = names.cend(); ; ++n) { - if (n == n_end) { - // Add name to the list. - names.push_back(std::move(name)); - break; - } else if (ZRCola::tagname_db::tagname::CompareName(lcid, n->data(), (uint16_t)n->length(), name.data(), (uint16_t)name.length()) == CSTR_EQUAL) { - // Name is already on the list. - break; - } - } - } - - return true; -} - - -bool ZRCola::DBSource::SelectNormPermSets(winstd::com_obj& rs) const -{ - // Create a new recordset. - rs.free(); - wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); - - // Open it. - if (FAILED(rs->Open(variant( - L"SELECT [oblika], [oblike] " - L"FROM [VRS_CharCanoOblike] " - L"ORDER BY [oblika], [oblike]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) - { - _ftprintf(stderr, wxT("%s: error ZCC0160: Error loading normalization permutation sets from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetNormPerm(const winstd::com_obj& rs, std::string& norm, normperm& np) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"oblika"), &f))); - wxCHECK(GetValue(f, norm), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"oblike"), &f))); - wxCHECK(GetNormPerm(f, np), false); - } - - // Verify all lengths match. - size_t n = norm.length(); - for (auto p = np.cbegin(), p_end = np.cend(); p != p_end; ++p) { - if (p->size() != n) { - _ftprintf(stderr, wxT("%s: error ZCC0170: Inconsistent normalization sequence \"%.*hs\" permutation length. Please make sure all permutation lengths match normalization sequence length (%u).\n"), m_filename.c_str(), n, norm.c_str(), n); - return false; - } - } - - return true; -} - - -bool ZRCola::DBSource::SelectTranslations(com_obj &rs) const -{ - // Create a new recordset. - rs.free(); - wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); - - // Open it. - if (FAILED(rs->Open(variant( - L"SELECT [komb], [rang_komb], [Kano], [Kanoniziraj], [znak], [rang_znak] " - L"FROM [VRS_ReplChar] " - L"WHERE [rang_komb]=1 " - L"ORDER BY [znak], [rang_znak], [rang_komb], [komb]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) - { - _ftprintf(stderr, wxT("%s: error ZCC0040: Error loading translations from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::SelectTranslations(short set, winstd::com_obj& rs) const -{ - // Create a new recordset. - rs.free(); - wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs))); - wxVERIFY(SUCCEEDED(rs->put_CursorLocation(adUseClient))); - wxVERIFY(SUCCEEDED(rs->put_CursorType(adOpenForwardOnly))); - wxVERIFY(SUCCEEDED(rs->put_LockType(adLockReadOnly))); - - // Open it. - wxVERIFY(SUCCEEDED(m_pTranslation1->put_Value(variant(set)))); - if (FAILED(rs->Open(variant((IDispatch*)m_comTranslation), variant(DISP_E_PARAMNOTFOUND, VT_ERROR)))) { - _ftprintf(stderr, wxT("%s: error ZCC0100: Error loading translations from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetTranslation(const com_obj& rs, ZRCola::DBSource::translation& t) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"komb"), &f))); - wxCHECK(GetUnicodeString(f, t.src.str), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"rang_komb"), &f))); - wxCHECK(GetValue(f, t.src.rank), false); - } - - { - bool norm; - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Kanoniziraj"), &f))); - wxCHECK(GetValue(f, norm), false); - if (norm) { - com_obj f2; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Kano"), &f2))); - wxCHECK(GetValue(f2, t.norm), false); - } else - t.norm.clear(); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"rang_znak"), &f))); - wxCHECK(GetValue(f, t.dst.rank), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak"), &f))); - wxCHECK(GetUnicodeString(f, t.dst.str), false); - } - - return true; -} - - -bool ZRCola::DBSource::SelectTranlationSets(com_obj &rs) const -{ - // Create a new recordset. - rs.free(); - wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); - - // Open it. - if (FAILED(rs->Open(variant( - L"SELECT DISTINCT [entCode], [Src_En], [Dst_En] " - L"FROM [VRS_Script2] " - L"ORDER BY [entCode], [Src_En], [Dst_En]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) - { - _ftprintf(stderr, wxT("%s: error ZCC0060: Error loading translation sets from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetTranslationSet(const com_obj& rs, ZRCola::DBSource::transet& ts) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"entCode"), &f))); - wxCHECK(GetValue(f, ts.set), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Src_En"), &f))); - wxCHECK(GetValue(f, ts.src), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Dst_En"), &f))); - wxCHECK(GetValue(f, ts.dst), false); - } - - return true; -} - - -bool ZRCola::DBSource::SelectTranlationSeqs(com_obj &rs) const -{ - // Create a new recordset. - rs.free(); - wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); - - // Open it. - if (FAILED(rs->Open(variant( - L"SELECT DISTINCT [ID], [Descr], [Rank] " - L"FROM [VRS_Script2Seq] " - L"ORDER BY [Rank], [Descr]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) - { - _ftprintf(stderr, wxT("%s: error ZCC0060: Error loading translation sequences from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetTranslationSeq(const com_obj& rs, ZRCola::DBSource::transeq& ts) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"ID"), &f))); - wxCHECK(GetValue(f, ts.seq), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Rank"), &f))); - wxCHECK(GetValue(f, ts.rank), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Descr"), &f))); - wxCHECK(GetValue(f, ts.name), false); - } - - // Read translation sequence sets from database. - wxVERIFY(SUCCEEDED(m_pTranslationSets1->put_Value(variant(ts.seq)))); - com_obj rs_chars; - wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs_chars))); - wxVERIFY(SUCCEEDED(rs_chars->put_CursorLocation(adUseClient))); - wxVERIFY(SUCCEEDED(rs_chars->put_CursorType(adOpenForwardOnly))); - wxVERIFY(SUCCEEDED(rs_chars->put_LockType(adLockReadOnly))); - if (FAILED(rs_chars->Open(variant((IDispatch*)m_comTranslationSets), variant(DISP_E_PARAMNOTFOUND, VT_ERROR)))) { - _ftprintf(stderr, wxT("%s: error ZCC0140: Error loading character group characters from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - { - ts.sets.clear(); - com_obj flds2; - wxVERIFY(SUCCEEDED(rs_chars->get_Fields(&flds2))); - com_obj f_set; - wxVERIFY(SUCCEEDED(flds2->get_Item(variant(L"Script"), &f_set))); - size_t n = 0; - for (VARIANT_BOOL eof = VARIANT_TRUE; SUCCEEDED(rs_chars->get_EOF(&eof)) && !eof; rs_chars->MoveNext(), n++) { - short set; - wxCHECK(GetValue(f_set, set), false); - ts.sets.push_back(set); - } - } - - return true; -} - - -bool ZRCola::DBSource::SelectKeySequences(com_obj &rs) const -{ - // Create a new recordset. - rs.free(); - wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); - - // Open it. - if (FAILED(rs->Open(variant( - L"SELECT DISTINCT [VRS_KeyCodes].[Znak], [VRS_CharGroup].[CharGroup], IIF([VRS_CharGroup].[Arg1] IS NOT NULL, [VRS_CharGroup].[Arg1], 0)+IIF([VRS_CharGroup].[Arg2] IS NOT NULL, [VRS_CharGroup].[Arg2], 0)+IIF([VRS_CharGroup].[Arg3] IS NOT NULL, [VRS_CharGroup].[Arg3], 0) AS [Modifiers], IIF([VRS_CharGroup].[Arg4] IS NOT NULL, [VRS_CharGroup].[Arg4], 0) AS [KeyCodePre], [VRS_KeyCodes].[KeyCode], [VRS_KeyCodes].[Shift] " - L"FROM [VRS_KeyCodes] LEFT JOIN [VRS_CharGroup] ON [VRS_CharGroup].[CharGroup]=[VRS_KeyCodes].[CharGroup] " - L"ORDER BY [VRS_CharGroup].[CharGroup], [VRS_KeyCodes].[KeyCode], [VRS_KeyCodes].[Shift], [VRS_KeyCodes].[Znak]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) - { - _ftprintf(stderr, wxT("%s: error ZCC0050: Error loading key sequences from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetKeySequence(const com_obj& rs, ZRCola::DBSource::keyseq& ks) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Znak"), &f))); - wxCHECK(GetUnicodeString(f, ks.chr), false); - } - - short modifiers; - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Modifiers"), &f))); - wxCHECK(GetValue(f, modifiers), false); - } - - short keycode1; - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"KeyCodePre"), &f))); - wxCHECK(GetValue(f, keycode1), false); - } - - short keycode; - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"KeyCode"), &f))); - wxCHECK(GetValue(f, keycode), false); - } - - bool shift; - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Shift"), &f))); - wxCHECK(GetValue(f, shift), false); - } - - ks.seq.clear(); - if (keycode1) { - // First key in the sequence is complete. - keyseq::keycode kc1 = { - keyseq::keycode::translate_slen(static_cast(keycode1)), - (modifiers & 0x100) != 0, - (modifiers & 0x200) != 0, - (modifiers & 0x400) != 0 }; - ks.seq.push_back(kc1); - - keyseq::keycode kc2 = { keyseq::keycode::translate_slen(static_cast(keycode)), shift }; - ks.seq.push_back(kc2); - } else { - // First key in the sequence is only modifier(s). - keyseq::keycode kc1 = { - keyseq::keycode::translate_slen(static_cast(keycode)), - shift || (modifiers & 0x100) != 0, - (modifiers & 0x200) != 0, - (modifiers & 0x400) != 0 }; - ks.seq.push_back(kc1); - } - - return true; -} - - -bool ZRCola::DBSource::SelectLanguages(com_obj &rs) const -{ - // Create a new recordset. - rs.free(); - wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); - - // Open it. - if (FAILED(rs->Open(variant( - L"SELECT DISTINCT [entCode], [Jezik_En] " - L"FROM [VRS_Jezik] " - L"ORDER BY [entCode], [Jezik_En]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) - { - _ftprintf(stderr, wxT("%s: error ZCC0060: Error loading languages from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetLanguage(const com_obj& rs, ZRCola::DBSource::language& lang) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"entCode"), &f))); - wxCHECK(GetLanguage(f, lang.lang), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Jezik_En"), &f))); - wxCHECK(GetValue(f, lang.name), false); - } - - return true; -} - - -bool ZRCola::DBSource::SelectLanguageCharacters(com_obj &rs) const -{ - // Create a new recordset. - rs.free(); - wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); - - // Open it. - if (FAILED(rs->Open(variant( - L"SELECT DISTINCT [znak], [lang] " - L"FROM [VRS_CharLocal] " - L"ORDER BY [znak], [lang]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) - { - _ftprintf(stderr, wxT("%s: error ZCC0090: Error loading language characters from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetLanguageCharacter(const com_obj& rs, ZRCola::DBSource::langchar& lc) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak"), &f))); - wxCHECK(GetUnicodeString(f, lc.chr), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"lang"), &f))); - wxCHECK(GetLanguage(f, lc.lang), false); - } - - return true; -} - - -bool ZRCola::DBSource::SelectCharacterGroups(com_obj& rs) const -{ - // Create a new recordset. - rs.free(); - wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); - - // Open it. - if (FAILED(rs->Open(variant( - L"SELECT DISTINCT [id], [Skupina], [opis_en], [Rang] " - L"FROM [VRS_SkupinaZnakov] " - L"ORDER BY [Rang], [opis_en]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) - { - _ftprintf(stderr, wxT("%s: error ZCC0090: Error loading character groups from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetCharacterGroup(const com_obj& rs, chrgrp& cg) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - wstring grp; - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"id"), &f))); - wxCHECK(GetValue(f, cg.grp), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Skupina"), &f))); - wxCHECK(GetValue(f, grp), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Rang"), &f))); - wxCHECK(GetValue(f, cg.rank), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_en"), &f))); - wxCHECK(GetValue(f, cg.name), false); - } - - // Read character list from database. - wxVERIFY(SUCCEEDED(m_pCharacterGroup1->put_Value(variant(grp.c_str())))); - com_obj rs_chars; - wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs_chars))); - wxVERIFY(SUCCEEDED(rs_chars->put_CursorLocation(adUseClient))); - wxVERIFY(SUCCEEDED(rs_chars->put_CursorType(adOpenForwardOnly))); - wxVERIFY(SUCCEEDED(rs_chars->put_LockType(adLockReadOnly))); - if (FAILED(rs_chars->Open(variant((IDispatch*)m_comCharacterGroup), variant(DISP_E_PARAMNOTFOUND, VT_ERROR)))) { - _ftprintf(stderr, wxT("%s: error ZCC0140: Error loading character group characters from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - { - cg.chars.clear(); - cg.show.clear(); - com_obj flds2; - wxVERIFY(SUCCEEDED(rs_chars->get_Fields(&flds2))); - com_obj f_char, f_show; - wxVERIFY(SUCCEEDED(flds2->get_Item(variant(L"Znak" ), &f_char))); - wxVERIFY(SUCCEEDED(flds2->get_Item(variant(L"pogost"), &f_show))); - size_t n = 0; - for (VARIANT_BOOL eof = VARIANT_TRUE; SUCCEEDED(rs_chars->get_EOF(&eof)) && !eof; rs_chars->MoveNext(), n++) { - wstring c; - wxCHECK(GetUnicodeString(f_char, c), false); - cg.chars.insert(cg.chars.end(), c.data(), c.data() + c.length() + 1); - bool show; - wxCHECK(GetValue(f_show, show), false); - if ((n % 16) == 0) - cg.show.push_back(show ? 1 : 0); - else if (show) - cg.show[n / 16] |= 1 << (n % 16); - } - } - - return true; -} - - -bool ZRCola::DBSource::SelectCharacters(com_obj& rs) const -{ - // Create a new recordset. - rs.free(); - wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); - - // Open it. - if (FAILED(rs->Open(variant( - L"SELECT DISTINCT [znak], [opis_en], [kat], [znak_v], [znak_m] " - L"FROM [VRS_CharList] " - L"WHERE " - L"[aktiven]=1 AND " // Active characters only - L"[kat]<>'g' " // Ignore "Other, Control" category! - L"ORDER BY [znak]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) - { - _ftprintf(stderr, wxT("%s: error ZCC0120: Error loading characters from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetCharacter(const com_obj& rs, character& chr) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - wstring c; - chr.second.terms.clear(); - chr.second.terms_rel.clear(); - chr.second.rel.clear(); - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak"), &f))); - wxCHECK(GetUnicodeString(f, chr.first), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak_v"), &f))); - wxCHECK(GetUnicodeString(f, c), false); - if (!c.empty() && c != chr.first) - chr.second.rel.insert(chr.second.rel.end(), c.data(), c.data() + c.length() + 1); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak_m"), &f))); - wxCHECK(GetUnicodeString(f, c), false); - if (!c.empty() && c != chr.first) - chr.second.rel.insert(chr.second.rel.end(), c.data(), c.data() + c.length() + 1); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_en"), &f))); - wxCHECK(GetValue(f, chr.second.desc), false); - ZRCola::DBSource::character_desc_idx::parse_keywords(chr.second.desc.c_str(), chr.second.terms); - for (auto term = chr.second.terms.cbegin(), term_end = chr.second.terms.cend(); term != term_end; ++term) { - if (m_terms_ignore.find(*term) != m_terms_ignore.cend()) - continue; - chr.second.terms_rel.insert(*term); - } - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"kat"), &f))); - wxCHECK(GetChrCat(f, chr.second.cat), false); - } - - return true; -} - - -bool ZRCola::DBSource::SelectCharacterCategories(com_obj& rs) const -{ - // Create a new recordset. - rs.free(); - wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); - - // Open it. - if (FAILED(rs->Open(variant( - L"SELECT DISTINCT [kat], [opis_en], [Rang] " - L"FROM [VRS_CharCategory] " - L"WHERE [kat]<>'g' " // Ignore "Other, Control" category! - L"ORDER BY [Rang], [opis_en]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) - { - _ftprintf(stderr, wxT("%s: error ZCC0130: Error loading character categories from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetCharacterCategory(const com_obj& rs, chrcat& cc) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"kat"), &f))); - wxCHECK(GetChrCat(f, cc.cat), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Rang"), &f))); - wxCHECK(GetValue(f, cc.rank), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_en"), &f))); - wxCHECK(GetValue(f, cc.name), false); - } - - return true; -} - - -bool ZRCola::DBSource::SelectCharacterTags(winstd::com_obj& rs) const -{ - // Create a new recordset. - rs.free(); - wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); - - // Open it. - if (FAILED(rs->Open(variant( - L"SELECT DISTINCT [znak], [oznaka] " - L"FROM [VRS_CharTags] " - L"ORDER BY [znak], [oznaka]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) - { - _ftprintf(stderr, wxT("%s: error ZCC0130: Error loading character tags from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetCharacterTag(const winstd::com_obj& rs, chrtag& ct) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak"), &f))); - wxCHECK(GetUnicodeString(f, ct.chr), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"oznaka"), &f))); - wxCHECK(GetValue(f, ct.tag), false); - } - - return true; -} - - -bool ZRCola::DBSource::SelectTagNames(winstd::com_obj& rs) const -{ - // Create a new recordset. - rs.free(); - wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); - - // Open it. - if (FAILED(rs->Open(variant( - L"SELECT DISTINCT [oznaka], [opis_en], [opis_sl], [opis_ru] " - L"FROM [VRS_Tags] " - L"ORDER BY [oznaka]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) - { - _ftprintf(stderr, wxT("%s: error ZCC0130: Error loading tags from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetTagName(const winstd::com_obj& rs, tagname& tn) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - tn.names.clear(); - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"oznaka"), &f))); - wxCHECK(GetValue(f, tn.tag), false); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_en"), &f))); - LCID lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), SORT_DEFAULT); - list names; - wxCHECK(GetTagNames(f, lcid, names), false); - tn.names.insert(std::move(pair >(lcid, std::move(names)))); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_sl"), &f))); - LCID lcid = MAKELCID(MAKELANGID(LANG_SLOVENIAN, SUBLANG_DEFAULT), SORT_DEFAULT); - list names; - wxCHECK(GetTagNames(f, lcid, names), false); - tn.names.insert(std::move(pair >(lcid, std::move(names)))); - } - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_ru"), &f))); - LCID lcid = MAKELCID(MAKELANGID(LANG_RUSSIAN, SUBLANG_DEFAULT), SORT_DEFAULT); - list names; - wxCHECK(GetTagNames(f, lcid, names), false); - tn.names.insert(std::move(pair >(lcid, std::move(names)))); - } - - return true; -} - - -bool ZRCola::DBSource::SelectHighlights(short set, winstd::com_obj& rs) const -{ - // Create a new recordset. - rs.free(); - wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs))); - wxVERIFY(SUCCEEDED(rs->put_CursorLocation(adUseClient))); - wxVERIFY(SUCCEEDED(rs->put_CursorType(adOpenForwardOnly))); - wxVERIFY(SUCCEEDED(rs->put_LockType(adLockReadOnly))); - - // Open it. - wxVERIFY(SUCCEEDED(m_pHighlight1->put_Value(variant(set)))); - if (FAILED(rs->Open(variant((IDispatch*)m_comHighlight), variant(DISP_E_PARAMNOTFOUND, VT_ERROR)))) { - _ftprintf(stderr, wxT("%s: error ZCC0101: Error loading highlights from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); - LogErrors(); - return false; - } - - return true; -} - - -bool ZRCola::DBSource::GetHighlight(const com_obj& rs, ZRCola::DBSource::highlight& h) const -{ - wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); - - com_obj flds; - wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); - - { - com_obj f; - wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"komb"), &f))); - wxCHECK(GetUnicodeString(f, h.chr), false); - } - - return true; -} +/* + SPDX-License-Identifier: GPL-3.0-or-later + Copyright © 2015-2022 Amebis +*/ + +#include "pch.h" + +using namespace std; +using namespace stdex; +using namespace winstd; + + +////////////////////////////////////////////////////////////////////////// +// ZRCola::DBSource::character_bank +////////////////////////////////////////////////////////////////////////// + +void ZRCola::DBSource::character_bank::build_related() +{ + SYSTEM_INFO si; + GetSystemInfo(&si); + + // Launch workers. + build_related_worker **workers = new build_related_worker*[si.dwNumberOfProcessors]; + size_type from = 0, total = size(); + iterator chr_from = begin(), chr_to; + for (DWORD i = 0; i < si.dwNumberOfProcessors; i++, chr_from = chr_to) { + size_type to = MulDiv(i + 1, total, si.dwNumberOfProcessors); + for (chr_to = chr_from; from < to; from++, ++chr_to); + workers[i] = new build_related_worker(*this, chr_from, chr_to); + } + + // Wait for workers. + for (DWORD i = 0; i < si.dwNumberOfProcessors; i++) { + if (workers[i]) { + workers[i]->join(); + delete workers[i]; + } + } + + delete [] workers; // This line of code sounds horrible, I know. +} + + +_Use_decl_annotations_ +ZRCola::DBSource::character_bank::build_related_worker::build_related_worker(const character_bank &cb, iterator from, iterator to) : + winstd::thread((HANDLE)_beginthreadex(NULL, 0, process, this, CREATE_SUSPENDED, NULL)), + m_heap(HeapCreate(0, 0, 0)), + m_cb(cb), + m_from(from), + m_to(to) +{ + // Now that members of this class are surely initialized, proceed. + ResumeThread(m_h); +} + + +unsigned int ZRCola::DBSource::character_bank::build_related_worker::process() +{ + heap_allocator al(m_heap); + vector > rel(al); + set, heap_allocator > matching(less(), al); + + for (auto c = m_from; c != m_to; ++c) { + rel.clear(); + + // Skip all inexistent, or self related characters. + auto m_cb_end = m_cb.cend(); + for (std::vector::const_pointer c_rel = c->second.rel.data(), c_rel_end = c_rel + c->second.rel.size(), c_rel_next = c_rel_end; c_rel < c_rel_end; c_rel = c_rel_next) { + c_rel_next = c_rel + stdex::strnlen(c_rel, c_rel_end - c_rel) + 1; + if (m_cb.find(c_rel) != m_cb_end && c->first.compare(c_rel) != 0) + rel.insert(rel.end(), c_rel, c_rel_next); + } + + // Add all characters that share enough keywords. + for (auto c2 = m_cb.cbegin(), c2_end = m_cb.cend(); c2 != c2_end; ++c2) { + if (c == c2) + continue; + bool already_present = false; + for (std::vector::const_pointer c_rel = rel.data(), c_rel_end = c_rel + rel.size(), c_rel_next = c_rel_end; c_rel < c_rel_end; c_rel = c_rel_next) { + c_rel_next = c_rel + stdex::strnlen(c_rel, c_rel_end - c_rel) + 1; + if (c2->first.compare(c_rel) == 0) { + already_present = true; + break; + } + } + if (already_present) + continue; + + set::size_type comparisons = 0; + matching.clear(); + for (auto term = c->second.terms_rel.cbegin(), term_end = c->second.terms_rel.cend(); term != term_end; ++term) { + for (auto term2 = c2->second.terms_rel.cbegin(), term2_end = c2->second.terms_rel.cend(); term2 != term2_end; ++term2) { + comparisons++; + if (*term == *term2) + matching.insert(*term); + } + } + + if (comparisons) { + // If 1/2 terms match, assume related. + auto hits = matching.size(); + if (hits*hits*2 >= comparisons) + rel.insert(rel.end(), c2->first.data(), c2->first.data() + c2->first.length() + 1); + } + } + + c->second.rel.assign(rel.cbegin(), rel.cend()); + + if (!c->first.empty()) { + // Find the block this character belongs to. + char32_t ch = c->first.size() == 1 ? c->first[0] : stdex::surrogate_pair_to_ucs4(c->first.c_str()); + if (auto it = m_cb.idxChrBlk.upper_bound(ch); it != m_cb.idxChrBlk.begin()) { + --it; + if (ch >= it->first && ch <= it->second.chr_end) { + c->second.blk = it->second.id; + // May un-const, as `used` field does not change the map sort order. + const_cast(it->second).used = true; + } + } + } + } + + return 0; +} + + +unsigned int __stdcall ZRCola::DBSource::character_bank::build_related_worker::process(_In_ void *param) +{ + return ((ZRCola::DBSource::character_bank::build_related_worker*)param)->process(); +} + + +////////////////////////////////////////////////////////////////////////// +// ZRCola::DBSource::character_desc_idx +////////////////////////////////////////////////////////////////////////// + +void ZRCola::DBSource::character_desc_idx::parse_keywords(_In_ const wchar_t *str, _Inout_ set &terms) +{ + wxASSERT_MSG(str, wxT("string is NULL")); + + while (*str) { + // Skip white space. + for (;;) { + if (*str == 0) + return; + else if (!iswspace(*str)) + break; + else + str++; + } + + // Get term. + wstring term; + if (*str == L'"') { + const wchar_t *str_end = ++str; + for (;;) { + if (*str_end == 0) { + term.assign(str, str_end); + break; + } else if (*str_end == L'"') { + term.assign(str, str_end); + str_end++; + break; + } else + str_end++; + } + str = str_end; + } else { + const wchar_t *str_end = str + 1; + for (; *str_end && !iswspace(*str_end); str_end++); + term.assign(str, str_end); + str = str_end; + } + + if (!term.empty()) { + transform(term.begin(), term.end(), term.begin(), towlower); + terms.insert(term); + } + } +} + + +void ZRCola::DBSource::character_desc_idx::add_keywords(const set &terms, const wstring &chr, size_t sub) +{ + for (auto term = terms.cbegin(), term_end = terms.cend(); term != term_end; ++term) { + if (sub) { + wstring::size_type j_end = term->size(); + if (j_end >= sub) { + // Insert all keyword substrings "sub" or more characters long. + for (wstring::size_type i = 0, i_end = j_end - sub; i <= i_end; ++i) { + for (wstring::size_type j = i + sub; j <= j_end; ++j) + add_keyword(term->substr(i, j - i), chr); + } + } + } else { + // Insert exact keyword only. + add_keyword(*term, chr); + } + } +} + + +void ZRCola::DBSource::character_desc_idx::save(ZRCola::textindex &idx) const +{ + idx .clear(); + idx.keys .clear(); + idx.values.clear(); + + // Pre-allocate memory. + vector::size_type size_keys = 0; + vector::size_type size_values = 0; + for (const_iterator i = cbegin(), i_end = cend(); i != i_end; ++i) { + size_keys += i->first.size(); + size_values += i->second.size(); + } + idx .reserve(size() ); + idx.keys .reserve(size_keys ); + idx.values.reserve(size_values); + + // Convert the index. + for (const_iterator i = cbegin(), i_end = cend(); i != i_end; ++i) { + ZRCola::mappair_t p = { idx.keys.size(), idx.values.size() }; + idx.push_back(p); + idx.keys.insert(idx.keys.end(), i->first.cbegin(), i->first.cend()); + idx.values.insert(idx.values.end(), i->second.cbegin(), i->second.cend()); + } +} + + +////////////////////////////////////////////////////////////////////////// +// ZRCola::DBSource +////////////////////////////////////////////////////////////////////////// + +ZRCola::DBSource::DBSource() : + m_locale(nullptr) +{ + // Initialize ignore list. + m_terms_ignore.insert(L"letter"); + m_terms_ignore.insert(L"modifier"); + m_terms_ignore.insert(L"symbol"); + m_terms_ignore.insert(L"accent"); + m_terms_ignore.insert(L"with"); + m_terms_ignore.insert(L"and"); + m_terms_ignore.insert(L"capital"); + m_terms_ignore.insert(L"small"); + m_terms_ignore.insert(L"combining"); +} + + +ZRCola::DBSource::~DBSource() +{ + // Manually release all COM objects related to the database before we close the database. + m_pTranslationSets1.free(); + m_comTranslationSets.free(); + m_pTranslation1.free(); + m_comTranslation.free(); + m_pCharacterGroup1.free(); + m_comCharacterGroup.free(); + m_pHighlight1.free(); + m_comHighlight.free(); + + if (m_db.valid()) + m_db->Close(); + + if (m_locale) + _free_locale(m_locale); +} + + +bool ZRCola::DBSource::Open(LPCTSTR filename) +{ + wxASSERT_MSG(!m_db.valid(), wxT("database already open")); + + // Create COM object. + HRESULT hr = ::CoCreateInstance(CLSID_CADOConnection, NULL, CLSCTX_ALL, IID_IADOConnection, (LPVOID*)&m_db); + if (SUCCEEDED(hr)) { + // Open the database. + wstring cn; + cn = L"Driver={Microsoft Access Driver (*.mdb)};"; + cn += L"Dbq="; + cn += filename; + cn += L";Uid=;Pwd=;"; +#pragma warning(push) +#pragma warning(disable: 6387) // Connection15::Open() declaration is wrong: it defaults username and password parameters to NULL, but annotates them as required non-NULL. + hr = m_db->Open(bstr(cn.c_str())); +#pragma warning(pop) + if (SUCCEEDED(hr)) { + // Database open and ready. + m_filename = filename; + m_locale = _create_locale(LC_ALL, "Slovenian_Slovenia.1250"); + + // Create ADO command(s). + wxASSERT_MSG(!m_comCharacterGroup.valid(), wxT("ADO command already created")); + wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADOCommand, NULL, CLSCTX_ALL, IID_IADOCommand, (LPVOID*)&m_comCharacterGroup))); + wxVERIFY(SUCCEEDED(m_comCharacterGroup->put_ActiveConnection(variant((IDispatch*)m_db)))); + wxVERIFY(SUCCEEDED(m_comCharacterGroup->put_CommandType(adCmdText))); + wxVERIFY(SUCCEEDED(m_comCharacterGroup->put_CommandText(bstr(L"SELECT [VRS_SkupineZnakov].[Znak], [VRS_SkupineZnakov].[pogost] " + L"FROM [VRS_SkupineZnakov] " + L"LEFT JOIN [VRS_CharList] ON [VRS_SkupineZnakov].[Znak]=[VRS_CharList].[znak] " + L"WHERE [VRS_CharList].[aktiven]=1 AND [VRS_SkupineZnakov].[Skupina]=? " + L"ORDER BY [VRS_SkupineZnakov].[Rang] ASC, [VRS_SkupineZnakov].[Znak] ASC")))); + { + // Create and add command parameters. + com_obj params; + wxVERIFY(SUCCEEDED(m_comCharacterGroup->get_Parameters(¶ms))); + wxASSERT_MSG(!m_pCharacterGroup1.valid(), wxT("ADO command parameter already created")); + wxVERIFY(SUCCEEDED(m_comCharacterGroup->CreateParameter(bstr(L"@Skupina"), adVarWChar, adParamInput, 50, variant(DISP_E_PARAMNOTFOUND, VT_ERROR), &m_pCharacterGroup1))); + wxVERIFY(SUCCEEDED(params->Append(m_pCharacterGroup1))); + } + + wxASSERT_MSG(!m_comTranslation.valid(), wxT("ADO command already created")); + wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADOCommand, NULL, CLSCTX_ALL, IID_IADOCommand, (LPVOID*)&m_comTranslation))); + wxVERIFY(SUCCEEDED(m_comTranslation->put_ActiveConnection(variant((IDispatch*)m_db)))); + wxVERIFY(SUCCEEDED(m_comTranslation->put_CommandType(adCmdText))); + wxVERIFY(SUCCEEDED(m_comTranslation->put_CommandText(bstr(L"SELECT [Komb1] AS [komb], [rang_komb1] AS [rang_komb], '' AS [Kano], 0 AS [Kanoniziraj], [Komb2] AS [znak], [rang_komb2] AS [rang_znak] " + L"FROM [VRS_ScriptRepl2] " + L"WHERE [Script]=? " + L"ORDER BY [Komb2], [rang_komb2], [rang_komb1], [Komb1]")))); + { + // Create and add command parameters. + com_obj params; + wxVERIFY(SUCCEEDED(m_comTranslation->get_Parameters(¶ms))); + wxASSERT_MSG(!m_pTranslation1.valid(), wxT("ADO command parameter already created")); + wxVERIFY(SUCCEEDED(m_comTranslation->CreateParameter(bstr(L"@Script"), adSmallInt, adParamInput, 0, variant(DISP_E_PARAMNOTFOUND, VT_ERROR), &m_pTranslation1))); + wxVERIFY(SUCCEEDED(params->Append(m_pTranslation1))); + } + + wxASSERT_MSG(!m_comTranslationSets.valid(), wxT("ADO command already created")); + wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADOCommand, NULL, CLSCTX_ALL, IID_IADOCommand, (LPVOID*)&m_comTranslationSets))); + wxVERIFY(SUCCEEDED(m_comTranslationSets->put_ActiveConnection(variant((IDispatch*)m_db)))); + wxVERIFY(SUCCEEDED(m_comTranslationSets->put_CommandType(adCmdText))); + wxVERIFY(SUCCEEDED(m_comTranslationSets->put_CommandText(bstr(L"SELECT [Script] " + L"FROM [VRS_Script2SeqScr] " + L"WHERE [ID]=? " + L"ORDER BY [Rank] ASC")))); + { + // Create and add command parameters. + com_obj params; + wxVERIFY(SUCCEEDED(m_comTranslationSets->get_Parameters(¶ms))); + wxASSERT_MSG(!m_pTranslationSets1.valid(), wxT("ADO command parameter already created")); + wxVERIFY(SUCCEEDED(m_comTranslationSets->CreateParameter(bstr(L"@ID"), adSmallInt, adParamInput, 0, variant(DISP_E_PARAMNOTFOUND, VT_ERROR), &m_pTranslationSets1))); + wxVERIFY(SUCCEEDED(params->Append(m_pTranslationSets1))); + } + + wxASSERT_MSG(!m_comHighlight.valid(), wxT("ADO command already created")); + wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADOCommand, NULL, CLSCTX_ALL, IID_IADOCommand, (LPVOID*)&m_comHighlight))); + wxVERIFY(SUCCEEDED(m_comHighlight->put_ActiveConnection(variant((IDispatch*)m_db)))); + wxVERIFY(SUCCEEDED(m_comHighlight->put_CommandType(adCmdText))); + wxVERIFY(SUCCEEDED(m_comHighlight->put_CommandText(bstr(L"SELECT [komb] " + L"FROM [VRS_HighlightChars2] " + L"WHERE [group]=? " + L"ORDER BY [komb]")))); + { + // Create and add command parameters. + com_obj params; + wxVERIFY(SUCCEEDED(m_comHighlight->get_Parameters(¶ms))); + wxASSERT_MSG(!m_pHighlight1.valid(), wxT("ADO command parameter already created")); + wxVERIFY(SUCCEEDED(m_comHighlight->CreateParameter(bstr(L"@group"), adSmallInt, adParamInput, 0, variant(DISP_E_PARAMNOTFOUND, VT_ERROR), &m_pHighlight1))); + wxVERIFY(SUCCEEDED(params->Append(m_pHighlight1))); + } + + return true; + } else { + _ftprintf(stderr, wxT("%s: error ZCC0011: Could not open database (0x%x).\n"), (LPCTSTR)filename, hr); + LogErrors(); + } + m_db.free(); + } else + _ftprintf(stderr, wxT("%s: error ZCC0012: Creating ADOConnection object failed (0x%x).\n"), (LPCTSTR)filename, hr); + + return false; +} + + +void ZRCola::DBSource::LogErrors() const +{ + wxASSERT_MSG(m_db.valid(), wxT("database does not exist")); + + // Get array of errors. + ADOErrors *errors = NULL; + if (SUCCEEDED(m_db->get_Errors(&errors))) { + // Get number of errors. + long n = 0; + wxVERIFY(SUCCEEDED(errors->get_Count(&n))); + + // Iterate the errors. + for (long i = 0; i < n; i++) { + ADOError *err = NULL; + if (SUCCEEDED(errors->get_Item(variant(i), &err))) { + // Write error number and description to the log. + long num = 0; + wxVERIFY(SUCCEEDED(err->get_Number(&num))); + + bstr desc; + wxVERIFY(SUCCEEDED(err->get_Description(&desc))); + + _ftprintf(stderr, wxT(" error ADO%x: %ls\n"), num, (BSTR)desc); + + err->Release(); + } + } + + errors->Release(); + } +} + + +bool ZRCola::DBSource::GetValue(const com_obj& f, bool& val) const +{ + wxASSERT_MSG(f.valid(), wxT("field is empty")); + + variant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + wxCHECK(SUCCEEDED(v.change_type(VT_BOOL)), false); + + val = V_BOOL(&v) ? true : false; + + return true; +} + + +bool ZRCola::DBSource::GetValue(const com_obj& f, short& val) const +{ + wxASSERT_MSG(f.valid(), wxT("field is empty")); + + variant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + wxCHECK(SUCCEEDED(v.change_type(VT_I2)), false); + + val = V_I2(&v); + + return true; +} + + +bool ZRCola::DBSource::GetValue(const com_obj& f, string& val) const +{ + wxASSERT_MSG(f.valid(), wxT("field is empty")); + + variant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + if (V_VT(&v) != VT_NULL) { + wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); + + WideCharToMultiByte(CP_ACP, 0, V_BSTR(&v), ::SysStringLen(V_BSTR(&v)), val, NULL, NULL); + } else + val.clear(); + + return true; +} + + +bool ZRCola::DBSource::GetValue(const com_obj& f, wstring& val) const +{ + wxASSERT_MSG(f.valid(), wxT("field is empty")); + + variant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + if (V_VT(&v) != VT_NULL) { + wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); + + val.reserve(::SysStringLen(V_BSTR(&v))); + val = V_BSTR(&v); + } else + val.clear(); + + return true; +} + + +bool ZRCola::DBSource::GetUnicodeCharacter(const com_obj& f, char32_t& chr) const +{ + wxASSERT_MSG(f.valid(), wxT("field is empty")); + + variant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + if (V_VT(&v) != VT_NULL) { + wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); + + // Parse the field. Must be exactly one Unicode code. + UINT i = 0, n = ::SysStringLen(V_BSTR(&v)); + chr = 0; + for (; i < n && V_BSTR(&v)[i]; i++) { + if (L'0' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'9') chr = chr*0x10 + (V_BSTR(&v)[i] - L'0'); + else if (L'A' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'F') chr = chr*0x10 + (V_BSTR(&v)[i] - L'A' + 10); + else if (L'a' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'f') chr = chr*0x10 + (V_BSTR(&v)[i] - L'a' + 10); + else break; + } + if (i <= 0 && 6 < i) { + bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0030: Syntax error in \"%.*ls\" field (\"%.*ls\"). Unicode code must be one to six hexadecimal characters long.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); + return false; + } else if (i != n) { + bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0031: Syntax error in \"%.*ls\" field (\"%.*ls\"). Extra trailing characters.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); + return false; + } + } else + chr = 0; + + return true; +} + + +bool ZRCola::DBSource::GetUnicodeString(const com_obj& f, wstring& str) const +{ + wxASSERT_MSG(f.valid(), wxT("field is empty")); + + variant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + str.clear(); + if (V_VT(&v) != VT_NULL) { + wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); + + // Parse the field. Must be "xxxx+xxxx+xxxx..." sequence. + for (UINT i = 0, n = ::SysStringLen(V_BSTR(&v)); i < n && V_BSTR(&v)[i];) { + // Parse Unicode code. + UINT j = 0; + wchar_t c = 0; + for (; i < n && V_BSTR(&v)[i]; i++, j++) { + if (L'0' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'9') c = c*0x10 + (V_BSTR(&v)[i] - L'0'); + else if (L'A' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'F') c = c*0x10 + (V_BSTR(&v)[i] - L'A' + 10); + else if (L'a' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'f') c = c*0x10 + (V_BSTR(&v)[i] - L'a' + 10); + else break; + } + if (j <= 0 || 4 < j) { + bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0020: Syntax error in \"%.*ls\" field (\"%.*ls\"). Unicode code must be one to four hexadecimal characters long.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); + return false; + } + str += c; + + // Skip delimiter(s) and whitespace. + for (; i < n && V_BSTR(&v)[i] && (V_BSTR(&v)[i] == L'+' || _iswspace_l(V_BSTR(&v)[i], m_locale)); i++); + } + } + + return true; +} + + +bool ZRCola::DBSource::GetNormPerm(const winstd::com_obj& f, normperm& np) const +{ + wxASSERT_MSG(f.valid(), wxT("field is empty")); + + variant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + np.clear(); + if (V_VT(&v) != VT_NULL) { + wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); + + // Parse the field. Must be "nnnn,nnnn,nnnn..." sequence. + for (UINT i = 0, n = ::SysStringLen(V_BSTR(&v)); i < n && V_BSTR(&v)[i];) { + // Parse Unicode code. + UINT j = 0; + std::vector p; + for (; i < n && V_BSTR(&v)[i]; i++, j++) { + if (L'0' <= V_BSTR(&v)[i] && V_BSTR(&v)[i] <= L'9') p.push_back(V_BSTR(&v)[i] - L'0'); + else break; + } + if (j <= 0) { + bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0150: Syntax error in \"%.*ls\" field (\"%.*ls\"). Permutation sequence must be at least one decimal digit long.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); + return false; + } + np.insert(std::move(p)); + + // Skip delimiter(s) and whitespace. + for (; i < n && V_BSTR(&v)[i] && (V_BSTR(&v)[i] == L',' || _iswspace_l(V_BSTR(&v)[i], m_locale)); i++); + } + } + + return true; +} + + + +bool ZRCola::DBSource::GetLanguage(const com_obj& f, ZRCola::langid_t& lang) const +{ + wxASSERT_MSG(f.valid(), wxT("field is empty")); + + variant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); + + // Convert to lowercase. + _wcslwr_l(V_BSTR(&v), m_locale); + + // Parse the field. + size_t n = wcsnlen(V_BSTR(&v), ::SysStringLen(V_BSTR(&v))); + if (n != 3) { + bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0080: Syntax error in \"%.*ls\" field (\"%.*ls\"). Language ID must be exactly three (3) characters long.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); + return false; + } + for (size_t i = 0;; i++) { + if (i < sizeof(lang)) { + if (i < n) { + wchar_t c = V_BSTR(&v)[i]; + if ((unsigned short)c > 0x7f) { + bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0081: Syntax error in \"%.*ls\" field (\"%.*ls\"). Language ID must contain ASCII characters only.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); + return false; + } + lang.data[i] = (char)c; + } else + lang.data[i] = 0; + } else + break; + } + + return true; +} + + +bool ZRCola::DBSource::GetChrCat(const com_obj& f, chrcatid_t& cc) const +{ + wxASSERT_MSG(f.valid(), wxT("field is empty")); + + variant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + if (V_VT(&v) != VT_NULL) { + wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); + + // Parse the field. + size_t n = wcsnlen(V_BSTR(&v), ::SysStringLen(V_BSTR(&v))); + if (n < 1 || 2 < n) { + bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0110: Syntax error in \"%.*ls\" field (\"%.*ls\"). Character category ID must be one (1) or two (2) characters long.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); + return false; + } + for (size_t i = 0;; i++) { + if (i < sizeof(cc)) { + if (i < n) { + wchar_t c = V_BSTR(&v)[i]; + if ((unsigned short)c > 0x7f) { + bstr fieldname; wxVERIFY(SUCCEEDED(f->get_Name(&fieldname))); + _ftprintf(stderr, wxT("%s: error ZCC0111: Syntax error in \"%.*ls\" field (\"%.*ls\"). Character category ID must contain ASCII characters only.\n"), m_filename.c_str(), fieldname.length(), (BSTR)fieldname, n, V_BSTR(&v)); + return false; + } + cc.data[i] = (char)c; + } else + cc.data[i] = 0; + } else + break; + } + } else + memset(cc.data, 0, sizeof(cc)); + + return true; +} + + +bool ZRCola::DBSource::GetTagNames(const winstd::com_obj& f, LCID lcid, list& names) const +{ + wxASSERT_MSG(f.valid(), wxT("field is empty")); + + variant v; + wxVERIFY(SUCCEEDED(f->get_Value(&v))); + wxCHECK(SUCCEEDED(v.change_type(VT_BSTR)), false); + + // Parse the field. Must be "name, name, name..." sequence. + names.clear(); + for (UINT i = 0, i_end = ::SysStringLen(V_BSTR(&v)); i < i_end && V_BSTR(&v)[i];) { + if (iswspace(V_BSTR(&v)[i])) { + // Skip leading white space. + i++; continue; + } + + // Parse name. + UINT j = i, j_end = i; + for (; i < i_end && V_BSTR(&v)[i]; i++) { + if (V_BSTR(&v)[i] == L',' || V_BSTR(&v)[i] == L';') { + // Delimiter found. + i++; break; + } else if (!iswspace(V_BSTR(&v)[i])) { + // Remember last non-white space character. + j_end = i + 1; + } + } + wstring name(V_BSTR(&v) + j, V_BSTR(&v) + j_end); + for (auto n = names.cbegin(), n_end = names.cend(); ; ++n) { + if (n == n_end) { + // Add name to the list. + names.push_back(std::move(name)); + break; + } else if (ZRCola::tagname_db::tagname::CompareName(lcid, n->data(), (uint16_t)n->length(), name.data(), (uint16_t)name.length()) == CSTR_EQUAL) { + // Name is already on the list. + break; + } + } + } + + return true; +} + + +bool ZRCola::DBSource::SelectNormPermSets(winstd::com_obj& rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT [oblika], [oblike] " + L"FROM [VRS_CharCanoOblike] " + L"ORDER BY [oblika], [oblike]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0160: Error loading normalization permutation sets from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetNormPerm(const winstd::com_obj& rs, std::string& norm, normperm& np) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"oblika"), &f))); + wxCHECK(GetValue(f, norm), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"oblike"), &f))); + wxCHECK(GetNormPerm(f, np), false); + } + + // Verify all lengths match. + size_t n = norm.length(); + for (auto p = np.cbegin(), p_end = np.cend(); p != p_end; ++p) { + if (p->size() != n) { + _ftprintf(stderr, wxT("%s: error ZCC0170: Inconsistent normalization sequence \"%.*hs\" permutation length. Please make sure all permutation lengths match normalization sequence length (%u).\n"), m_filename.c_str(), n, norm.c_str(), n); + return false; + } + } + + return true; +} + + +bool ZRCola::DBSource::SelectTranslations(com_obj &rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT [komb], [rang_komb], [Kano], [Kanoniziraj], [znak], [rang_znak] " + L"FROM [VRS_ReplChar] " + L"WHERE [rang_komb]=1 " + L"ORDER BY [znak], [rang_znak], [rang_komb], [komb]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0040: Error loading translations from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::SelectTranslations(short set, winstd::com_obj& rs) const +{ + // Create a new recordset. + rs.free(); + wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs))); + wxVERIFY(SUCCEEDED(rs->put_CursorLocation(adUseClient))); + wxVERIFY(SUCCEEDED(rs->put_CursorType(adOpenForwardOnly))); + wxVERIFY(SUCCEEDED(rs->put_LockType(adLockReadOnly))); + + // Open it. + wxVERIFY(SUCCEEDED(m_pTranslation1->put_Value(variant(set)))); + if (FAILED(rs->Open(variant((IDispatch*)m_comTranslation), variant(DISP_E_PARAMNOTFOUND, VT_ERROR)))) { + _ftprintf(stderr, wxT("%s: error ZCC0100: Error loading translations from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetTranslation(const com_obj& rs, ZRCola::DBSource::translation& t) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"komb"), &f))); + wxCHECK(GetUnicodeString(f, t.src.str), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"rang_komb"), &f))); + wxCHECK(GetValue(f, t.src.rank), false); + } + + { + bool norm; + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Kanoniziraj"), &f))); + wxCHECK(GetValue(f, norm), false); + if (norm) { + com_obj f2; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Kano"), &f2))); + wxCHECK(GetValue(f2, t.norm), false); + } else + t.norm.clear(); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"rang_znak"), &f))); + wxCHECK(GetValue(f, t.dst.rank), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak"), &f))); + wxCHECK(GetUnicodeString(f, t.dst.str), false); + } + + return true; +} + + +bool ZRCola::DBSource::SelectTranlationSets(com_obj &rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT DISTINCT [entCode], [Src_En], [Dst_En] " + L"FROM [VRS_Script2] " + L"ORDER BY [entCode], [Src_En], [Dst_En]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0060: Error loading translation sets from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetTranslationSet(const com_obj& rs, ZRCola::DBSource::transet& ts) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"entCode"), &f))); + wxCHECK(GetValue(f, ts.set), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Src_En"), &f))); + wxCHECK(GetValue(f, ts.src), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Dst_En"), &f))); + wxCHECK(GetValue(f, ts.dst), false); + } + + return true; +} + + +bool ZRCola::DBSource::SelectTranlationSeqs(com_obj &rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT DISTINCT [ID], [Descr], [Rank] " + L"FROM [VRS_Script2Seq] " + L"ORDER BY [Rank], [Descr]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0060: Error loading translation sequences from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetTranslationSeq(const com_obj& rs, ZRCola::DBSource::transeq& ts) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"ID"), &f))); + wxCHECK(GetValue(f, ts.seq), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Rank"), &f))); + wxCHECK(GetValue(f, ts.rank), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Descr"), &f))); + wxCHECK(GetValue(f, ts.name), false); + } + + // Read translation sequence sets from database. + wxVERIFY(SUCCEEDED(m_pTranslationSets1->put_Value(variant(ts.seq)))); + com_obj rs_chars; + wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs_chars))); + wxVERIFY(SUCCEEDED(rs_chars->put_CursorLocation(adUseClient))); + wxVERIFY(SUCCEEDED(rs_chars->put_CursorType(adOpenForwardOnly))); + wxVERIFY(SUCCEEDED(rs_chars->put_LockType(adLockReadOnly))); + if (FAILED(rs_chars->Open(variant((IDispatch*)m_comTranslationSets), variant(DISP_E_PARAMNOTFOUND, VT_ERROR)))) { + _ftprintf(stderr, wxT("%s: error ZCC0140: Error loading character group characters from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + { + ts.sets.clear(); + com_obj flds2; + wxVERIFY(SUCCEEDED(rs_chars->get_Fields(&flds2))); + com_obj f_set; + wxVERIFY(SUCCEEDED(flds2->get_Item(variant(L"Script"), &f_set))); + size_t n = 0; + for (VARIANT_BOOL eof = VARIANT_TRUE; SUCCEEDED(rs_chars->get_EOF(&eof)) && !eof; rs_chars->MoveNext(), n++) { + short set; + wxCHECK(GetValue(f_set, set), false); + ts.sets.push_back(set); + } + } + + return true; +} + + +bool ZRCola::DBSource::SelectKeySequences(com_obj &rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT DISTINCT [VRS_KeyCodes].[Znak], [VRS_CharGroup].[CharGroup], IIF([VRS_CharGroup].[Arg1] IS NOT NULL, [VRS_CharGroup].[Arg1], 0)+IIF([VRS_CharGroup].[Arg2] IS NOT NULL, [VRS_CharGroup].[Arg2], 0)+IIF([VRS_CharGroup].[Arg3] IS NOT NULL, [VRS_CharGroup].[Arg3], 0) AS [Modifiers], IIF([VRS_CharGroup].[Arg4] IS NOT NULL, [VRS_CharGroup].[Arg4], 0) AS [KeyCodePre], [VRS_KeyCodes].[KeyCode], [VRS_KeyCodes].[Shift] " + L"FROM [VRS_KeyCodes] LEFT JOIN [VRS_CharGroup] ON [VRS_CharGroup].[CharGroup]=[VRS_KeyCodes].[CharGroup] " + L"ORDER BY [VRS_CharGroup].[CharGroup], [VRS_KeyCodes].[KeyCode], [VRS_KeyCodes].[Shift], [VRS_KeyCodes].[Znak]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0050: Error loading key sequences from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetKeySequence(const com_obj& rs, ZRCola::DBSource::keyseq& ks) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Znak"), &f))); + wxCHECK(GetUnicodeString(f, ks.chr), false); + } + + short modifiers; + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Modifiers"), &f))); + wxCHECK(GetValue(f, modifiers), false); + } + + short keycode1; + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"KeyCodePre"), &f))); + wxCHECK(GetValue(f, keycode1), false); + } + + short keycode; + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"KeyCode"), &f))); + wxCHECK(GetValue(f, keycode), false); + } + + bool shift; + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Shift"), &f))); + wxCHECK(GetValue(f, shift), false); + } + + ks.seq.clear(); + if (keycode1) { + // First key in the sequence is complete. + keyseq::keycode kc1 = { + keyseq::keycode::translate_slen(static_cast(keycode1)), + (modifiers & 0x100) != 0, + (modifiers & 0x200) != 0, + (modifiers & 0x400) != 0 }; + ks.seq.push_back(kc1); + + keyseq::keycode kc2 = { keyseq::keycode::translate_slen(static_cast(keycode)), shift }; + ks.seq.push_back(kc2); + } else { + // First key in the sequence is only modifier(s). + keyseq::keycode kc1 = { + keyseq::keycode::translate_slen(static_cast(keycode)), + shift || (modifiers & 0x100) != 0, + (modifiers & 0x200) != 0, + (modifiers & 0x400) != 0 }; + ks.seq.push_back(kc1); + } + + return true; +} + + +bool ZRCola::DBSource::SelectLanguages(com_obj &rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT DISTINCT [entCode], [Jezik_En] " + L"FROM [VRS_Jezik] " + L"ORDER BY [entCode], [Jezik_En]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0060: Error loading languages from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetLanguage(const com_obj& rs, ZRCola::DBSource::language& lang) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"entCode"), &f))); + wxCHECK(GetLanguage(f, lang.lang), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Jezik_En"), &f))); + wxCHECK(GetValue(f, lang.name), false); + } + + return true; +} + + +bool ZRCola::DBSource::SelectLanguageCharacters(com_obj &rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT DISTINCT [znak], [lang] " + L"FROM [VRS_CharLocal] " + L"ORDER BY [znak], [lang]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0090: Error loading language characters from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetLanguageCharacter(const com_obj& rs, ZRCola::DBSource::langchar& lc) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak"), &f))); + wxCHECK(GetUnicodeString(f, lc.chr), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"lang"), &f))); + wxCHECK(GetLanguage(f, lc.lang), false); + } + + return true; +} + + +bool ZRCola::DBSource::SelectCharacterGroups(com_obj& rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT DISTINCT [id], [Skupina], [opis_en], [Rang] " + L"FROM [VRS_SkupinaZnakov] " + L"ORDER BY [Rang], [opis_en]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0090: Error loading character groups from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetCharacterGroup(const com_obj& rs, chrgrp& cg) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + wstring grp; + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"id"), &f))); + wxCHECK(GetValue(f, cg.grp), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Skupina"), &f))); + wxCHECK(GetValue(f, grp), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Rang"), &f))); + wxCHECK(GetValue(f, cg.rank), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_en"), &f))); + wxCHECK(GetValue(f, cg.name), false); + } + + // Read character list from database. + wxVERIFY(SUCCEEDED(m_pCharacterGroup1->put_Value(variant(grp.c_str())))); + com_obj rs_chars; + wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs_chars))); + wxVERIFY(SUCCEEDED(rs_chars->put_CursorLocation(adUseClient))); + wxVERIFY(SUCCEEDED(rs_chars->put_CursorType(adOpenForwardOnly))); + wxVERIFY(SUCCEEDED(rs_chars->put_LockType(adLockReadOnly))); + if (FAILED(rs_chars->Open(variant((IDispatch*)m_comCharacterGroup), variant(DISP_E_PARAMNOTFOUND, VT_ERROR)))) { + _ftprintf(stderr, wxT("%s: error ZCC0140: Error loading character group characters from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + { + cg.chars.clear(); + cg.show.clear(); + com_obj flds2; + wxVERIFY(SUCCEEDED(rs_chars->get_Fields(&flds2))); + com_obj f_char, f_show; + wxVERIFY(SUCCEEDED(flds2->get_Item(variant(L"Znak" ), &f_char))); + wxVERIFY(SUCCEEDED(flds2->get_Item(variant(L"pogost"), &f_show))); + size_t n = 0; + for (VARIANT_BOOL eof = VARIANT_TRUE; SUCCEEDED(rs_chars->get_EOF(&eof)) && !eof; rs_chars->MoveNext(), n++) { + wstring c; + wxCHECK(GetUnicodeString(f_char, c), false); + cg.chars.insert(cg.chars.end(), c.data(), c.data() + c.length() + 1); + bool show; + wxCHECK(GetValue(f_show, show), false); + if ((n % 16) == 0) + cg.show.push_back(show ? 1 : 0); + else if (show) + cg.show[n / 16] |= 1 << (n % 16); + } + } + + return true; +} + + +bool ZRCola::DBSource::SelectCharacters(com_obj& rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT DISTINCT [znak], [opis_en], [kat], [znak_v], [znak_m] " + L"FROM [VRS_CharList] " + L"WHERE " + L"[aktiven]=1 AND " // Active characters only + L"[kat]<>'g' " // Ignore "Other, Control" category! + L"ORDER BY [znak]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0120: Error loading characters from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetCharacter(const com_obj& rs, character& chr) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + wstring c; + chr.second.terms.clear(); + chr.second.terms_rel.clear(); + chr.second.rel.clear(); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak"), &f))); + wxCHECK(GetUnicodeString(f, chr.first), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak_v"), &f))); + wxCHECK(GetUnicodeString(f, c), false); + if (!c.empty() && c != chr.first) + chr.second.rel.insert(chr.second.rel.end(), c.data(), c.data() + c.length() + 1); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak_m"), &f))); + wxCHECK(GetUnicodeString(f, c), false); + if (!c.empty() && c != chr.first) + chr.second.rel.insert(chr.second.rel.end(), c.data(), c.data() + c.length() + 1); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_en"), &f))); + wxCHECK(GetValue(f, chr.second.desc), false); + ZRCola::DBSource::character_desc_idx::parse_keywords(chr.second.desc.c_str(), chr.second.terms); + for (auto term = chr.second.terms.cbegin(), term_end = chr.second.terms.cend(); term != term_end; ++term) { + if (m_terms_ignore.find(*term) != m_terms_ignore.cend()) + continue; + chr.second.terms_rel.insert(*term); + } + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"kat"), &f))); + wxCHECK(GetChrCat(f, chr.second.cat), false); + } + + return true; +} + + +bool ZRCola::DBSource::SelectCharacterBlocks(com_obj& rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT DISTINCT [blok], [znak_od], [znak_do], [opis_en], [Rang] " + L"FROM [VRS_CharBlocks] " + L"ORDER BY [Rang], [opis_en]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0130: Error loading character blocks from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetCharacterBlock(const com_obj& rs, chrblk& cb) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"blok"), &f))); + wxCHECK(GetValue(f, cb.second.id), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak_od"), &f))); + wxCHECK(GetUnicodeCharacter(f, cb.first), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak_do"), &f))); + wxCHECK(GetUnicodeCharacter(f, cb.second.chr_end), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Rang"), &f))); + wxCHECK(GetValue(f, cb.second.rank), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_en"), &f))); + wxCHECK(GetValue(f, cb.second.name), false); + } + + return true; +} + + +bool ZRCola::DBSource::SelectCharacterCategories(com_obj& rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT DISTINCT [kat], [opis_en], [Rang] " + L"FROM [VRS_CharCategory] " + L"WHERE [kat]<>'g' " // Ignore "Other, Control" category! + L"ORDER BY [Rang], [opis_en]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0130: Error loading character categories from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetCharacterCategory(const com_obj& rs, chrcat& cc) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"kat"), &f))); + wxCHECK(GetChrCat(f, cc.cat), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"Rang"), &f))); + wxCHECK(GetValue(f, cc.rank), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_en"), &f))); + wxCHECK(GetValue(f, cc.name), false); + } + + return true; +} + + +bool ZRCola::DBSource::SelectCharacterTags(winstd::com_obj& rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT DISTINCT [znak], [oznaka] " + L"FROM [VRS_CharTags] " + L"ORDER BY [znak], [oznaka]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0130: Error loading character tags from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetCharacterTag(const winstd::com_obj& rs, chrtag& ct) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak"), &f))); + wxCHECK(GetUnicodeString(f, ct.chr), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"oznaka"), &f))); + wxCHECK(GetValue(f, ct.tag), false); + } + + return true; +} + + +bool ZRCola::DBSource::SelectTagNames(winstd::com_obj& rs) const +{ + // Create a new recordset. + rs.free(); + wxCHECK(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs)), false); + + // Open it. + if (FAILED(rs->Open(variant( + L"SELECT DISTINCT [oznaka], [opis_en], [opis_sl], [opis_ru] " + L"FROM [VRS_Tags] " + L"ORDER BY [oznaka]"), variant((IDispatch*)m_db), adOpenStatic, adLockReadOnly, adCmdText))) + { + _ftprintf(stderr, wxT("%s: error ZCC0130: Error loading tags from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetTagName(const winstd::com_obj& rs, tagname& tn) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + tn.names.clear(); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"oznaka"), &f))); + wxCHECK(GetValue(f, tn.tag), false); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_en"), &f))); + LCID lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), SORT_DEFAULT); + list names; + wxCHECK(GetTagNames(f, lcid, names), false); + tn.names.insert(std::move(pair >(lcid, std::move(names)))); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_sl"), &f))); + LCID lcid = MAKELCID(MAKELANGID(LANG_SLOVENIAN, SUBLANG_DEFAULT), SORT_DEFAULT); + list names; + wxCHECK(GetTagNames(f, lcid, names), false); + tn.names.insert(std::move(pair >(lcid, std::move(names)))); + } + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"opis_ru"), &f))); + LCID lcid = MAKELCID(MAKELANGID(LANG_RUSSIAN, SUBLANG_DEFAULT), SORT_DEFAULT); + list names; + wxCHECK(GetTagNames(f, lcid, names), false); + tn.names.insert(std::move(pair >(lcid, std::move(names)))); + } + + return true; +} + + +bool ZRCola::DBSource::SelectHighlights(short set, winstd::com_obj& rs) const +{ + // Create a new recordset. + rs.free(); + wxVERIFY(SUCCEEDED(::CoCreateInstance(CLSID_CADORecordset, NULL, CLSCTX_ALL, IID_IADORecordset, (LPVOID*)&rs))); + wxVERIFY(SUCCEEDED(rs->put_CursorLocation(adUseClient))); + wxVERIFY(SUCCEEDED(rs->put_CursorType(adOpenForwardOnly))); + wxVERIFY(SUCCEEDED(rs->put_LockType(adLockReadOnly))); + + // Open it. + wxVERIFY(SUCCEEDED(m_pHighlight1->put_Value(variant(set)))); + if (FAILED(rs->Open(variant((IDispatch*)m_comHighlight), variant(DISP_E_PARAMNOTFOUND, VT_ERROR)))) { + _ftprintf(stderr, wxT("%s: error ZCC0101: Error loading highlights from database. Please make sure the file is ZRCola.zrc compatible.\n"), m_filename.c_str()); + LogErrors(); + return false; + } + + return true; +} + + +bool ZRCola::DBSource::GetHighlight(const com_obj& rs, ZRCola::DBSource::highlight& h) const +{ + wxASSERT_MSG(rs.valid(), wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"komb"), &f))); + wxCHECK(GetUnicodeString(f, h.chr), false); + } + + return true; +} diff --git a/ZRColaCompile/dbsource.h b/ZRColaCompile/dbsource.h index 47b0ff6..e832a0a 100644 --- a/ZRColaCompile/dbsource.h +++ b/ZRColaCompile/dbsource.h @@ -1,1092 +1,1143 @@ -/* - SPDX-License-Identifier: GPL-3.0-or-later - Copyright © 2015-2022 Amebis -*/ - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include -#pragma warning(push) -#pragma warning(disable: WXWIDGETS_CODE_ANALYSIS_WARNINGS) -#include -#pragma warning(pop) - -#pragma warning(push) -#pragma warning(disable: 4091) -#include -#pragma warning(pop) -#include -#include -#include -#include -#include -#include - - -namespace ZRCola { - /// - /// Source database - /// - class DBSource - { - public: - /// - /// Character sequence - /// - struct charseq { - short rank = 0; ///< Sequence rank - std::wstring str; ///< Sequence string - - charseq() = default; - - charseq(_In_ short _rank, _In_z_ const wchar_t *_str) : - rank(_rank), - str (_str) - { - } - - charseq(_In_ short _rank, _In_ const std::wstring &_str) : - rank(_rank), - str (_str) - { - } - - charseq(_In_ short _rank, _Inout_ std::wstring &&_str) : - rank(_rank), - str (std::move(_str)) - { - } - - bool operator==(_In_ const charseq &other) const - { - return rank == other.rank && str == other.str; - } - - bool operator!=(_In_ const charseq &other) const - { - return !operator==(other); - } - - bool operator<(_In_ const charseq &other) const - { - if (rank < other.rank) return true; - else if (rank > other.rank) return false; - else if (str < other.str ) return true; - else return false; - } - - bool operator<=(_In_ const charseq &other) const - { - return !operator>(other); - } - - bool operator>(_In_ const charseq &other) const - { - return other.operator<(*this); - } - - bool operator>=(_In_ const charseq &other) const - { - return !operator<(other); - } - }; - - - /// - /// Translation - /// - struct translation { - short set = (short)ZRCOLA_TRANSETID_DEFAULT; ///< Translation set ID - charseq src; ///< Source sequence - std::string norm; ///< Normalization footprint - charseq dst; ///< Destination sequence - }; - - - /// - /// Translation set - /// - struct transet { - short set = (short)ZRCOLA_TRANSETID_DEFAULT; ///< ID - std::wstring src; ///< Source name - std::wstring dst; ///< Destination name - }; - - - /// - /// Translation sequence - /// - struct transeq { - short seq = 0; ///< ID - short rank = 0; ///< Rank - std::wstring name; ///< Name - std::vector sets; ///< Sets - }; - - - /// - /// Normalization permutation set - /// - typedef std::set > normperm; - - - /// - /// Key sequence - /// - struct keyseq { - /// - /// Key code - /// - struct keycode { - wchar_t key; ///< Key - bool shift; ///< Shift modifier - bool ctrl; ///< Ctrl modifier - bool alt; ///< Alt modifier - - /// - /// Translates keycode from Slovenian to English keyboard - /// - static wchar_t translate_slen(_In_ wchar_t key) - { - switch (key) { - case L'Z': return L'Y'; - case L'Y': return L'Z'; - case 191: return 189; - case 189: return 191; - default : return key; - } - } - }; - - std::wstring chr; ///< Character - std::vector seq; ///< Key sequence - }; - - - /// - /// Language - /// - struct language { - ZRCola::langid_t lang; ///< Language ID - std::wstring name; ///< Name - }; - - - /// - /// Language Character - /// - struct langchar { - std::wstring chr; ///> Character - ZRCola::langid_t lang; ///< Language ID - }; - - - /// - /// Character group - /// - struct chrgrp { - short grp = 0; ///< Character group ID - short rank = 0; ///< Rank - std::wstring name; ///< Name - std::vector chars; ///< Characters (zero-delimited) - std::vector show; ///< Bit vector if particular character from \c chars is displayed initially - }; - - - /// - /// Character data - /// - struct character_data { - ZRCola::chrcatid_t cat; ///< Category ID - std::wstring desc; ///< Character description - std::set terms; ///< Search terms - std::set terms_rel; ///< Relevant terms for relating characters - std::vector rel; ///< Related characters (zero-delimited) - - character_data() = default; - - character_data(_In_ const character_data &othr) : - cat (othr.cat), - desc (othr.desc), - terms (othr.terms), - terms_rel(othr.terms_rel), - rel (othr.rel) - { - } - }; - - - /// - /// Character - /// - typedef std::pair character; - - - /// - /// Character bank - /// - class character_bank : public std::map - { - public: - void build_related(); - - protected: - class build_related_worker : public winstd::thread - { - public: - build_related_worker(_In_ const character_bank *cb, _In_ iterator from, _In_ iterator to); - - void join() - { - if (m_h != invalid) - WaitForSingleObject(m_h, INFINITE); - } - - private: - // This class is non-copyable AND non-movable - build_related_worker(_Inout_ build_related_worker &othr); - build_related_worker(_Inout_ build_related_worker &&othr); - build_related_worker& operator=(_Inout_ build_related_worker &othr); - build_related_worker& operator=(_Inout_ build_related_worker &&othr); - - protected: - unsigned int process(); - static unsigned int __stdcall process(_In_ void *param); - - protected: - const character_bank *m_cb; - iterator m_from, m_to; - winstd::heap m_heap; - }; - }; - - - /// - /// Character description index key comparator - /// - struct character_desc_idx_less - { - bool operator()(const std::wstring& a, const std::wstring& b) const - { - auto &coll = std::use_facet>(std::locale()); - return coll.compare( - a.data(), a.data() + a.size(), - b.data(), b.data() + b.size()) < 0; - } - }; - - - /// - /// Character description index - /// - class character_desc_idx : public std::map, character_desc_idx_less> - { - public: - static void parse_keywords(_In_ const wchar_t *str, _Inout_ std::set &terms); - void add_keywords(const std::set &terms, const std::wstring &chr, size_t sub = 0); - void add_keywords(const wchar_t *str, const std::wstring &chr, size_t sub = 0) - { - std::set terms; - parse_keywords(str, terms); - add_keywords(terms, chr, sub); - } - - void save(ZRCola::textindex &idx) const; - - protected: - void add_keyword(const std::wstring &term, const std::wstring &chr) - { - iterator idx = find(term); - if (idx == end()) { - // New keyword. - insert(std::make_pair(term, mapped_type(chr.data(), chr.data() + chr.length() + 1))); - } else { - // Append to existing keyword. - auto &val = idx->second; - for (mapped_type::size_type i = 0, n = val.size(); ; i += wcsnlen(val.data() + i, n - i) + 1) { - if (i >= n) { - // End-of-values reached. Append character. - val.insert(val.end(), chr.data(), chr.data() + chr.length() + 1); - break; - } else if (chr.compare(val.data() + i) == 0) { - // Character already among the values. - break; - } - } - } - } - }; - - - /// - /// Character category - /// - struct chrcat { - ZRCola::chrcatid_t cat; ///> Category ID - short rank = 0; ///< Rank - std::wstring name; ///< Name - }; - - - /// - /// Character tag - /// - struct chrtag { - std::wstring chr; ///> Character - short tag = 0; ///< Tag ID - }; - - - /// - /// Tag name - /// - struct tagname { - short tag = 0; ///< Tag ID - std::map > names; ///< Names - }; - - - /// - /// Highlight - /// - struct highlight { - short set = (short)ZRCOLA_HLGHTSETID_DEFAULT; ///< Highlight set ID - std::wstring chr; ///< Character sequence - }; - - - public: - DBSource(); - virtual ~DBSource(); - - /// - /// Opens the database - /// - /// \param[in] filename File name of the MDB database. - /// - /// \returns - /// - true when open succeeds - /// - false otherwise - /// - bool Open(LPCTSTR filename); - - /// - /// Logs errors in database connections - /// - void LogErrors() const; - - /// - /// Is recordset at end - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when at end - /// - false otherwise - /// - static bool IsEOF(const winstd::com_obj& rs) - { - VARIANT_BOOL eof = VARIANT_TRUE; - return FAILED(rs->get_EOF(&eof)) || eof ? true : false; - } - - /// - /// Gets number of records in a recordset - /// - /// \param[out] rs Recordset with results - /// - /// \returns Number of records - /// - static size_t GetRecordsetCount(const winstd::com_obj& rs) - { - ADO_LONGPTR count; - return SUCCEEDED(rs->get_RecordCount(&count)) ? count : (size_t)-1; - } - - /// - /// Gets boolean from ZRCola.zrc database - /// - /// \param[in] f Data field - /// \param[out] val Output boolean value - /// - /// \returns - /// - true when successful - /// - false otherwise - /// - bool GetValue(const winstd::com_obj& f, bool& val) const; - - /// - /// Gets integer from ZRCola.zrc database - /// - /// \param[in] f Data field - /// \param[out] val Output integer value - /// - /// \returns - /// - true when successful - /// - false otherwise - /// - bool GetValue(const winstd::com_obj& f, short& val) const; - - /// - /// Gets string from ZRCola.zrc database - /// - /// \param[in] f Data field - /// \param[out] val Output string value - /// - /// \returns - /// - true when successful - /// - false otherwise - /// - bool GetValue(const winstd::com_obj& f, std::string& val) const; - - /// - /// Gets string from ZRCola.zrc database - /// - /// \param[in] f Data field - /// \param[out] val Output string value - /// - /// \returns - /// - true when successful - /// - false otherwise - /// - bool GetValue(const winstd::com_obj& f, std::wstring& val) const; - - /// - /// Gets encoded Unicode character from ZRCola.zrc database - /// - /// \param[in] f Data field - /// \param[out] chr Output character - /// - /// \returns - /// - true when successful - /// - false otherwise - /// - bool GetUnicodeCharacter(const winstd::com_obj& f, wchar_t& chr) const; - - /// - /// Gets encoded Unicode string from ZRCola.zrc database - /// - /// \param[in] f Data field - /// \param[out] str Output string - /// - /// \returns - /// - true when successful - /// - false otherwise - /// - bool GetUnicodeString(const winstd::com_obj& f, std::wstring& str) const; - - /// - /// Gets encoded normalization permutations from ZRCola.zrc database - /// - /// \param[in] f Data field - /// \param[out] str Output normalization permutation set - /// - /// \returns - /// - true when successful - /// - false otherwise - /// - bool GetNormPerm(const winstd::com_obj& f, normperm& np) const; - - /// - /// Gets language ID from ZRCola.zrc database - /// - /// \param[in] f Data field - /// \param[out] lang Language - /// - /// \returns - /// - true when successful - /// - false otherwise - /// - bool GetLanguage(const winstd::com_obj& f, langid_t& lang) const; - - /// - /// Gets character category ID from ZRCola.zrc database - /// - /// \param[in] f Data field - /// \param[out] cc Character category - /// - /// \returns - /// - true when successful - /// - false otherwise - /// - bool GetChrCat(const winstd::com_obj& f, chrcatid_t& cc) const; - - /// - /// Gets tag names from ZRCola.zrc database - /// - /// \param[in] f Data field - /// \param[out] names Output names - /// - /// \returns - /// - true when successful - /// - false otherwise - /// - bool GetTagNames(const winstd::com_obj& f, LCID lcid, std::list& names) const; - - /// - /// Returns normalization permutation sets - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectNormPermSets(winstd::com_obj& rs) const; - - /// - /// Returns normalization permutation set - /// - /// \param[in] rs Recordset with results - /// \param[out] np Normalization permutation set - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetNormPerm(const winstd::com_obj& rs, std::string& norm, normperm& np) const; - - /// - /// Returns character translations - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectTranslations(winstd::com_obj& rs) const; - - /// - /// Returns character translations by set - /// - /// \param[in ] set Translation set ID - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectTranslations(short set, winstd::com_obj& rs) const; - - /// - /// Returns translation data - /// - /// \param[in] rs Recordset with results - /// \param[out] t Translation - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetTranslation(const winstd::com_obj& rs, translation& t) const; - - /// - /// Returns translation sets - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectTranlationSets(winstd::com_obj& rs) const; - - /// - /// Returns translation set data - /// - /// \param[in] rs Recordset with results - /// \param[out] lang Language - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetTranslationSet(const winstd::com_obj& rs, transet& ts) const; - - /// - /// Returns translation sequences - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectTranlationSeqs(winstd::com_obj& rs) const; - - /// - /// Returns translation sequence data - /// - /// \param[in] rs Recordset with results - /// \param[out] lang Language - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetTranslationSeq(const winstd::com_obj& rs, transeq& ts) const; - - /// - /// Returns key sequences - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectKeySequences(winstd::com_obj& rs) const; - - /// - /// Returns key sequence data - /// - /// \param[in] rs Recordset with results - /// \param[out] ks Key sequence - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetKeySequence(const winstd::com_obj& rs, keyseq& ks) const; - - /// - /// Returns languages - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectLanguages(winstd::com_obj& rs) const; - - /// - /// Returns language data - /// - /// \param[in] rs Recordset with results - /// \param[out] lang Language - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetLanguage(const winstd::com_obj& rs, language& lang) const; - - /// - /// Returns language character - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectLanguageCharacters(winstd::com_obj& rs) const; - - /// - /// Returns language character data - /// - /// \param[in] rs Recordset with results - /// \param[out] lang Language character data - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetLanguageCharacter(const winstd::com_obj& rs, langchar& lc) const; - - /// - /// Returns character groups - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectCharacterGroups(winstd::com_obj& rs) const; - - /// - /// Returns character group data - /// - /// \param[in] rs Recordset with results - /// \param[out] cg Character group - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetCharacterGroup(const winstd::com_obj& rs, chrgrp& cg) const; - - /// - /// Returns characters - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectCharacters(winstd::com_obj& rs) const; - - /// - /// Returns character data - /// - /// \param[in] rs Recordset with results - /// \param[out] chr Character - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetCharacter(const winstd::com_obj& rs, character& chr) const; - - /// - /// Returns character categories - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectCharacterCategories(winstd::com_obj& rs) const; - - /// - /// Returns character category data - /// - /// \param[in] rs Recordset with results - /// \param[out] cc Character category - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetCharacterCategory(const winstd::com_obj& rs, chrcat& cc) const; - - /// - /// Returns character tags - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectCharacterTags(winstd::com_obj& rs) const; - - /// - /// Returns character tag data - /// - /// \param[in] rs Recordset with results - /// \param[out] cc Character tag - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetCharacterTag(const winstd::com_obj& rs, chrtag& tc) const; - - /// - /// Returns tag names - /// - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectTagNames(winstd::com_obj& rs) const; - - /// - /// Returns tag name data - /// - /// \param[in] rs Recordset with results - /// \param[out] tn Tag name - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetTagName(const winstd::com_obj& rs, tagname& tn) const; - - /// - /// Returns character highlights by set - /// - /// \param[in ] set Highlight set ID - /// \param[out] rs Recordset with results - /// - /// \returns - /// - true when query succeeds - /// - false otherwise - /// - bool SelectHighlights(short set, winstd::com_obj& rs) const; - - /// - /// Returns highlight data - /// - /// \param[in] rs Recordset with results - /// \param[out] h Highlight - /// - /// \returns - /// - true when succeeded - /// - false otherwise - /// - bool GetHighlight(const winstd::com_obj& rs, highlight& h) const; - - protected: - std::basic_string m_filename; ///< Database filename - winstd::com_obj m_db; ///< Database - _locale_t m_locale; ///< Database locale - - winstd::com_obj m_comCharacterGroup; ///< ADO Command for GetCharacterGroup subquery - winstd::com_obj m_pCharacterGroup1; ///< \c m_comCharacterGroup parameter - - winstd::com_obj m_comTranslation; ///< ADO Command for SelectTranslations subquery - winstd::com_obj m_pTranslation1; ///< \c m_comTranslations parameter - - winstd::com_obj m_comTranslationSets; ///< ADO Command for GetTranslationSeq subquery - winstd::com_obj m_pTranslationSets1; ///< \c m_comTranslationSets parameter - - winstd::com_obj m_comHighlight; ///< ADO Command for SelectHighlights subquery - winstd::com_obj m_pHighlight1; ///< \c m_comHighlights parameter - - std::set m_terms_ignore; ///< Terms to ignore when comparing characters - }; -}; - - -inline ZRCola::translation_db& operator<<(_Inout_ ZRCola::translation_db &db, _In_ const ZRCola::DBSource::translation &rec) -{ - uint32_t idx = db.data.size(); - db.data.push_back((uint16_t)rec.set); - db.data.push_back((uint16_t)rec.dst.rank); - db.data.push_back((uint16_t)rec.src.rank); - std::wstring::size_type n = rec.dst.str.length(); - wxASSERT_MSG(n <= 0xffff, wxT("destination overflow")); - db.data.push_back((uint16_t)n); - n += rec.src.str.length(); - wxASSERT_MSG(n <= 0xffff, wxT("source overflow")); - db.data.push_back((uint16_t)n); - db.data.insert(db.data.end(), rec.dst.str.cbegin(), rec.dst.str.cend()); - db.data.insert(db.data.end(), rec.src.str.cbegin(), rec.src.str.cend()); - db.idxSrc.push_back(idx); - db.idxDst.push_back(idx); - - return db; -} - - -inline ZRCola::transet_db& operator<<(_Inout_ ZRCola::transet_db &db, _In_ const ZRCola::DBSource::transet &rec) -{ - uint32_t idx = db.data.size(); - db.data.push_back((uint16_t)rec.set); - std::wstring::size_type n = rec.src.length(); - wxASSERT_MSG(n <= 0xffff, wxT("translation set source name overflow")); - db.data.push_back((uint16_t)n); - n += rec.dst.length(); - wxASSERT_MSG(n <= 0xffff, wxT("translation set destination name overflow")); - db.data.push_back((uint16_t)n); - db.data.insert(db.data.end(), rec.src.cbegin(), rec.src.cend()); - db.data.insert(db.data.end(), rec.dst.cbegin(), rec.dst.cend()); - db.idxTranSet.push_back(idx); - - return db; -} - - -inline ZRCola::transeq_db& operator<<(_Inout_ ZRCola::transeq_db &db, _In_ const ZRCola::DBSource::transeq &rec) -{ - uint32_t idx = db.data.size(); - db.data.push_back((uint16_t)rec.seq); - db.data.push_back((uint16_t)rec.rank); - std::wstring::size_type n = rec.name.length(); - wxASSERT_MSG(n <= 0xffff, wxT("translation sequence name overflow")); - db.data.push_back((uint16_t)n); - n += rec.sets.size(); - wxASSERT_MSG(n <= 0xffff, wxT("translation sequence sets overflow")); - db.data.push_back((uint16_t)n); - db.data.insert(db.data.end(), rec.name.cbegin(), rec.name.cend()); - for (auto s = rec.sets.cbegin(), s_end = rec.sets.cend(); s != s_end; ++s) - db.data.push_back((uint16_t)*s); - db.idxTranSeq.push_back(idx); - db.idxRank .push_back(idx); - - return db; -} - - -inline ZRCola::keyseq_db& operator<<(_Inout_ ZRCola::keyseq_db &db, _In_ const ZRCola::DBSource::keyseq &rec) -{ - uint32_t idx = db.data.size(); - std::wstring::size_type n = rec.chr.length(); - wxASSERT_MSG(n <= 0xffff, wxT("character overflow")); - db.data.push_back((uint16_t)n); - n += rec.seq.size() * sizeof(ZRCola::keyseq_db::keyseq::key_t) / sizeof(wchar_t); - wxASSERT_MSG(n <= 0xffff, wxT("key sequence overflow")); - db.data.push_back((uint16_t)n); - db.data.insert(db.data.end(), rec.chr.cbegin(), rec.chr.cend()); - for (auto kc = rec.seq.cbegin(), kc_end = rec.seq.cend(); kc != kc_end; ++kc) { - db.data.push_back(kc->key); - db.data.push_back( - (kc->shift ? ZRCola::keyseq_db::keyseq::SHIFT : 0) | - (kc->ctrl ? ZRCola::keyseq_db::keyseq::CTRL : 0) | - (kc->alt ? ZRCola::keyseq_db::keyseq::ALT : 0)); - } - db.idxChr.push_back(idx); - db.idxKey.push_back(idx); - - return db; -} - - -inline ZRCola::language_db& operator<<(_Inout_ ZRCola::language_db &db, _In_ const ZRCola::DBSource::language &rec) -{ - uint32_t idx = db.data.size(); - db.data.insert(db.data.end(), reinterpret_cast(&rec.lang), reinterpret_cast(&rec.lang + 1)); - std::wstring::size_type n = rec.name.length(); - wxASSERT_MSG(n <= 0xffff, wxT("language name overflow")); - db.data.push_back((uint16_t)n); - db.data.insert(db.data.end(), rec.name.cbegin(), rec.name.cend()); - db.idxLang.push_back(idx); - - return db; -} - - -inline ZRCola::langchar_db& operator<<(_Inout_ ZRCola::langchar_db &db, _In_ const ZRCola::DBSource::langchar &rec) -{ - uint32_t idx = db.data.size(); - db.data.insert(db.data.end(), reinterpret_cast(&rec.lang), reinterpret_cast(&rec.lang + 1)); - std::wstring::size_type n = rec.chr.length(); - wxASSERT_MSG(n <= 0xffff, wxT("character overflow")); - db.data.push_back((uint16_t)n); - db.data.insert(db.data.end(), rec.chr.cbegin(), rec.chr.cend()); - db.idxChr .push_back(idx); -#ifdef ZRCOLA_LANGCHAR_LANG_IDX - db.idxLang.push_back(idx); -#endif - - return db; -} - - -inline ZRCola::chrgrp_db& operator<<(_Inout_ ZRCola::chrgrp_db &db, _In_ const ZRCola::DBSource::chrgrp &rec) -{ - uint32_t idx = db.data.size(); - db.data.push_back((uint16_t)rec.grp); - db.data.push_back((uint16_t)rec.rank); - std::wstring::size_type n = rec.name.length(); - wxASSERT_MSG(n <= 0xffff, wxT("character group name overflow")); - db.data.push_back((uint16_t)n); - n += rec.chars.size(); - wxASSERT_MSG(n <= 0xffff, wxT("character group characters overflow")); - db.data.push_back((uint16_t)n); - db.data.insert(db.data.end(), rec.name .cbegin(), rec.name .cend()); - db.data.insert(db.data.end(), rec.chars.cbegin(), rec.chars.cend()); - db.data.insert(db.data.end(), rec.show .cbegin(), rec.show .cend()); - db.idxRank.push_back(idx); - - return db; -} - - -inline ZRCola::character_db& operator<<(_Inout_ ZRCola::character_db &db, _In_ const ZRCola::DBSource::character &rec) -{ - uint32_t idx = db.data.size(); - db.data.insert(db.data.end(), reinterpret_cast(&rec.second.cat), reinterpret_cast(&rec.second.cat + 1)); - std::wstring::size_type n = rec.first.length(); - wxASSERT_MSG(n <= 0xffff, wxT("character overflow")); - db.data.push_back((uint16_t)n); - n += rec.second.desc.length(); - wxASSERT_MSG(n <= 0xffff, wxT("character description overflow")); - db.data.push_back((uint16_t)n); - n += rec.second.rel.size(); - wxASSERT_MSG(n <= 0xffff, wxT("related characters overflow")); - db.data.push_back((uint16_t)n); - db.data.insert(db.data.end(), rec.first .cbegin(), rec.first .cend()); - db.data.insert(db.data.end(), rec.second.desc.cbegin(), rec.second.desc.cend()); - db.data.insert(db.data.end(), rec.second.rel .cbegin(), rec.second.rel .cend()); - db.idxChr.push_back(idx); - - return db; -} - - -inline ZRCola::chrcat_db& operator<<(_Inout_ ZRCola::chrcat_db &db, _In_ const ZRCola::DBSource::chrcat &rec) -{ - uint32_t idx = db.data.size(); - db.data.insert(db.data.end(), reinterpret_cast(&rec.cat), reinterpret_cast(&rec.cat + 1)); - db.data.push_back((uint16_t)rec.rank); - std::wstring::size_type n = rec.name.length(); - wxASSERT_MSG(n <= 0xffff, wxT("character category name overflow")); - db.data.push_back((uint16_t)n); - db.data.insert(db.data.end(), rec.name.cbegin(), rec.name.cend()); - db.idxChrId.push_back(idx); - db.idxRank .push_back(idx); - - return db; -} - - -inline ZRCola::chrtag_db& operator<<(_Inout_ ZRCola::chrtag_db &db, _In_ const ZRCola::DBSource::chrtag &rec) -{ - uint32_t idx = db.data.size(); - db.data.push_back((uint16_t)rec.tag); - std::wstring::size_type n = rec.chr.length(); - wxASSERT_MSG(n <= 0xffff, wxT("character overflow")); - db.data.push_back((uint16_t)n); - db.data.insert(db.data.end(), rec.chr.cbegin(), rec.chr.cend()); - db.idxChr.push_back(idx); - db.idxTag.push_back(idx); - - return db; -} - - -inline ZRCola::tagname_db& operator<<(_Inout_ ZRCola::tagname_db &db, _In_ const ZRCola::DBSource::tagname &rec) -{ - for (auto ln = rec.names.cbegin(), ln_end = rec.names.cend(); ln != ln_end; ++ln) { - for (auto nm = ln->second.cbegin(), nm_end = ln->second.cend(); nm != nm_end; ++nm) { - uint32_t idx = db.data.size(); - db.data.push_back((uint16_t)rec.tag); - db.data.push_back(LOWORD(ln->first)); - db.data.push_back(HIWORD(ln->first)); - std::wstring::size_type n = nm->length(); - wxASSERT_MSG(n <= 0xffff, wxT("tag name overflow")); - db.data.push_back((uint16_t)n); - db.data.insert(db.data.end(), nm->cbegin(), nm->cend()); - db.idxName.push_back(idx); - db.idxTag .push_back(idx); - } - } - - return db; -} - - -inline ZRCola::highlight_db& operator<<(_Inout_ ZRCola::highlight_db &db, _In_ const ZRCola::DBSource::highlight &rec) -{ - uint32_t idx = db.data.size(); - db.data.push_back((uint16_t)rec.set); - std::wstring::size_type n = rec.chr.length(); - wxASSERT_MSG(n <= 0xffff, wxT("character overflow")); - db.data.push_back((uint16_t)n); - db.data.insert(db.data.end(), rec.chr.cbegin(), rec.chr.cend()); - db.idxChr.push_back(idx); - - return db; -} +/* + SPDX-License-Identifier: GPL-3.0-or-later + Copyright © 2015-2022 Amebis +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#pragma warning(push) +#pragma warning(disable: WXWIDGETS_CODE_ANALYSIS_WARNINGS) +#include +#pragma warning(pop) + +#pragma warning(push) +#pragma warning(disable: 4091) +#include +#pragma warning(pop) +#include +#include +#include +#include +#include +#include + + +namespace ZRCola { + /// + /// Source database + /// + class DBSource + { + public: + /// + /// Character sequence + /// + struct charseq { + short rank = 0; ///< Sequence rank + std::wstring str; ///< Sequence string + + charseq() = default; + + charseq(_In_ short _rank, _In_z_ const wchar_t *_str) : + rank(_rank), + str (_str) + { + } + + charseq(_In_ short _rank, _In_ const std::wstring &_str) : + rank(_rank), + str (_str) + { + } + + charseq(_In_ short _rank, _Inout_ std::wstring &&_str) : + rank(_rank), + str (std::move(_str)) + { + } + + bool operator==(_In_ const charseq &other) const + { + return rank == other.rank && str == other.str; + } + + bool operator!=(_In_ const charseq &other) const + { + return !operator==(other); + } + + bool operator<(_In_ const charseq &other) const + { + if (rank < other.rank) return true; + else if (rank > other.rank) return false; + else if (str < other.str ) return true; + else return false; + } + + bool operator<=(_In_ const charseq &other) const + { + return !operator>(other); + } + + bool operator>(_In_ const charseq &other) const + { + return other.operator<(*this); + } + + bool operator>=(_In_ const charseq &other) const + { + return !operator<(other); + } + }; + + + /// + /// Translation + /// + struct translation { + short set = (short)ZRCOLA_TRANSETID_DEFAULT; ///< Translation set ID + charseq src; ///< Source sequence + std::string norm; ///< Normalization footprint + charseq dst; ///< Destination sequence + }; + + + /// + /// Translation set + /// + struct transet { + short set = (short)ZRCOLA_TRANSETID_DEFAULT; ///< ID + std::wstring src; ///< Source name + std::wstring dst; ///< Destination name + }; + + + /// + /// Translation sequence + /// + struct transeq { + short seq = 0; ///< ID + short rank = 0; ///< Rank + std::wstring name; ///< Name + std::vector sets; ///< Sets + }; + + + /// + /// Normalization permutation set + /// + typedef std::set > normperm; + + + /// + /// Key sequence + /// + struct keyseq { + /// + /// Key code + /// + struct keycode { + wchar_t key; ///< Key + bool shift; ///< Shift modifier + bool ctrl; ///< Ctrl modifier + bool alt; ///< Alt modifier + + /// + /// Translates keycode from Slovenian to English keyboard + /// + static wchar_t translate_slen(_In_ wchar_t key) + { + switch (key) { + case L'Z': return L'Y'; + case L'Y': return L'Z'; + case 191: return 189; + case 189: return 191; + default : return key; + } + } + }; + + std::wstring chr; ///< Character + std::vector seq; ///< Key sequence + }; + + + /// + /// Language + /// + struct language { + ZRCola::langid_t lang; ///< Language ID + std::wstring name; ///< Name + }; + + + /// + /// Language Character + /// + struct langchar { + std::wstring chr; ///> Character + ZRCola::langid_t lang; ///< Language ID + }; + + + /// + /// Character group + /// + struct chrgrp { + short grp = 0; ///< Character group ID + short rank = 0; ///< Rank + std::wstring name; ///< Name + std::vector chars; ///< Characters (zero-delimited) + std::vector show; ///< Bit vector if particular character from \c chars is displayed initially + }; + + + /// + /// Character data + /// + struct character_data { + ZRCola::chrcatid_t cat; ///< Category ID + std::wstring desc; ///< Character description + std::set terms; ///< Search terms + std::set terms_rel; ///< Relevant terms for relating characters + std::vector rel; ///< Related characters (zero-delimited) + short blk = 0; ///> Block ID + }; + + + /// + /// Character block + /// + struct chrblk_data { + short id = 0; ///> Block ID + char32_t chr_end = 0; ///> Last character in the block + short rank = 0; ///< Rank + std::wstring name; ///< Name + bool used = false; ///< Are there any characters from this block + }; + + + /// + /// Character block + /// + using chrblk = std::pair; + + + /// + /// Character + /// + using character = std::pair; + + + /// + /// Character bank + /// + class character_bank : public std::map + { + public: + void build_related(); + + protected: + class build_related_worker : public winstd::thread + { + public: + build_related_worker(_In_ const character_bank &cb, _In_ iterator from, _In_ iterator to); + + void join() + { + if (m_h != invalid) + WaitForSingleObject(m_h, INFINITE); + } + + private: + // This class is non-copyable AND non-movable + build_related_worker(_Inout_ build_related_worker &othr); + build_related_worker(_Inout_ build_related_worker &&othr); + build_related_worker& operator=(_Inout_ build_related_worker &othr); + build_related_worker& operator=(_Inout_ build_related_worker &&othr); + + protected: + unsigned int process(); + static unsigned int __stdcall process(_In_ void *param); + + protected: + const character_bank &m_cb; + iterator m_from, m_to; + winstd::heap m_heap; + }; + + public: + std::map idxChrBlk; + }; + + + /// + /// Character comparator + /// + struct character_less + { + bool operator()(const std::wstring& a, const std::wstring& b) const + { + auto &coll = std::use_facet>(std::locale()); + return coll.compare( + a.data(), a.data() + a.size(), + b.data(), b.data() + b.size()) < 0; + } + }; + + + /// + /// Character description index + /// + class character_desc_idx : public std::map, character_less> + { + public: + static void parse_keywords(_In_ const wchar_t *str, _Inout_ std::set &terms); + void add_keywords(const std::set &terms, const std::wstring &chr, size_t sub = 0); + void add_keywords(const wchar_t *str, const std::wstring &chr, size_t sub = 0) + { + std::set terms; + parse_keywords(str, terms); + add_keywords(terms, chr, sub); + } + + void save(ZRCola::textindex &idx) const; + + protected: + void add_keyword(const std::wstring &term, const std::wstring &chr) + { + iterator idx = find(term); + if (idx == end()) { + // New keyword. + insert(std::make_pair(term, mapped_type(chr.data(), chr.data() + chr.length() + 1))); + } else { + // Append to existing keyword. + auto &val = idx->second; + for (mapped_type::size_type i = 0, n = val.size(); ; i += stdex::strnlen(val.data() + i, n - i) + 1) { + if (i >= n) { + // End-of-values reached. Append character. + val.insert(val.end(), chr.data(), chr.data() + chr.length() + 1); + break; + } else if (chr.compare(val.data() + i) == 0) { + // Character already among the values. + break; + } + } + } + } + }; + + + /// + /// Character category + /// + struct chrcat { + ZRCola::chrcatid_t cat; ///> Category ID + short rank = 0; ///< Rank + std::wstring name; ///< Name + }; + + + /// + /// Character tag + /// + struct chrtag { + std::wstring chr; ///> Character + short tag = 0; ///< Tag ID + }; + + + /// + /// Tag name + /// + struct tagname { + short tag = 0; ///< Tag ID + std::map > names; ///< Names + }; + + + /// + /// Highlight + /// + struct highlight { + short set = (short)ZRCOLA_HLGHTSETID_DEFAULT; ///< Highlight set ID + std::wstring chr; ///< Character sequence + }; + + + public: + DBSource(); + virtual ~DBSource(); + + /// + /// Opens the database + /// + /// \param[in] filename File name of the MDB database. + /// + /// \returns + /// - true when open succeeds + /// - false otherwise + /// + bool Open(LPCTSTR filename); + + /// + /// Logs errors in database connections + /// + void LogErrors() const; + + /// + /// Is recordset at end + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when at end + /// - false otherwise + /// + static bool IsEOF(const winstd::com_obj& rs) + { + VARIANT_BOOL eof = VARIANT_TRUE; + return FAILED(rs->get_EOF(&eof)) || eof ? true : false; + } + + /// + /// Gets number of records in a recordset + /// + /// \param[out] rs Recordset with results + /// + /// \returns Number of records + /// + static size_t GetRecordsetCount(const winstd::com_obj& rs) + { + ADO_LONGPTR count; + return SUCCEEDED(rs->get_RecordCount(&count)) ? count : (size_t)-1; + } + + /// + /// Gets boolean from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] val Output boolean value + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetValue(const winstd::com_obj& f, bool& val) const; + + /// + /// Gets integer from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] val Output integer value + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetValue(const winstd::com_obj& f, short& val) const; + + /// + /// Gets string from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] val Output string value + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetValue(const winstd::com_obj& f, std::string& val) const; + + /// + /// Gets string from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] val Output string value + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetValue(const winstd::com_obj& f, std::wstring& val) const; + + /// + /// Gets encoded Unicode character from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] chr Output character + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetUnicodeCharacter(const winstd::com_obj& f, char32_t& chr) const; + + /// + /// Gets encoded UTF-16 string from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] str Output string + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetUnicodeString(const winstd::com_obj& f, std::wstring& str) const; + + /// + /// Gets encoded normalization permutations from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] str Output normalization permutation set + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetNormPerm(const winstd::com_obj& f, normperm& np) const; + + /// + /// Gets language ID from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] lang Language + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetLanguage(const winstd::com_obj& f, langid_t& lang) const; + + /// + /// Gets character category ID from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] cc Character category + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetChrCat(const winstd::com_obj& f, chrcatid_t& cc) const; + + /// + /// Gets tag names from ZRCola.zrc database + /// + /// \param[in] f Data field + /// \param[out] names Output names + /// + /// \returns + /// - true when successful + /// - false otherwise + /// + bool GetTagNames(const winstd::com_obj& f, LCID lcid, std::list& names) const; + + /// + /// Returns normalization permutation sets + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectNormPermSets(winstd::com_obj& rs) const; + + /// + /// Returns normalization permutation set + /// + /// \param[in] rs Recordset with results + /// \param[out] np Normalization permutation set + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetNormPerm(const winstd::com_obj& rs, std::string& norm, normperm& np) const; + + /// + /// Returns character translations + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectTranslations(winstd::com_obj& rs) const; + + /// + /// Returns character translations by set + /// + /// \param[in ] set Translation set ID + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectTranslations(short set, winstd::com_obj& rs) const; + + /// + /// Returns translation data + /// + /// \param[in] rs Recordset with results + /// \param[out] t Translation + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetTranslation(const winstd::com_obj& rs, translation& t) const; + + /// + /// Returns translation sets + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectTranlationSets(winstd::com_obj& rs) const; + + /// + /// Returns translation set data + /// + /// \param[in] rs Recordset with results + /// \param[out] lang Language + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetTranslationSet(const winstd::com_obj& rs, transet& ts) const; + + /// + /// Returns translation sequences + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectTranlationSeqs(winstd::com_obj& rs) const; + + /// + /// Returns translation sequence data + /// + /// \param[in] rs Recordset with results + /// \param[out] lang Language + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetTranslationSeq(const winstd::com_obj& rs, transeq& ts) const; + + /// + /// Returns key sequences + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectKeySequences(winstd::com_obj& rs) const; + + /// + /// Returns key sequence data + /// + /// \param[in] rs Recordset with results + /// \param[out] ks Key sequence + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetKeySequence(const winstd::com_obj& rs, keyseq& ks) const; + + /// + /// Returns languages + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectLanguages(winstd::com_obj& rs) const; + + /// + /// Returns language data + /// + /// \param[in] rs Recordset with results + /// \param[out] lang Language + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetLanguage(const winstd::com_obj& rs, language& lang) const; + + /// + /// Returns language character + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectLanguageCharacters(winstd::com_obj& rs) const; + + /// + /// Returns language character data + /// + /// \param[in] rs Recordset with results + /// \param[out] lang Language character data + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetLanguageCharacter(const winstd::com_obj& rs, langchar& lc) const; + + /// + /// Returns character groups + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectCharacterGroups(winstd::com_obj& rs) const; + + /// + /// Returns character group data + /// + /// \param[in] rs Recordset with results + /// \param[out] cg Character group + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetCharacterGroup(const winstd::com_obj& rs, chrgrp& cg) const; + + /// + /// Returns characters + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectCharacters(winstd::com_obj& rs) const; + + /// + /// Returns character data + /// + /// \param[in] rs Recordset with results + /// \param[out] chr Character + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetCharacter(const winstd::com_obj& rs, character& chr) const; + + /// + /// Returns character blocks + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectCharacterBlocks(winstd::com_obj& rs) const; + + /// + /// Returns character category data + /// + /// \param[in] rs Recordset with results + /// \param[out] cc Character category + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetCharacterBlock(const winstd::com_obj& rs, chrblk& cb) const; + + /// + /// Returns character categories + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectCharacterCategories(winstd::com_obj& rs) const; + + /// + /// Returns character category data + /// + /// \param[in] rs Recordset with results + /// \param[out] cc Character category + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetCharacterCategory(const winstd::com_obj& rs, chrcat& cc) const; + + /// + /// Returns character tags + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectCharacterTags(winstd::com_obj& rs) const; + + /// + /// Returns character tag data + /// + /// \param[in] rs Recordset with results + /// \param[out] cc Character tag + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetCharacterTag(const winstd::com_obj& rs, chrtag& tc) const; + + /// + /// Returns tag names + /// + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectTagNames(winstd::com_obj& rs) const; + + /// + /// Returns tag name data + /// + /// \param[in] rs Recordset with results + /// \param[out] tn Tag name + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetTagName(const winstd::com_obj& rs, tagname& tn) const; + + /// + /// Returns character highlights by set + /// + /// \param[in ] set Highlight set ID + /// \param[out] rs Recordset with results + /// + /// \returns + /// - true when query succeeds + /// - false otherwise + /// + bool SelectHighlights(short set, winstd::com_obj& rs) const; + + /// + /// Returns highlight data + /// + /// \param[in] rs Recordset with results + /// \param[out] h Highlight + /// + /// \returns + /// - true when succeeded + /// - false otherwise + /// + bool GetHighlight(const winstd::com_obj& rs, highlight& h) const; + + protected: + std::basic_string m_filename; ///< Database filename + winstd::com_obj m_db; ///< Database + _locale_t m_locale; ///< Database locale + + winstd::com_obj m_comCharacterGroup; ///< ADO Command for GetCharacterGroup subquery + winstd::com_obj m_pCharacterGroup1; ///< \c m_comCharacterGroup parameter + + winstd::com_obj m_comTranslation; ///< ADO Command for SelectTranslations subquery + winstd::com_obj m_pTranslation1; ///< \c m_comTranslations parameter + + winstd::com_obj m_comTranslationSets; ///< ADO Command for GetTranslationSeq subquery + winstd::com_obj m_pTranslationSets1; ///< \c m_comTranslationSets parameter + + winstd::com_obj m_comHighlight; ///< ADO Command for SelectHighlights subquery + winstd::com_obj m_pHighlight1; ///< \c m_comHighlights parameter + + std::set m_terms_ignore; ///< Terms to ignore when comparing characters + }; +}; + + +inline ZRCola::translation_db& operator<<(_Inout_ ZRCola::translation_db &db, _In_ const ZRCola::DBSource::translation &rec) +{ + uint32_t idx = db.data.size(); + db.data.push_back((uint16_t)rec.set); + db.data.push_back((uint16_t)rec.dst.rank); + db.data.push_back((uint16_t)rec.src.rank); + std::wstring::size_type n = rec.dst.str.length(); + wxASSERT_MSG(n <= 0xffff, wxT("destination overflow")); + db.data.push_back((uint16_t)n); + n += rec.src.str.length(); + wxASSERT_MSG(n <= 0xffff, wxT("source overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), rec.dst.str.cbegin(), rec.dst.str.cend()); + db.data.insert(db.data.end(), rec.src.str.cbegin(), rec.src.str.cend()); + db.idxSrc.push_back(idx); + db.idxDst.push_back(idx); + + return db; +} + + +inline ZRCola::transet_db& operator<<(_Inout_ ZRCola::transet_db &db, _In_ const ZRCola::DBSource::transet &rec) +{ + uint32_t idx = db.data.size(); + db.data.push_back((uint16_t)rec.set); + std::wstring::size_type n = rec.src.length(); + wxASSERT_MSG(n <= 0xffff, wxT("translation set source name overflow")); + db.data.push_back((uint16_t)n); + n += rec.dst.length(); + wxASSERT_MSG(n <= 0xffff, wxT("translation set destination name overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), rec.src.cbegin(), rec.src.cend()); + db.data.insert(db.data.end(), rec.dst.cbegin(), rec.dst.cend()); + db.idxTranSet.push_back(idx); + + return db; +} + + +inline ZRCola::transeq_db& operator<<(_Inout_ ZRCola::transeq_db &db, _In_ const ZRCola::DBSource::transeq &rec) +{ + uint32_t idx = db.data.size(); + db.data.push_back((uint16_t)rec.seq); + db.data.push_back((uint16_t)rec.rank); + std::wstring::size_type n = rec.name.length(); + wxASSERT_MSG(n <= 0xffff, wxT("translation sequence name overflow")); + db.data.push_back((uint16_t)n); + n += rec.sets.size(); + wxASSERT_MSG(n <= 0xffff, wxT("translation sequence sets overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), rec.name.cbegin(), rec.name.cend()); + for (auto s = rec.sets.cbegin(), s_end = rec.sets.cend(); s != s_end; ++s) + db.data.push_back((uint16_t)*s); + db.idxTranSeq.push_back(idx); + db.idxRank .push_back(idx); + + return db; +} + + +inline ZRCola::keyseq_db& operator<<(_Inout_ ZRCola::keyseq_db &db, _In_ const ZRCola::DBSource::keyseq &rec) +{ + uint32_t idx = db.data.size(); + std::wstring::size_type n = rec.chr.length(); + wxASSERT_MSG(n <= 0xffff, wxT("character overflow")); + db.data.push_back((uint16_t)n); + n += rec.seq.size() * sizeof(ZRCola::keyseq_db::keyseq::key_t) / sizeof(wchar_t); + wxASSERT_MSG(n <= 0xffff, wxT("key sequence overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), rec.chr.cbegin(), rec.chr.cend()); + for (auto kc = rec.seq.cbegin(), kc_end = rec.seq.cend(); kc != kc_end; ++kc) { + db.data.push_back(kc->key); + db.data.push_back( + (kc->shift ? ZRCola::keyseq_db::keyseq::SHIFT : 0) | + (kc->ctrl ? ZRCola::keyseq_db::keyseq::CTRL : 0) | + (kc->alt ? ZRCola::keyseq_db::keyseq::ALT : 0)); + } + db.idxChr.push_back(idx); + db.idxKey.push_back(idx); + + return db; +} + + +inline ZRCola::language_db& operator<<(_Inout_ ZRCola::language_db &db, _In_ const ZRCola::DBSource::language &rec) +{ + uint32_t idx = db.data.size(); + db.data.insert(db.data.end(), reinterpret_cast(&rec.lang), reinterpret_cast(&rec.lang + 1)); + std::wstring::size_type n = rec.name.length(); + wxASSERT_MSG(n <= 0xffff, wxT("language name overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), rec.name.cbegin(), rec.name.cend()); + db.idxLang.push_back(idx); + + return db; +} + + +inline ZRCola::langchar_db& operator<<(_Inout_ ZRCola::langchar_db &db, _In_ const ZRCola::DBSource::langchar &rec) +{ + uint32_t idx = db.data.size(); + db.data.insert(db.data.end(), reinterpret_cast(&rec.lang), reinterpret_cast(&rec.lang + 1)); + std::wstring::size_type n = rec.chr.length(); + wxASSERT_MSG(n <= 0xffff, wxT("character overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), rec.chr.cbegin(), rec.chr.cend()); + db.idxChr .push_back(idx); +#ifdef ZRCOLA_LANGCHAR_LANG_IDX + db.idxLang.push_back(idx); +#endif + + return db; +} + + +inline ZRCola::chrgrp_db& operator<<(_Inout_ ZRCola::chrgrp_db &db, _In_ const ZRCola::DBSource::chrgrp &rec) +{ + uint32_t idx = db.data.size(); + db.data.push_back((uint16_t)rec.grp); + db.data.push_back((uint16_t)rec.rank); + std::wstring::size_type n = rec.name.length(); + wxASSERT_MSG(n <= 0xffff, wxT("character group name overflow")); + db.data.push_back((uint16_t)n); + n += rec.chars.size(); + wxASSERT_MSG(n <= 0xffff, wxT("character group characters overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), rec.name .cbegin(), rec.name .cend()); + db.data.insert(db.data.end(), rec.chars.cbegin(), rec.chars.cend()); + db.data.insert(db.data.end(), rec.show .cbegin(), rec.show .cend()); + db.idxRank.push_back(idx); + + return db; +} + + +inline ZRCola::character_db& operator<<(_Inout_ ZRCola::character_db &db, _In_ const ZRCola::DBSource::character &rec) +{ + uint32_t idx = db.data.size(); + db.data.insert(db.data.end(), reinterpret_cast(&rec.second.cat), reinterpret_cast(&rec.second.cat + 1)); + db.data.push_back((uint16_t)rec.second.blk); + std::wstring::size_type n = rec.first.length(); + wxASSERT_MSG(n <= 0xffff, wxT("character overflow")); + db.data.push_back((uint16_t)n); + n += rec.second.desc.length(); + wxASSERT_MSG(n <= 0xffff, wxT("character description overflow")); + db.data.push_back((uint16_t)n); + n += rec.second.rel.size(); + wxASSERT_MSG(n <= 0xffff, wxT("related characters overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), rec.first .cbegin(), rec.first .cend()); + db.data.insert(db.data.end(), rec.second.desc.cbegin(), rec.second.desc.cend()); + db.data.insert(db.data.end(), rec.second.rel .cbegin(), rec.second.rel .cend()); + db.idxChr.push_back(idx); + + return db; +} + + +inline ZRCola::chrblk_db& operator<<(_Inout_ ZRCola::chrblk_db &db, _In_ const ZRCola::DBSource::chrblk &rec) +{ + uint32_t idx = db.data.size(); + db.data.push_back((uint16_t)rec.second.id); + db.data.push_back((uint16_t)rec.second.rank); + std::wstring::size_type n = rec.second.name.length(); + wxASSERT_MSG(n <= 0xffff, wxT("character block name overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), rec.second.name.cbegin(), rec.second.name.cend()); + db.idxChrId.push_back(idx); + db.idxRank .push_back(idx); + + return db; +} + + +inline ZRCola::chrcat_db& operator<<(_Inout_ ZRCola::chrcat_db &db, _In_ const ZRCola::DBSource::chrcat &rec) +{ + uint32_t idx = db.data.size(); + db.data.insert(db.data.end(), reinterpret_cast(&rec.cat), reinterpret_cast(&rec.cat + 1)); + db.data.push_back((uint16_t)rec.rank); + std::wstring::size_type n = rec.name.length(); + wxASSERT_MSG(n <= 0xffff, wxT("character category name overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), rec.name.cbegin(), rec.name.cend()); + db.idxChrId.push_back(idx); + db.idxRank .push_back(idx); + + return db; +} + + +inline ZRCola::chrtag_db& operator<<(_Inout_ ZRCola::chrtag_db &db, _In_ const ZRCola::DBSource::chrtag &rec) +{ + uint32_t idx = db.data.size(); + db.data.push_back((uint16_t)rec.tag); + std::wstring::size_type n = rec.chr.length(); + wxASSERT_MSG(n <= 0xffff, wxT("character overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), rec.chr.cbegin(), rec.chr.cend()); + db.idxChr.push_back(idx); + db.idxTag.push_back(idx); + + return db; +} + + +inline ZRCola::tagname_db& operator<<(_Inout_ ZRCola::tagname_db &db, _In_ const ZRCola::DBSource::tagname &rec) +{ + for (auto ln = rec.names.cbegin(), ln_end = rec.names.cend(); ln != ln_end; ++ln) { + for (auto nm = ln->second.cbegin(), nm_end = ln->second.cend(); nm != nm_end; ++nm) { + uint32_t idx = db.data.size(); + db.data.push_back((uint16_t)rec.tag); + db.data.push_back(LOWORD(ln->first)); + db.data.push_back(HIWORD(ln->first)); + std::wstring::size_type n = nm->length(); + wxASSERT_MSG(n <= 0xffff, wxT("tag name overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), nm->cbegin(), nm->cend()); + db.idxName.push_back(idx); + db.idxTag .push_back(idx); + } + } + + return db; +} + + +inline ZRCola::highlight_db& operator<<(_Inout_ ZRCola::highlight_db &db, _In_ const ZRCola::DBSource::highlight &rec) +{ + uint32_t idx = db.data.size(); + db.data.push_back((uint16_t)rec.set); + std::wstring::size_type n = rec.chr.length(); + wxASSERT_MSG(n <= 0xffff, wxT("character overflow")); + db.data.push_back((uint16_t)n); + db.data.insert(db.data.end(), rec.chr.cbegin(), rec.chr.cend()); + db.idxChr.push_back(idx); + + return db; +} diff --git a/ZRColaCompile/main.cpp b/ZRColaCompile/main.cpp index eb0ed70..c8636e6 100644 --- a/ZRColaCompile/main.cpp +++ b/ZRColaCompile/main.cpp @@ -756,7 +756,32 @@ int _tmain(int argc, _TCHAR *argv[]) ZRCola::DBSource::character_desc_idx idxChrDsc, idxChrDscSub; ZRCola::DBSource::character_bank chrs; - // Phase 1: Parse characters and build indexes. + // Phase 1: Get character blocks. + com_obj rs2; + if (src.SelectCharacterBlocks(rs2)) { + size_t count2 = src.GetRecordsetCount(rs2); + if (count2 < 0xffffffff) { // 4G check (-1 is reserved for error condition) + // Parse character blocks and build index and data. + for (; !ZRCola::DBSource::IsEOF(rs2); rs2->MoveNext()) { + // Read character block from the database. + ZRCola::DBSource::chrblk cb; + if (src.GetCharacterBlock(rs2, cb)) + chrs.idxChrBlk[cb.first] = std::move(cb.second); + else + has_errors = true; + } + } + else { + _ftprintf(stderr, wxT("%s: error ZCC0029: Error getting character block count from database or too many character blocks.\n"), (LPCTSTR)filenameIn.c_str()); + has_errors = true; + } + } + else { + _ftprintf(stderr, wxT("%s: error ZCC0028: Error getting character blocks from database. Please make sure the file is ZRCola.zrc compatible.\n"), (LPCTSTR)filenameIn.c_str()); + has_errors = true; + } + + // Phase 2: Parse characters and build indexes. for (; !ZRCola::DBSource::IsEOF(rs); rs->MoveNext()) { // Read character from the database. ZRCola::DBSource::character chr; @@ -766,33 +791,64 @@ int _tmain(int argc, _TCHAR *argv[]) has_errors = true; } - // Phase 2: Build related character lists. + // Phase 3: Build related character lists. chrs.build_related(); - ZRCola::character_db db; + { + ZRCola::character_db db; - // Preallocate memory. - db.idxChr.reserve(count); - db.data .reserve(count*4); + // Preallocate memory. + db.idxChr.reserve(count); + db.data.reserve(count * 4); - // Phase 3: Parse characters and build index and data. - for (auto chr = chrs.cbegin(), chr_end = chrs.cend(); chr != chr_end; ++chr) { - // Add character to index and data. - db << *chr; + // Phase 4: Parse characters and build index and data. + for (auto chr = chrs.cbegin(), chr_end = chrs.cend(); chr != chr_end; ++chr) { + // Add character to index and data. + db << *chr; - // Add description (and keywords) to index. - idxChrDsc .add_keywords(chr->second.terms, chr->first, 0); - idxChrDscSub.add_keywords(chr->second.terms, chr->first, 3); + // Add description (and keywords) to index. + idxChrDsc.add_keywords(chr->second.terms, chr->first, 0); + idxChrDscSub.add_keywords(chr->second.terms, chr->first, 3); - // Mark category used. - categories_used.insert(chr->second.cat); + // Mark category used. + categories_used.insert(chr->second.cat); + } + + // Write characters to file. + db.idxChr.sort(); + idxChrDsc.save(db.idxDsc); + idxChrDscSub.save(db.idxDscSub); + dst << ZRCola::character_rec(db); } - // Write characters to file. - db.idxChr.sort(); - idxChrDsc .save(db.idxDsc ); - idxChrDscSub.save(db.idxDscSub); - dst << ZRCola::character_rec(db); + { + ZRCola::chrblk_db db; + + // Preallocate memory. + db.idxChrId.reserve(chrs.idxChrBlk.size()); + db.idxRank.reserve(chrs.idxChrBlk.size()); + db.data.reserve(chrs.idxChrBlk.size() * 16); + + // Phase 5: Parse character blocks and build index and data. + for (auto& cb : chrs.idxChrBlk) + { + if (!cb.second.used) { + // Skip unused character blocks. + continue; + } + + if (build_pot) + pot.insert(cb.second.name); + + // Add character block to index and data. + db << cb; + } + + // Write character blocks to file. + db.idxChrId.sort(); + db.idxRank.sort(); + dst << ZRCola::chrblk_rec(db); + } } else { _ftprintf(stderr, wxT("%s: error ZCC0017: Error getting character count from database or too many characters.\n"), (LPCTSTR)filenameIn.c_str()); has_errors = true; diff --git a/lib/libZRCola/include/zrcola/character.h b/lib/libZRCola/include/zrcola/character.h index 1552c34..551df9a 100644 --- a/lib/libZRCola/include/zrcola/character.h +++ b/lib/libZRCola/include/zrcola/character.h @@ -171,6 +171,12 @@ namespace ZRCola { } + /// + /// Character block ID + /// + typedef uint16_t chrblkid_t; + + /// /// Character Database /// @@ -184,6 +190,7 @@ namespace ZRCola { struct character { public: chrcatid_t cat; ///> Character category ID + chrblkid_t blk; ///> Character block ID protected: uint16_t chr_to; ///< Character end in \c data @@ -202,6 +209,7 @@ namespace ZRCola { /// \param[in] chr Character /// \param[in] chr_len Number of UTF-16 characters in \p chr /// \param[in] cat Category + /// \param[in] blk Unicode block /// \param[in] desc Description /// \param[in] desc_len Number of UTF-16 characters in \p desc /// \param[in] rel Related characters list (zero delimited) @@ -211,12 +219,14 @@ namespace ZRCola { _In_opt_z_count_(chr_len) const char_t *chr = NULL, _In_opt_ size_t chr_len = 0, _In_opt_ chrcatid_t cat = chrcatid_t(), + _In_opt_ chrblkid_t blk = 0, _In_opt_z_count_(desc_len) const char_t *desc = NULL, _In_opt_ size_t desc_len = 0, _In_opt_z_count_(rel_len) const char_t *rel = NULL, _In_opt_ size_t rel_len = 0) { this->cat = cat; + this->blk = blk; this->chr_to = static_cast(chr_len); if (chr && chr_len) memcpy(this->data, chr, sizeof(char_t)*chr_len); this->desc_to = static_cast(this->chr_to + desc_len); @@ -330,6 +340,25 @@ namespace ZRCola { return idxChr.find(*c, start) ? idxChr[start].cat : chrcatid_t(); } + /// + /// Get character block + /// + /// \param[in] chr Character + /// \param[in] len Number of UTF-16 characters in \p chr + /// + /// \returns + /// - Character block if character found + /// - 0 otherwise + /// + chrblkid_t GetCharBlk(_In_z_count_(len) const char_t *chr, _In_ const size_t len) const + { + assert(len <= 0xffff); + std::unique_ptr c((character*)new char[sizeof(character) + sizeof(char_t)*len]); + new (c.get()) character(chr, len); + indexChr::size_type start; + return idxChr.find(*c, start) ? idxChr[start].blk : 0; + } + /// /// Writes character database to a stream /// @@ -650,6 +679,11 @@ namespace ZRCola { /// Character category database /// using chrcat_db = chrclass_db; + + /// + /// Character block database + /// + using chrblk_db = chrclass_db; }; #pragma warning(pop) diff --git a/lib/libZRCola/include/zrcola/idrec.h b/lib/libZRCola/include/zrcola/idrec.h index ea608fc..eee77a0 100644 --- a/lib/libZRCola/include/zrcola/idrec.h +++ b/lib/libZRCola/include/zrcola/idrec.h @@ -16,6 +16,7 @@ namespace ZRCola { typedef stdex::idrec::record character_rec; typedef stdex::idrec::record chrcat_rec; + typedef stdex::idrec::record chrblk_rec; typedef stdex::idrec::record highlight_rec; typedef stdex::idrec::record langchar_rec; typedef stdex::idrec::record language_rec; diff --git a/output/data/ZRCola.zrcdb b/output/data/ZRCola.zrcdb index 18fc63c2762d1aec364d84d4633e3931cc34a614..9cb82688b8ada044319aeb6fd92867b178c95286 100644 GIT binary patch delta 153414 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%0A_mKz!ks*jL#pvu79DZP{s!;QA(fk$t2-Z;}js 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