From f53779dbadcfe7a85d5dc6a05d4c15db1d41f768 Mon Sep 17 00:00:00 2001 From: Simon Rozman Date: Wed, 12 Oct 2016 14:30:24 +0200 Subject: [PATCH] Character tagging support added --- ZRColaCompile/dbsource.cpp | 156 ++++++ ZRColaCompile/dbsource.h | 101 +++- ZRColaCompile/main.cpp | 97 ++++ ZRColaCompile/stdafx.h | 1 + lib/libZRCola/build/libZRCola.vcxproj | 1 + lib/libZRCola/build/libZRCola.vcxproj.filters | 3 + lib/libZRCola/include/zrcola/tag.h | 444 ++++++++++++++++++ lib/libZRCola/src/stdafx.h | 1 + output/data/ZRCola.zrcdb | Bin 2308834 -> 2378846 bytes 9 files changed, 781 insertions(+), 23 deletions(-) create mode 100644 lib/libZRCola/include/zrcola/tag.h diff --git a/ZRColaCompile/dbsource.cpp b/ZRColaCompile/dbsource.cpp index c254e2a..e46fc2d 100644 --- a/ZRColaCompile/dbsource.cpp +++ b/ZRColaCompile/dbsource.cpp @@ -539,6 +539,50 @@ bool ZRCola::DBSource::GetChrCat(const com_obj& f, chrcatid_t& cc) con } +bool ZRCola::DBSource::GetTagNames(const winstd::com_obj& f, LCID lcid, list& names) const +{ + wxASSERT_MSG(f, 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, n = ::SysStringLen(V_BSTR(&v)); i < n && 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 < n && 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(), (unsigned __int16)n->length(), name.data(), (unsigned __int16)name.length()) == CSTR_EQUAL) { + // Name is already on the list. + break; + } + } + } + + return true; +} + + bool ZRCola::DBSource::SelectTranslations(com_obj &rs) const { // Create a new recordset. @@ -979,3 +1023,115 @@ bool ZRCola::DBSource::GetCharacterCategory(const com_obj& rs, chr 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(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, wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + wstring id; + + { + com_obj f; + wxVERIFY(SUCCEEDED(flds->get_Item(variant(L"znak"), &f))); + wxCHECK(GetUnicodeCharacter(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(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, wxT("recordset is empty")); + + com_obj flds; + wxVERIFY(SUCCEEDED(rs->get_Fields(&flds))); + wstring id; + 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; +} diff --git a/ZRColaCompile/dbsource.h b/ZRColaCompile/dbsource.h index 337565b..c7c90ed 100644 --- a/ZRColaCompile/dbsource.h +++ b/ZRColaCompile/dbsource.h @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -311,11 +312,30 @@ namespace ZRCola { }; + /// + /// Character tag + /// + class chrtag { + public: + wchar_t chr; ///> Character + int tag; ///< Tag ID + }; + + + /// + /// Tag name + /// + class tagname { + public: + int tag; ///< Tag ID + std::map > names; ///< Names + }; + + public: DBSource(); virtual ~DBSource(); - /// /// Opens the database /// @@ -327,13 +347,11 @@ namespace ZRCola { /// bool Open(LPCTSTR filename); - /// /// Logs errors in database connections /// void LogErrors() const; - /// /// Is recordset at end /// @@ -349,7 +367,6 @@ namespace ZRCola { return FAILED(rs->get_EOF(&eof)) || eof ? true : false; } - /// /// Gets number of records in a recordset /// @@ -363,7 +380,6 @@ namespace ZRCola { return SUCCEEDED(rs->get_RecordCount(&count)) ? count : (size_t)-1; } - /// /// Splits string to individual keywords /// @@ -376,7 +392,6 @@ namespace ZRCola { /// static bool GetKeywords(const wchar_t *str, std::vector< std::wstring > &keywords); - /// /// Gets boolean from ZRCola.zrc database /// @@ -389,7 +404,6 @@ namespace ZRCola { /// bool GetValue(const winstd::com_obj& f, bool& val) const; - /// /// Gets integer from ZRCola.zrc database /// @@ -402,7 +416,6 @@ namespace ZRCola { /// bool GetValue(const winstd::com_obj& f, int& val) const; - /// /// Gets string from ZRCola.zrc database /// @@ -415,7 +428,6 @@ namespace ZRCola { /// bool GetValue(const winstd::com_obj& f, std::wstring& val) const; - /// /// Gets encoded Unicode character from ZRCola.zrc database /// @@ -428,7 +440,6 @@ namespace ZRCola { /// bool GetUnicodeCharacter(const winstd::com_obj& f, wchar_t& chr) const; - /// /// Gets encoded Unicode string from ZRCola.zrc database /// @@ -441,7 +452,6 @@ namespace ZRCola { /// bool GetUnicodeString(const winstd::com_obj& f, std::wstring& str) const; - /// /// Gets language ID from ZRCola.zrc database /// @@ -454,7 +464,6 @@ namespace ZRCola { /// bool GetLanguage(const winstd::com_obj& f, langid_t& lang) const; - /// /// Gets character category ID from ZRCola.zrc database /// @@ -467,6 +476,17 @@ namespace ZRCola { /// 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 character translations @@ -479,7 +499,6 @@ namespace ZRCola { /// bool SelectTranslations(winstd::com_obj& rs) const; - /// /// Returns translation data /// @@ -492,7 +511,6 @@ namespace ZRCola { /// bool GetTranslation(const winstd::com_obj& rs, translation& t) const; - /// /// Returns key sequences /// @@ -504,7 +522,6 @@ namespace ZRCola { /// bool SelectKeySequences(winstd::com_obj& rs) const; - /// /// Returns key sequence data /// @@ -517,7 +534,6 @@ namespace ZRCola { /// bool GetKeySequence(const winstd::com_obj& rs, keyseq& ks) const; - /// /// Returns languages /// @@ -529,7 +545,6 @@ namespace ZRCola { /// bool SelectLanguages(winstd::com_obj& rs) const; - /// /// Returns language data /// @@ -542,7 +557,6 @@ namespace ZRCola { /// bool GetLanguage(const winstd::com_obj& rs, language& lang) const; - /// /// Returns language character /// @@ -554,7 +568,6 @@ namespace ZRCola { /// bool SelectLanguageCharacters(winstd::com_obj& rs) const; - /// /// Returns language character data /// @@ -567,7 +580,6 @@ namespace ZRCola { /// bool GetLanguageCharacter(const winstd::com_obj& rs, langchar& lc) const; - /// /// Returns character groups /// @@ -579,7 +591,6 @@ namespace ZRCola { /// bool SelectCharacterGroups(winstd::com_obj& rs) const; - /// /// Returns character group data /// @@ -603,7 +614,6 @@ namespace ZRCola { /// bool SelectCharacters(winstd::com_obj& rs) const; - /// /// Returns character data /// @@ -627,7 +637,6 @@ namespace ZRCola { /// bool SelectCharacterCategories(winstd::com_obj& rs) const; - /// /// Returns character category data /// @@ -640,6 +649,52 @@ namespace ZRCola { /// 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; + protected: std::basic_string m_filename; ///< Database filename winstd::com_obj m_db; ///< Database diff --git a/ZRColaCompile/main.cpp b/ZRColaCompile/main.cpp index bed2081..4050960 100644 --- a/ZRColaCompile/main.cpp +++ b/ZRColaCompile/main.cpp @@ -610,6 +610,103 @@ int _tmain(int argc, _TCHAR *argv[]) } } + { + // Get characters tags. + com_obj rs; + if (src.SelectCharacterTags(rs)) { + size_t count = src.GetRecordsetCount(rs); + if (count < 0xffffffff) { // 4G check (-1 is reserved for error condition) + ZRCola::DBSource::chrtag ct; + ZRCola::chrtag_db db; + + // Preallocate memory. + db.idxChr.reserve(count); + db.idxTag.reserve(count); + db.data .reserve(count*4); + + // Parse characters tags and build index and data. + for (; !ZRCola::DBSource::IsEOF(rs); rs->MoveNext()) { + // Read characters tags from the database. + if (src.GetCharacterTag(rs, ct)) { + // Add characters tags to index and data. + unsigned __int32 idx = db.data.size(); + db.data.push_back(ct.chr); + wxASSERT_MSG((int)0xffff8000 <= ct.tag && ct.tag <= (int)0x00007fff, wxT("tag out of bounds")); + db.data.push_back((unsigned __int16)ct.tag); + db.idxChr.push_back(idx); + db.idxTag.push_back(idx); + } else + has_errors = true; + } + + // Sort indices. + db.idxChr .sort(); + db.idxTag.sort(); + + // Write characters tags to file. + dst << ZRCola::chrtag_rec(db); + } else { + _ftprintf(stderr, wxT("%s: error ZCC0021: Error getting characters tags count from database or too many character tags.\n"), (LPCTSTR)filenameIn.c_str()); + has_errors = true; + } + } else { + _ftprintf(stderr, wxT("%s: error ZCC0020: Error getting characters tags from database. Please make sure the file is ZRCola.zrc compatible.\n"), (LPCTSTR)filenameIn.c_str()); + has_errors = true; + } + } + + { + // Get tag names. + com_obj rs; + if (src.SelectTagNames(rs)) { + size_t count = src.GetRecordsetCount(rs); + if (count < 0xffffffff) { // 4G check (-1 is reserved for error condition) + ZRCola::DBSource::tagname tn; + ZRCola::tagname_db db; + + // Preallocate memory. + db.idxName.reserve(count*3); + db.data .reserve(count*3*4); + + // Parse tags and build index and data. + for (; !ZRCola::DBSource::IsEOF(rs); rs->MoveNext()) { + // Read tag name from the database. + if (src.GetTagName(rs, tn)) { + // Add tag name to index and data. + for (auto ln = tn.names.cbegin(), ln_end = tn.names.cend(); ln != ln_end; ++ln) { + for (auto nm = ln->second.cbegin(), nm_end = ln->second.cend(); nm != nm_end; ++nm) { + unsigned __int32 idx = db.data.size(); + wxASSERT_MSG((int)0xffff8000 <= tn.tag && tn.tag <= (int)0x00007fff, wxT("tag out of bounds")); + db.data.push_back((unsigned __int16)tn.tag); + db.data.push_back(LOWORD(ln->first)); + db.data.push_back(HIWORD(ln->first)); + wstring::size_type n = nm->length(); + wxASSERT_MSG(n <= 0xffff, wxT("tag name too long")); + db.data.push_back((unsigned __int16)n); + for (wstring::size_type i = 0; i < n; i++) + db.data.push_back(nm->at(i)); + db.idxName.push_back(idx); + } + } + } else + has_errors = true; + } + + // Sort indices. + db.idxName.sort(); + + // Write tags to file. + dst << ZRCola::tagname_rec(db); + } else { + _ftprintf(stderr, wxT("%s: error ZCC0023: Error getting tag name count from database or too many tags.\n"), (LPCTSTR)filenameIn.c_str()); + has_errors = true; + } + } else { + _ftprintf(stderr, wxT("%s: error ZCC0022: Error getting tags from database. Please make sure the file is ZRCola.zrc compatible.\n"), (LPCTSTR)filenameIn.c_str()); + has_errors = true; + } + } + idrec::close(dst, dst_start); if (dst.fail()) { diff --git a/ZRColaCompile/stdafx.h b/ZRColaCompile/stdafx.h index 1aab67f..773ac08 100644 --- a/ZRColaCompile/stdafx.h +++ b/ZRColaCompile/stdafx.h @@ -24,6 +24,7 @@ #include #include +#include #include #include diff --git a/lib/libZRCola/build/libZRCola.vcxproj b/lib/libZRCola/build/libZRCola.vcxproj index f077c81..d83ae03 100644 --- a/lib/libZRCola/build/libZRCola.vcxproj +++ b/lib/libZRCola/build/libZRCola.vcxproj @@ -37,6 +37,7 @@ + diff --git a/lib/libZRCola/build/libZRCola.vcxproj.filters b/lib/libZRCola/build/libZRCola.vcxproj.filters index eb80148..6a611b4 100644 --- a/lib/libZRCola/build/libZRCola.vcxproj.filters +++ b/lib/libZRCola/build/libZRCola.vcxproj.filters @@ -56,6 +56,9 @@ Header Files + + Header Files + diff --git a/lib/libZRCola/include/zrcola/tag.h b/lib/libZRCola/include/zrcola/tag.h new file mode 100644 index 0000000..fbd7c1f --- /dev/null +++ b/lib/libZRCola/include/zrcola/tag.h @@ -0,0 +1,444 @@ +/* + Copyright 2015-2016 Amebis + + This file is part of ZRCola. + + ZRCola is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ZRCola is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with ZRCola. If not, see . +*/ + +#pragma once + +#include "common.h" + +#include +#include +#include +#include +#include + +#pragma warning(push) +#pragma warning(disable: 4200) +#pragma warning(disable: 4251) +#pragma warning(disable: 4512) + + +namespace ZRCola { + typedef unsigned __int16 tagid_t; + + /// + /// Character Tag Database + /// + class ZRCOLA_API chrtag_db { + public: +#pragma pack(push) +#pragma pack(2) + /// + /// Character tag data + /// + struct chrtag { + wchar_t chr; ///> Character + tagid_t tag; ///< Tag ID + }; +#pragma pack(pop) + + /// + /// Character Index + /// + class indexChar : public index + { + public: + /// + /// Constructs the index + /// + /// \param[in] h Reference to vector holding the data + /// + indexChar(_In_ std::vector &h) : index(h) {} + + /// + /// Compares two character tags by character (for searching) + /// + /// \param[in] a Pointer to first element + /// \param[in] b Pointer to second element + /// + /// \returns + /// - <0 when a < b + /// - =0 when a == b + /// - >0 when a > b + /// + virtual int compare(_In_ const chrtag &a, _In_ const chrtag &b) const + { + if (a.chr < b.chr) return -1; + else if (a.chr > b.chr) return 1; + + return 0; + } + + /// + /// Compares two character tags by character (for sorting) + /// + /// \param[in] a Pointer to first element + /// \param[in] b Pointer to second element + /// + /// \returns + /// - <0 when a < b + /// - =0 when a == b + /// - >0 when a > b + /// + virtual int compare_sort(_In_ const chrtag &a, _In_ const chrtag &b) const + { + if (a.chr < b.chr) return -1; + else if (a.chr > b.chr) return 1; + + if (a.tag < b.tag) return -1; + else if (a.tag > b.tag) return 1; + + return 0; + } + } idxChr; ///< Character index + + + /// + /// Tag Index + /// + class indexTag : public index + { + public: + /// + /// Constructs the index + /// + /// \param[in] h Reference to vector holding the data + /// + indexTag(_In_ std::vector &h) : index(h) {} + + /// + /// Compares two character tags by tag (for searching) + /// + /// \param[in] a Pointer to first element + /// \param[in] b Pointer to second element + /// + /// \returns + /// - <0 when a < b + /// - =0 when a == b + /// - >0 when a > b + /// + virtual int compare(_In_ const chrtag &a, _In_ const chrtag &b) const + { + if (a.tag < b.tag) return -1; + else if (a.tag > b.tag) return 1; + + return 0; + } + + /// + /// Compares two character tags by tag (for sorting) + /// + /// \param[in] a Pointer to first element + /// \param[in] b Pointer to second element + /// + /// \returns + /// - <0 when a < b + /// - =0 when a == b + /// - >0 when a > b + /// + virtual int compare_sort(_In_ const chrtag &a, _In_ const chrtag &b) const + { + if (a.tag < b.tag) return -1; + else if (a.tag > b.tag) return 1; + + if (a.chr < b.chr) return -1; + else if (a.chr > b.chr) return 1; + + return 0; + } + } idxTag; ///< Tag index + + std::vector data; ///< Character tags data + + public: + /// + /// Constructs the database + /// + inline chrtag_db() : idxChr(data), idxTag(data) {} + }; + + + typedef ZRCOLA_API stdex::idrec::record chrtag_rec; + + + /// + /// Tag name database + /// + class ZRCOLA_API tagname_db { + public: +#pragma pack(push) +#pragma pack(2) + /// + /// Tag name data + /// + struct tagname { + tagid_t tag; ///< Tag ID + LCID lang; ///< Language ID + unsigned __int16 name_len; ///< \c name length (in characters) + wchar_t name[]; ///< Tag localized name + + /// + /// Compares two names + /// + /// \param[in] lcid Locale ID to use for compare + /// \param[in] str_a First name + /// \param[in] count_a Number of characters in string \p str_a + /// \param[in] str_b Second name + /// \param[in] count_b Number of characters in string \p str_b + /// + /// \returns + /// - <0 when str_a < str_b + /// - =0 when str_a == str_b + /// - >0 when str_a > str_b + /// + /// \note + /// The function does not treat \\0 characters as terminators for performance reasons. + /// Therefore \p count_a and \p count_b must represent exact string lengths. + /// + static inline int CompareName(LCID lcid, const wchar_t *str_a, unsigned __int16 count_a, const wchar_t *str_b, unsigned __int16 count_b) + { + switch (CompareString(lcid, LINGUISTIC_IGNORECASE | LINGUISTIC_IGNOREDIACRITIC | NORM_LINGUISTIC_CASING | NORM_IGNOREWIDTH, str_a, count_a, str_b, count_b)) { + case CSTR_LESS_THAN : return -1; + case CSTR_EQUAL : return 0; + case CSTR_GREATER_THAN: return 1; + default : assert(0); return -1; + } + } + }; +#pragma pack(pop) + + /// + /// Name index + /// + class indexName : public index + { + public: + /// + /// Constructs the index + /// + /// \param[in] h Reference to vector holding the data + /// \param[in] lcid Locale used to perform tag name comparison + /// + indexName(_In_ std::vector &h) : index(h) {} + + /// + /// Compares two tag names by name (for searching) + /// + /// \param[in] a Pointer to first element + /// \param[in] b Pointer to second element + /// + /// \returns + /// - <0 when a < b + /// - =0 when a == b + /// - >0 when a > b + /// + virtual int compare(_In_ const tagname &a, _In_ const tagname &b) const + { + if (a.lang < b.lang) return -1; + else if (a.lang > b.lang) return 1; + + int r = tagname::CompareName(a.lang, a.name, a.name_len, b.name, b.name_len); + if (r != 0) return r; + + return 0; + } + + /// + /// Compares two tag names by name (for sorting) + /// + /// \param[in] a Pointer to first element + /// \param[in] b Pointer to second element + /// + /// \returns + /// - <0 when a < b + /// - =0 when a == b + /// - >0 when a > b + /// + virtual int compare_sort(_In_ const tagname &a, _In_ const tagname &b) const + { + if (a.lang < b.lang) return -1; + else if (a.lang > b.lang) return 1; + + int r = tagname::CompareName(a.lang, a.name, a.name_len, b.name, b.name_len); + if (r != 0) return r; + + if (a.tag < b.tag) return -1; + else if (a.tag > b.tag) return 1; + + return 0; + } + } idxName; ///< Name index + + std::vector data; ///< Tag data + + public: + /// + /// Constructs the database + /// + /// \param[in] lcid Locale used to perform tag name comparison + /// + inline tagname_db() : idxName(data) {} + }; + + + typedef ZRCOLA_API stdex::idrec::record tagname_rec; +}; + + +const ZRCola::recordid_t stdex::idrec::record::id = *(ZRCola::recordid_t*)"C-T"; +const ZRCola::recordid_t stdex::idrec::record::id = *(ZRCola::recordid_t*)"TGN"; + + +/// +/// Writes character tag database to a stream +/// +/// \param[in] stream Output stream +/// \param[in] db Character tag database +/// +/// \returns The stream \p stream +/// +inline std::ostream& operator <<(_In_ std::ostream& stream, _In_ const ZRCola::chrtag_db &db) +{ + // Write character index. + if (stream.fail()) return stream; + stream << db.idxChr; + + // Write tag index. + if (stream.fail()) return stream; + stream << db.idxTag; + + // Write data count. + auto data_count = db.data.size(); +#if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__) + // 4G check + if (data_count > 0xffffffff) { + stream.setstate(std::ios_base::failbit); + return stream; + } +#endif + if (stream.fail()) return stream; + unsigned __int32 count = (unsigned __int32)data_count; + stream.write((const char*)&count, sizeof(count)); + + // Write data. + if (stream.fail()) return stream; + stream.write((const char*)db.data.data(), sizeof(unsigned __int16)*count); + + return stream; +} + + +/// +/// Reads character tag database from a stream +/// +/// \param[in ] stream Input stream +/// \param[out] db Character tag database +/// +/// \returns The stream \p stream +/// +inline std::istream& operator >>(_In_ std::istream& stream, _Out_ ZRCola::chrtag_db &db) +{ + // Read character index. + stream >> db.idxChr; + if (!stream.good()) return stream; + + // Read tag index. + stream >> db.idxTag; + if (!stream.good()) return stream; + + // Read data count. + unsigned __int32 count; + stream.read((char*)&count, sizeof(count)); + if (!stream.good()) return stream; + + if (count) { + // Read data. + db.data.resize(count); + stream.read((char*)db.data.data(), sizeof(unsigned __int16)*count); + } else + db.data.clear(); + + return stream; +} + + +/// +/// Writes tag database to a stream +/// +/// \param[in] stream Output stream +/// \param[in] db Tag database +/// +/// \returns The stream \p stream +/// +inline std::ostream& operator <<(_In_ std::ostream& stream, _In_ const ZRCola::tagname_db &db) +{ + // Write tag index. + if (stream.fail()) return stream; + stream << db.idxName; + + // Write data count. + auto data_count = db.data.size(); +#if defined(_WIN64) || defined(__x86_64__) || defined(__ppc64__) + // 4G check + if (data_count > 0xffffffff) { + stream.setstate(std::ios_base::failbit); + return stream; + } +#endif + if (stream.fail()) return stream; + unsigned __int32 count = (unsigned __int32)data_count; + stream.write((const char*)&count, sizeof(count)); + + // Write data. + if (stream.fail()) return stream; + stream.write((const char*)db.data.data(), sizeof(unsigned __int16)*count); + + return stream; +} + + +/// +/// Reads tag database from a stream +/// +/// \param[in ] stream Input stream +/// \param[out] db Tag database +/// +/// \returns The stream \p stream +/// +inline std::istream& operator >>(_In_ std::istream& stream, _Out_ ZRCola::tagname_db &db) +{ + // Read tag index. + stream >> db.idxName; + if (!stream.good()) return stream; + + // Read data count. + unsigned __int32 count; + stream.read((char*)&count, sizeof(count)); + if (!stream.good()) return stream; + + if (count) { + // Read data. + db.data.resize(count); + stream.read((char*)db.data.data(), sizeof(unsigned __int16)*count); + } else + db.data.clear(); + + return stream; +} + +#pragma warning(pop) diff --git a/lib/libZRCola/src/stdafx.h b/lib/libZRCola/src/stdafx.h index dc4a349..a56adb8 100644 --- a/lib/libZRCola/src/stdafx.h +++ b/lib/libZRCola/src/stdafx.h @@ -25,6 +25,7 @@ #include "../include/zrcola/language.h" #include "../include/zrcola/normalize.h" #include "../include/zrcola/translate.h" +#include "../include/zrcola/tag.h" #include diff --git a/output/data/ZRCola.zrcdb b/output/data/ZRCola.zrcdb index 7448849df2dd7e51faadc50c3c32dfe80943faba..6398ee5b077f9bbc77cd0d3aa2b9d2b5b07ebc26 100644 GIT binary patch delta 75019 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 P delta 4433 zcmZ9P4@}hO8OPuExC69T%N$s$%$`6Uv4k+kC_2(ymAI?D65^;Klo)0RA%+-gh#`)6 z%n`?$i(?(>j~QZ^A%<4cprt|;DRaybOAK?&5=sf;)e*;(IMy&j+&S(r7OOH)KO97+B%iAa-B8G0E6OaMP4WeF^v|mr*|B;U9mw;HU2K8roeZWJ)QR zwZ=@)d0s9JgZKgJtMCW(0Je*|WLSh|Mrh!f2XJhXC|}kHI%k--_P} zdH6daxF%%G0h}Z%%doW!HiCZw--kVg&BXRX7XC*Jd=~taA0-+y3w1nGKsgtD_`|gC zWx#ci4Yw$_!mIdw*vrsH-x=)n5dX%Yg2s2T-Ebck;Tu>17i}ew2+6P&Ho|r&fc?rPvZC8;Zwig&>8;G|a(q%C+F7&Wn8?#&{7w z))@SG`ZQyYKp{+1F2}BiRM-UpNaKr*@>~;38HRc|2o-P)4r~2S;#}g*gW!&1{oy8l zJN0Lv3z8W(2{!W!Rx}A-TBV!~}L2MxmDS z0S$zoi0|d|?xb8ud4^BF2S{==61hje?RK650mR zART?}FNvS_m;_K#plzWoqOGBAqAjFtBZ-J@C2bS!0Bx0HiR{goc4+@;V{0!-Dw>$R zGO;Rtfs|T`E%la)H-V%*3<@m@9SR%Dz+y@+1=p6-iB}xb>DRuGg_~$d@lqjaP_SEe zw)~Y9ks|19ND-tE+Lp@v6mAr1Vqr_$Q6WkZLuW`^T<1x#P_a*uLy<<&Bo^`%wiF{3 zAC*PU>$iadgLIi7ABrYui&V5wv4jm{6_k}3NEfAj3d9P+3dRb=(n{%}gt`)>qcL5T zo-XsAR^C&GU4b@youztwgWnDfxNfOM2L&Cn|CKB0opcX9i|U%>OtPj_RGKI4Q;Msc zR~jlUm4-@7C0Wu^X{fYRu~E7y{gh5hFO}*jhE^BxPeLm}m9Q%RRUX{Qq>`C&6XQtV zq;;{J>KeYZ@FqwPC!CZo?293#*^)`6#mdu^IxE9fYAnfB%KbD&ek#S1TV=}1l9iS# z-B#MIbW7Q?B>5DyfJ94iUcx2Gl>~P&Mco;inPQ?f&qYJ7m&zV!G0uZ8?B0-avRDqN zG)yU&QZuDtQadSP9`y2zE+k=ImW1C2KFZ110cfQ@6*~yZ3DdB{&_=zIRb{|RVwLPF zo+!4M1XCQ%fR*toD^@0}j8=g~0VZ}kD#TYBtqfP0u5L@pew9*JLNy$LTIctZc2j7C zakH31J45*JMK8*=d{L18DB<<}^|uBU=jKY9&N+8+jG{e^UnfD&py_;^VV6i_z{id}iwX;Au@ zNn0=YDbGWIx<1N9_zMuEoJDy60+bh_M9ZH|#UKQ!Sb|dOaw(VKm*F=%znikVJ%4st zGvwTvvYR|tj5Tx4zo+bPJpXxU&700IzqA?8$}KfU=n#9IcV_JF z@9NcqCf+=sqX*)nLk(d+^^th1L4kjB);O*S_Q|-L;uN{&= ztNxq#S6BUF-u`*Se$Vx`%Xt#9IZnaXw(yxhuC|sN)9-w3|8AM`&;Mq3*p&P`F0-H6 z{(u?267%~%ows-0De26RPQxWNPQkN}CW2G+te;Du*l r9Xtm~@GV#m--cvJfer9Hq{0iZ5jH^@d