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 18fc63c..9cb8268 100644 Binary files a/output/data/ZRCola.zrcdb and b/output/data/ZRCola.zrcdb differ diff --git a/output/locale/ZRCola-zrcdb.pot b/output/locale/ZRCola-zrcdb.pot index 9bda179..cfca3f8 100644 --- a/output/locale/ZRCola-zrcdb.pot +++ b/output/locale/ZRCola-zrcdb.pot @@ -10,6 +10,9 @@ msgstr "" msgid "Albanian" msgstr "" +msgid "Alphabetic Presentation Forms" +msgstr "" + msgid "Apostrophes 1" msgstr "" @@ -19,24 +22,42 @@ msgstr "" msgid "Arabic" msgstr "" +msgid "Armenian" +msgstr "" + msgid "Arrows" msgstr "" msgid "Belarusian" msgstr "" +msgid "Block Elements" +msgstr "" + msgid "Bosnian – Cyrillic" msgstr "" msgid "Bosnian – Latinic" msgstr "" +msgid "Box Drawing" +msgstr "" + +msgid "C0 Controls and Basic Latin (Basic Latin)" +msgstr "" + +msgid "C1 Controls and Latin-1 Supplement (Latin-1 Supplement)" +msgstr "" + msgid "CAPITAL Case" msgstr "" msgid "CAPITAL Case » small Case" msgstr "" +msgid "CJK Symbols and Punctuation" +msgstr "" + msgid "Combine" msgstr "" @@ -49,6 +70,18 @@ msgstr "" msgid "Combine Over" msgstr "" +msgid "Combining Diacritical Marks" +msgstr "" + +msgid "Combining Diacritical Marks Supplement" +msgstr "" + +msgid "Combining Diacritical Marks for Symbols" +msgstr "" + +msgid "Combining Half Marks" +msgstr "" + msgid "Combining Marks" msgstr "" @@ -58,6 +91,9 @@ msgstr "" msgid "Currencies" msgstr "" +msgid "Currency Symbols" +msgstr "" + msgid "Cyrillic" msgstr "" @@ -118,6 +154,12 @@ msgstr "" msgid "Cyrillic BdC" msgstr "" +msgid "Cyrillic Extended-B" +msgstr "" + +msgid "Cyrillic Supplement" +msgstr "" + msgid "Cyrillic » Latin (GOST2000)" msgstr "" @@ -130,9 +172,15 @@ msgstr "" msgid "Diacritics" msgstr "" +msgid "Dingbats" +msgstr "" + msgid "Encircled Characters" msgstr "" +msgid "Enclosed Alphanumerics" +msgstr "" + msgid "English" msgstr "" @@ -148,6 +196,12 @@ msgstr "" msgid "Friulian" msgstr "" +msgid "General Punctuation" +msgstr "" + +msgid "Geometric Shapes" +msgstr "" + msgid "Geometrical Shapes" msgstr "" @@ -169,12 +223,24 @@ msgstr "" msgid "Greek (Old)" msgstr "" +msgid "Greek Extended" +msgstr "" + +msgid "Greek and Coptic" +msgstr "" + +msgid "Gujarati" +msgstr "" + msgid "Hebrew" msgstr "" msgid "Hungarian" msgstr "" +msgid "IPA Extensions" +msgstr "" + msgid "Irish Gaelic" msgstr "" @@ -295,6 +361,21 @@ msgstr "" msgid "Latin BdC" msgstr "" +msgid "Latin Extended Additional" +msgstr "" + +msgid "Latin Extended-A" +msgstr "" + +msgid "Latin Extended-B" +msgstr "" + +msgid "Latin Extended-C" +msgstr "" + +msgid "Latin Extended-D" +msgstr "" + msgid "Latin » Cyrillic (Belarusian)" msgstr "" @@ -433,6 +514,9 @@ msgstr "" msgid "Letter, Uppercase" msgstr "" +msgid "Letterlike Symbols" +msgstr "" + msgid "Ligatures" msgstr "" @@ -457,9 +541,27 @@ msgstr "" msgid "Mathematical And Physical Symbols" msgstr "" +msgid "Mathematical Operators" +msgstr "" + msgid "Metric" msgstr "" +msgid "Miscellaneous Mathematical Symbols-A" +msgstr "" + +msgid "Miscellaneous Mathematical Symbols-B" +msgstr "" + +msgid "Miscellaneous Symbols" +msgstr "" + +msgid "Miscellaneous Symbols and Arrows" +msgstr "" + +msgid "Miscellaneous Technical" +msgstr "" + msgid "Modified" msgstr "" @@ -505,6 +607,9 @@ msgstr "" msgid "Number 9" msgstr "" +msgid "Number Forms" +msgstr "" + msgid "Number, Decimal Digit" msgstr "" @@ -532,12 +637,21 @@ msgstr "" msgid "Parentheses" msgstr "" +msgid "Phonetic Extensions" +msgstr "" + +msgid "Phonetic Extensions Supplement" +msgstr "" + msgid "Polish" msgstr "" msgid "Portuguese" msgstr "" +msgid "Private Use Area" +msgstr "" + msgid "Punctuation" msgstr "" @@ -607,12 +721,18 @@ msgstr "" msgid "Spaces" msgstr "" +msgid "Spacing Modifier Letters" +msgstr "" + msgid "Spanish" msgstr "" msgid "Special Characters" msgstr "" +msgid "Specials" +msgstr "" + msgid "Strokes" msgstr "" @@ -622,6 +742,18 @@ msgstr "" msgid "Superscript Sharacters" msgstr "" +msgid "Superscripts and Subscripts" +msgstr "" + +msgid "Supplemental Arrows-A" +msgstr "" + +msgid "Supplemental Arrows-B" +msgstr "" + +msgid "Supplemental Punctuation" +msgstr "" + msgid "Surrounded" msgstr "" @@ -676,6 +808,9 @@ msgstr "" msgid "Symbol, Other" msgstr "" +msgid "Syriac" +msgstr "" + msgid "Technical Characters" msgstr "" @@ -694,6 +829,9 @@ msgstr "" msgid "Ukrainian" msgstr "" +msgid "Unified Canadian Aboriginal Syllabics" +msgstr "" + msgid "Units" msgstr ""