e9f501c250
* Allow value be empty in lang files * Refactor solution.
629 lines
12 KiB
C++
Executable File
629 lines
12 KiB
C++
Executable File
// vim: set ts=4 sw=4 tw=99 noet:
|
|
//
|
|
// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO").
|
|
// Copyright (C) The AMX Mod X Development Team.
|
|
//
|
|
// This software is licensed under the GNU General Public License, version 3 or higher.
|
|
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
|
|
// https://alliedmods.net/amxmodx-license
|
|
|
|
#include <stdarg.h>
|
|
#include "amxmodx.h"
|
|
#include "CLang.h"
|
|
#include "format.h"
|
|
#include "ITextParsers.h"
|
|
|
|
#define LITIDX_NONE 0
|
|
#define LITIDX_BRACKET 1
|
|
#define LITIDX_DEFINITION 2
|
|
|
|
#define INSERT_NUMBER 1
|
|
#define INSERT_FLOAT 2
|
|
#define INSERT_STRING 3
|
|
#define INSERT_NEWLINE 4
|
|
|
|
template<>
|
|
int Compare<ke::AString>(const ke::AString &k1, const ke::AString &k2)
|
|
{
|
|
return k1.compare(k2);
|
|
}
|
|
|
|
template<>
|
|
int CompareAlt<char const *, ke::AString>(char const * const &k1, ke::AString const &k2)
|
|
{
|
|
return k2.compare(k1);
|
|
}
|
|
template<>
|
|
int CompareAlt<ke::AString, ke::AString>(ke::AString const &k1, ke::AString const &k2)
|
|
{
|
|
return k1.compare(k2);
|
|
}
|
|
|
|
template<>
|
|
int HashFunction<ke::AString>(const ke::AString &k)
|
|
{
|
|
unsigned long hash = 5381;
|
|
register const char *str = k.chars();
|
|
register char c;
|
|
while ((c = *str++))
|
|
{
|
|
hash = ((hash << 5) + hash) + c; // hash*33 + c
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
template<>
|
|
int HashAlt<const char *>(char const * const &k)
|
|
{
|
|
unsigned long hash = 5381;
|
|
register const char *str = k;
|
|
register char c;
|
|
while ((c = *str++))
|
|
{
|
|
hash = ((hash << 5) + hash) + c; // hash*33 + c
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
template<>
|
|
int HashAlt<ke::AString>(ke::AString const &k)
|
|
{
|
|
unsigned long hash = 5381;
|
|
register const char *str = k.chars();
|
|
register char c;
|
|
while ((c = *str++))
|
|
{
|
|
hash = ((hash << 5) + hash) + c; // hash*33 + c
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
|
|
template<>
|
|
int HashFunction<int>(const int &k)
|
|
{
|
|
return k;
|
|
}
|
|
|
|
template<>
|
|
int Compare<int>(const int &k1, const int &k2)
|
|
{
|
|
return (k1 - k2);
|
|
}
|
|
|
|
// strip the whitespaces at the beginning and the end of a string
|
|
// also convert to lowercase if needed
|
|
// return the number of written characters (including the terimating zero char)
|
|
size_t CLangMngr::strip(char *str, char *newstr, bool makelower)
|
|
{
|
|
size_t i = 0;
|
|
size_t pos = 0;
|
|
int flag = 0;
|
|
size_t strln = strlen(str);
|
|
|
|
for (i = strln - 1; i < strln; i--)
|
|
{
|
|
if (str[i] == '\n' || str[i] == ' ' || str[i] == '\t')
|
|
{
|
|
str[i] = 0;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
char *ptr = str;
|
|
while (*ptr)
|
|
{
|
|
if (!flag)
|
|
{
|
|
if (*ptr != '\n' && *ptr != ' ' && *ptr != '\t')
|
|
{
|
|
flag = 1;
|
|
newstr[pos++] = makelower ? tolower(*ptr) : *ptr;
|
|
}
|
|
} else {
|
|
newstr[pos++] = makelower ? tolower(*ptr) : *ptr;
|
|
}
|
|
++ptr;
|
|
}
|
|
|
|
newstr[pos] = 0;
|
|
|
|
return ptr - str + 1;
|
|
}
|
|
|
|
|
|
/******** CLangMngr::CLang *********/
|
|
|
|
CLangMngr::CLang::CLang()
|
|
{
|
|
m_LookUpTable.clear();
|
|
m_entries = 0;
|
|
}
|
|
|
|
CLangMngr::CLang::CLang(const char *lang)
|
|
{
|
|
m_LookUpTable.clear();
|
|
m_entries = 0;
|
|
strncpy(m_LanguageName, lang, 2);
|
|
m_LanguageName[2] = 0;
|
|
}
|
|
|
|
void CLangMngr::CLang::AddEntry(int key, const char *definition)
|
|
{
|
|
defentry &d = m_LookUpTable[key];
|
|
|
|
if (d.definition)
|
|
{
|
|
delete d.definition;
|
|
} else {
|
|
m_entries++;
|
|
}
|
|
|
|
d.definition = new ke::AString(definition);
|
|
}
|
|
|
|
CLangMngr::CLang::~CLang()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void CLangMngr::CLang::Clear()
|
|
{
|
|
THash<int, defentry>::iterator iter;
|
|
for (iter=m_LookUpTable.begin(); iter!=m_LookUpTable.end(); iter++)
|
|
{
|
|
if (iter->val.definition)
|
|
{
|
|
delete iter->val.definition;
|
|
iter->val.definition = NULL;
|
|
}
|
|
}
|
|
m_LookUpTable.clear();
|
|
m_entries = 0;
|
|
}
|
|
|
|
void CLangMngr::CLang::MergeDefinitions(ke::Vector<sKeyDef> &vec)
|
|
{
|
|
ke::AutoString *pDef;
|
|
int key = -1;
|
|
|
|
while (!vec.empty())
|
|
{
|
|
auto keydef = vec.popCopy();
|
|
|
|
key = keydef.key;
|
|
pDef = keydef.definition;
|
|
|
|
AddEntry(key, pDef->ptr());
|
|
|
|
delete pDef;
|
|
}
|
|
}
|
|
|
|
const char * CLangMngr::CLang::GetDef(int key, int &status)
|
|
{
|
|
defentry &def = m_LookUpTable[key];
|
|
|
|
if (!def.definition)
|
|
{
|
|
status = ERR_BADKEY;
|
|
return NULL;
|
|
}
|
|
|
|
status = 0;
|
|
return def.definition->chars();
|
|
}
|
|
|
|
int CLangMngr::CLang::Entries()
|
|
{
|
|
return m_entries;
|
|
}
|
|
|
|
/******** CLangMngr *********/
|
|
|
|
CLangMngr::CLangMngr()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
const char * CLangMngr::GetKey(int key)
|
|
{
|
|
if (key < 0 || key >= (int)KeyList.length())
|
|
return NULL;
|
|
|
|
return KeyList[key]->chars();
|
|
}
|
|
|
|
int CLangMngr::GetKeyEntry(const char *key)
|
|
{
|
|
keytbl_val &val = KeyTable[ke::AString(key)];
|
|
|
|
return val.index;
|
|
}
|
|
|
|
int CLangMngr::AddKeyEntry(const char *key)
|
|
{
|
|
keytbl_val val;
|
|
val.index = static_cast<int>(KeyList.length());
|
|
|
|
KeyList.append(new ke::AString(key));
|
|
|
|
KeyTable[ke::AString(key)] = val;
|
|
|
|
return val.index;
|
|
}
|
|
|
|
int CLangMngr::AddKeyEntry(ke::AString &key)
|
|
{
|
|
return AddKeyEntry(key.chars());
|
|
}
|
|
|
|
int CLangMngr::GetKeyEntry(ke::AString &key)
|
|
{
|
|
keytbl_val &val = KeyTable[key];
|
|
|
|
return val.index;
|
|
}
|
|
|
|
char * CLangMngr::FormatAmxString(AMX *amx, cell *params, int parm, int &len)
|
|
{
|
|
//do an initial run through all this
|
|
static char outbuf[4096];
|
|
cell *addr = get_amxaddr(amx, params[parm++]);
|
|
|
|
len = atcprintf(outbuf, sizeof(outbuf)-1, addr, amx, params, &parm);
|
|
|
|
return outbuf;
|
|
}
|
|
|
|
void CLangMngr::MergeDefinitions(const char *lang, ke::Vector<sKeyDef> &tmpVec)
|
|
{
|
|
CLang * language = GetLang(lang);
|
|
if (language)
|
|
language->MergeDefinitions(tmpVec);
|
|
}
|
|
|
|
void reparse_newlines_and_color(char* def)
|
|
{
|
|
size_t len = strlen(def);
|
|
int offs = 0;
|
|
int c;
|
|
|
|
if (!len)
|
|
return;
|
|
|
|
for (size_t i = 0; i < len; i++)
|
|
{
|
|
c = def[i];
|
|
|
|
if (c == '^' && (i != len - 1))
|
|
{
|
|
c = def[++i];
|
|
|
|
if (c == 'n' || c == 't' || (c >= '1' && c <= '4'))
|
|
{
|
|
switch (c)
|
|
{
|
|
case '1': c = '\x01'; break;
|
|
case '2': c = '\x02'; break;
|
|
case '3': c = '\x03'; break;
|
|
case '4': c = '\x04'; break;
|
|
case 'n': c = '\n'; break;
|
|
case 't': c = '\t'; break;
|
|
}
|
|
|
|
if (!g_bmod_cstrike && (c >= '1' && c <= '4')) // remove completely these two characters if not under CS
|
|
{
|
|
offs += 2;
|
|
continue;
|
|
}
|
|
|
|
offs++;
|
|
}
|
|
}
|
|
|
|
def[i - offs] = c;
|
|
}
|
|
|
|
def[len-offs] = '\0';
|
|
}
|
|
|
|
struct LangFileData
|
|
{
|
|
void reset()
|
|
{
|
|
multiLine = false;
|
|
|
|
*language = '\0';
|
|
*valueBuffer = '\0';
|
|
|
|
clearEntry();
|
|
}
|
|
|
|
void clearEntry()
|
|
{
|
|
entry.key = -1;
|
|
entry.definition = nullptr;
|
|
}
|
|
|
|
bool multiLine;
|
|
char language[3];
|
|
char valueBuffer[512];
|
|
ke::AString currentFile;
|
|
ke::AString lastKey;
|
|
ke::Vector<sKeyDef> defsQueue;
|
|
sKeyDef entry;
|
|
|
|
} Data;
|
|
|
|
void CLangMngr::ReadINI_ParseStart()
|
|
{
|
|
Data.reset();
|
|
}
|
|
|
|
bool CLangMngr::ReadINI_NewSection(const char *section, bool invalid_tokens, bool close_bracket, bool extra_tokens, unsigned int *curtok)
|
|
{
|
|
if (Data.multiLine)
|
|
{
|
|
AMXXLOG_Log("New section, unterminated block (file \"%s\" key \"%s\" lang \"%s\")", Data.currentFile.chars(), Data.lastKey.chars(), Data.language);
|
|
|
|
Data.clearEntry();
|
|
}
|
|
|
|
if (!Data.defsQueue.empty())
|
|
{
|
|
MergeDefinitions(Data.language, Data.defsQueue);
|
|
}
|
|
|
|
Data.reset();
|
|
|
|
Data.language[0] = section[0];
|
|
Data.language[1] = section[1];
|
|
Data.language[2] = '\0';
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CLangMngr::ReadINI_KeyValue(const char *key, const char *value, bool invalid_tokens, bool equal_token, bool quotes, unsigned int *curtok)
|
|
{
|
|
bool colons_token = (key[strlen(key) - 1] == ':');
|
|
|
|
if (!Data.multiLine)
|
|
{
|
|
Data.lastKey = key;
|
|
|
|
if (colons_token || equal_token)
|
|
{
|
|
int iKey = GetKeyEntry(key);
|
|
|
|
if (iKey == -1)
|
|
{
|
|
iKey = AddKeyEntry(key);
|
|
}
|
|
|
|
if (equal_token)
|
|
{
|
|
if(value == nullptr) // Support empty value
|
|
Data.valueBuffer[0] = '\0';
|
|
else
|
|
strncopy(Data.valueBuffer, value, sizeof(Data.valueBuffer));
|
|
|
|
reparse_newlines_and_color(Data.valueBuffer);
|
|
|
|
Data.entry.key = iKey;
|
|
Data.entry.definition = new ke::AutoString;
|
|
*Data.entry.definition = Data.valueBuffer;
|
|
|
|
Data.defsQueue.append(Data.entry);
|
|
Data.clearEntry();
|
|
}
|
|
else if (!value && colons_token)
|
|
{
|
|
Data.entry.key = iKey;
|
|
Data.entry.definition = new ke::AutoString;
|
|
|
|
Data.multiLine = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AMXXLOG_Log("Invalid multi-lingual line (file \"%s\" key \"%s\" lang \"%s\")", Data.currentFile.chars(), Data.lastKey.chars(), Data.language);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!value && colons_token)
|
|
{
|
|
strncopy(Data.valueBuffer, Data.entry.definition->ptr(), sizeof(Data.valueBuffer));
|
|
reparse_newlines_and_color(Data.valueBuffer);
|
|
|
|
*Data.entry.definition = Data.valueBuffer;
|
|
|
|
Data.defsQueue.append(Data.entry);
|
|
Data.clearEntry();
|
|
|
|
Data.multiLine = false;
|
|
}
|
|
else
|
|
{
|
|
if (!Data.entry.definition)
|
|
{
|
|
Data.entry.definition = new ke::AutoString();
|
|
}
|
|
|
|
*Data.entry.definition = *Data.entry.definition + key;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CLangMngr::ReadINI_ParseEnd(bool halted)
|
|
{
|
|
if (!Data.defsQueue.empty())
|
|
{
|
|
MergeDefinitions(Data.language, Data.defsQueue);
|
|
}
|
|
}
|
|
|
|
//this is the file parser for dictionary text files
|
|
// -- BAILOPAN
|
|
int CLangMngr::MergeDefinitionFile(const char *file)
|
|
{
|
|
/** Tries to open the file. */
|
|
struct stat fileStat;
|
|
if (stat(file, &fileStat))
|
|
{
|
|
FileList.remove(file);
|
|
AMXXLOG_Log("[AMXX] Failed to open dictionary file: %s", file);
|
|
return 0;
|
|
}
|
|
|
|
/** Checks if there is an existing entry with same time stamp. */
|
|
time_t timeStamp;
|
|
if (FileList.retrieve(file, &timeStamp) && fileStat.st_mtime == timeStamp)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
/** If yes, it either means that the entry doesn't exist or the existing entry needs to be updated. */
|
|
FileList.replace(file, fileStat.st_mtime);
|
|
|
|
Data.currentFile = file;
|
|
|
|
unsigned int line, col;
|
|
bool result = textparsers->ParseFile_INI(file, static_cast<ITextListener_INI*>(this), &line, &col, false);
|
|
|
|
if (!result)
|
|
{
|
|
AMXXLOG_Log("[AMXX] Failed to re-open dictionary file: %s", file);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Find a CLang by name, if not found, add it
|
|
CLangMngr::CLang * CLangMngr::GetLang(const char *name)
|
|
{
|
|
for (size_t iter = 0; iter < m_Languages.length(); ++iter)
|
|
{
|
|
if (strcmp(m_Languages[iter]->GetName(), name) == 0)
|
|
return m_Languages[iter];
|
|
}
|
|
|
|
CLang *p = new CLang(name);
|
|
p->SetMngr(this);
|
|
|
|
m_Languages.append(p);
|
|
return p;
|
|
}
|
|
|
|
// Find a CLang by name, if not found, return NULL
|
|
CLangMngr::CLang * CLangMngr::GetLangR(const char *name)
|
|
{
|
|
for (size_t iter = 0; iter < m_Languages.length(); ++iter)
|
|
{
|
|
if (strcmp(m_Languages[iter]->GetName(), name) == 0)
|
|
return m_Languages[iter];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const char *CLangMngr::GetDef(const char *langName, const char *key, int &status)
|
|
{
|
|
CLang *lang = GetLangR(langName);
|
|
|
|
keytbl_val &val = KeyTable.AltFindOrInsert(ke::AString(key)); //KeyTable[make_string(key)];
|
|
if (lang == NULL)
|
|
{
|
|
status = ERR_BADLANG;
|
|
return NULL;
|
|
} else if (val.index == -1) {
|
|
status = ERR_BADKEY;
|
|
return NULL;
|
|
} else {
|
|
status = 0;
|
|
return lang->GetDef(val.index, status);
|
|
}
|
|
}
|
|
|
|
void CLangMngr::InvalidateCache()
|
|
{
|
|
FileList.clear();
|
|
}
|
|
|
|
CLangMngr::~CLangMngr()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void CLangMngr::Clear()
|
|
{
|
|
unsigned int i = 0;
|
|
|
|
KeyTable.clear();
|
|
|
|
for (i = 0; i < m_Languages.length(); i++)
|
|
{
|
|
if (m_Languages[i])
|
|
delete m_Languages[i];
|
|
}
|
|
|
|
for (i = 0; i < KeyList.length(); i++)
|
|
{
|
|
if (KeyList[i])
|
|
delete KeyList[i];
|
|
}
|
|
|
|
m_Languages.clear();
|
|
KeyList.clear();
|
|
FileList.clear();
|
|
}
|
|
|
|
int CLangMngr::GetLangsNum()
|
|
{
|
|
return m_Languages.length();
|
|
}
|
|
|
|
const char *CLangMngr::GetLangName(int langId)
|
|
{
|
|
for (size_t iter = 0; iter < m_Languages.length(); ++iter)
|
|
{
|
|
if ((int)iter == langId)
|
|
{
|
|
return m_Languages[iter]->GetName();
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
bool CLangMngr::LangExists(const char *langName)
|
|
{
|
|
char buf[3] = {0};
|
|
int i = 0;
|
|
|
|
while ((buf[i] = tolower(*langName++)))
|
|
{
|
|
if (++i == 2)
|
|
break;
|
|
}
|
|
|
|
for (size_t iter = 0; iter < m_Languages.length(); ++iter)
|
|
{
|
|
if (strcmp(m_Languages[iter]->GetName(), buf) == 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CLangMngr::SetDefLang(int id)
|
|
{
|
|
m_CurGlobId = id;
|
|
}
|