#include "string_table.h" #include "helper_str.h" #include "logging.h" #include "../tinyxml/tinyxml.h" #include #if defined(_DEBUG) && defined(_MSC_VER) #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif using namespace irr; using namespace core; // -------------------------------------------------------- StringTableEntry::StringTableEntry(const irr::core::stringw &identifier_) : mIdentifier(identifier_) { } StringTableEntry::StringTableEntry(const irr::core::stringw &identifier_, const irr::core::stringw &string_) : mIdentifier(identifier_) , mOrigString(string_) { Tokenize(mOrigString); } StringTableEntry::StringTableEntry(const StringTableEntry &orig_) { *this = orig_; } StringTableEntry::~StringTableEntry() { DropTokens(); } StringTableEntry& StringTableEntry::operator=(const StringTableEntry &orig_) { DropTokens(); mIdentifier = orig_.mIdentifier; mOrigString = orig_.mOrigString; mTokens = orig_.mTokens; for ( u32 i=0; i < mTokens.size(); ++i ) mTokens[i]->grab(); return *this; } void StringTableEntry::Tokenize(const irr::core::stringw &string_) /* Automata to parse the string: state onenter condition onexit nextstate ------------------------------------------------------------------------- START : EOL -> FIN '\' next -> ESC '%' next -> PARAM default -> STRING ESC : EOL -> FIN 'n' next -> MAKE_NEWLINE default -> STRING PARAM: EOL -> FIN digit -> MAKE_PARAM '\' next -> ESC default -> STRING STRING: add_str,next EOL -> MAKE_STRING '\' -> MAKE_STRING '%' -> MAKE_STRING default -> STRING MAKE_NEWLINE: nl_token -> START MAKE_PARAM: atoi,param_token eat_digits -> START MAKE_STRING: string_token -> START FIN */ { DropTokens(); // Handcoded statemachine to tokenize the string. // Don't ever change anything without updating documentation above. unsigned int i=0; TokenParam * tokenParam = 0; TokenString * tokenString = 0; stringw stringHelper; STATE_START : if ( i >= string_.size() ) // EOL goto STATE_FIN; switch ( string_[i] ) { case L'\\': ++i; goto STATE_ESC; case L'%': ++i; goto STATE_PARAM; default: goto STATE_STRING; } STATE_ESC : // escape characters if ( i >= string_.size() ) // EOL goto STATE_FIN; switch ( string_[i] ) { case L'n': ++i; goto STATE_MAKE_NEWLINE; default: goto STATE_STRING; } STATE_PARAM: if ( i >= string_.size() ) // EOL goto STATE_FIN; if ( ::isdigit(string_[i]) ) goto STATE_MAKE_PARAM; switch ( string_[i] ) { case L'\\': ++i; goto STATE_ESC; default: goto STATE_STRING; } STATE_STRING: stringHelper += string_[i]; ++i; if ( i >= string_.size() ) // EOL goto STATE_MAKE_STRING; switch ( string_[i] ) { case L'\\': goto STATE_MAKE_STRING; case L'%': goto STATE_MAKE_STRING; default: goto STATE_STRING; } STATE_MAKE_NEWLINE: mTokens.push_back( new TokenEndl() ); goto STATE_START; STATE_MAKE_PARAM: tokenParam = new TokenParam; tokenParam->mParamId = wcstol( &(string_[i]), 0, 10 ); mTokens.push_back(tokenParam); while ( i < string_.size() && ::isdigit(string_[i]) ) // eat digits ++i; goto STATE_START; STATE_MAKE_STRING: tokenString = new TokenString; tokenString->mString = stringHelper; mTokens.push_back(tokenString); stringHelper = L""; goto STATE_START; STATE_FIN: ; } irr::core::stringw StringTableEntry::GetString( const irr::core::array & params_ ) { stringw result; for ( u32 i=0; i < mTokens.size(); ++i ) { switch ( mTokens[i]->GetType() ) { case STTT_STRING: result += static_cast(mTokens[i])->mString; break; case STTT_PARAM: { unsigned int id = static_cast(mTokens[i])->mParamId; if ( id == 0 ) { result += mIdentifier; } else if ( id > 0 && id-1 < params_.size() ) { result += params_[id-1]; } break; } case STTT_ENDL: result += L'\n'; break; } } return result; } void StringTableEntry::DropTokens() { for ( u32 i=0; i < mTokens.size(); ++i ) { mTokens[i]->drop(); } mTokens.clear(); } // -------------------------------------------------------- bool StringTable::Load(const char* filename_) { if ( !filename_ || !strlen(filename_) ) return false; TiXmlDocument xmlDoc(filename_); if ( !xmlDoc.LoadFile() ) { return false; } TiXmlNode * stringsNode = xmlDoc.FirstChild("strings"); if (!stringsNode) return false; TiXmlNode* node = stringsNode->IterateChildren("string", NULL); while ( node ) { TiXmlElement* element = node->ToElement(); const char * text = element->GetText(); const char *id = element->Attribute("id"); if ( id && strlen(id) && text ) { std::string strId(id); std::string strText(text); std::wstring wstrId(strId.begin(), strId.end()); std::wstring wstrText = ExtStr::FromUtf8(strText); Add(wstrId.c_str(), wstrText.c_str()); } node = stringsNode->IterateChildren("string", node); } int duplicates = RemoveDuplicates(); if ( duplicates > 0 ) { LOG.Log(LP_WARN, "StringTable "); LOG.Log(LP_WARN, filename_); LOG.LogLn(LP_WARN, " contains duplicates:", duplicates); } return true; } //bool StringTable::Save(const c8* filename_) //{ //} int StringTable::RemoveDuplicates() { int duplicates = 0; mStringTable.sort(); for ( s32 i=(s32)mStringTable.size()-2; i >= 0; --i ) { if ( mStringTable[i].GetIdentifier() == mStringTable[i+1].GetIdentifier() ) { ++duplicates; mStringTable.erase(i+1); } } return duplicates; } void StringTable::Add(const stringw &identifier_, const stringw &string_) { mStringTable.push_back( StringTableEntry(identifier_, string_) ); } void StringTable::Set(const stringw &identifier_, const stringw &string_) { int idx = mStringTable.binary_search( StringTableEntry(identifier_) ); if ( idx < 0 ) { mStringTable.push_back( StringTableEntry(identifier_, string_) ); } else { mStringTable[idx].Tokenize(string_); } } void StringTable::Remove(const irr::core::stringw &identifier_) { int idx = mStringTable.binary_search( StringTableEntry(identifier_) ); if ( idx >= 0 ) { mStringTable.erase(idx); } } stringw StringTable::Get(const stringw &identifier_, bool clearParamsAfterwards_) { int idx = mStringTable.binary_search( StringTableEntry(identifier_) ); if ( idx < 0 ) { if ( clearParamsAfterwards_ ) ClearParams(); // If we don't have that string we return the identifier. // This is usually easier to spot in applications than empty strings. return identifier_; } stringw result( mStringTable[idx].GetString(mParamStack) ); if ( clearParamsAfterwards_ ) ClearParams(); return result; } stringw StringTable::Get(const char *identifier_, bool clearParamsAfterwards_) { std::string str(identifier_); std::wstring wstr(str.begin(), str.end()); return Get(stringw(wstr.c_str()), clearParamsAfterwards_); } void StringTable::PushParam(const irr::core::stringw ¶m_) { mParamStack.push_back(param_); } void StringTable::ClearParams() { mParamStack.clear(); } bool StringTable::UnitTest() { bool result=true; Add(L"id_test1", "test %1"); Add(L"id_test2", "test2 %1 %2 %1 continue"); irr::core::stringw strTest; strTest = Get("id_test1"); result &= strTest == L"test "; strTest = Get("id_test2"); result &= strTest == L"test2 continue"; PushParam("paramA"); PushParam("paramB"); strTest = Get("id_test1", false); result &= strTest == L"test paramA"; strTest = Get("id_test2", false); result &= strTest == L"test2 paramA paramB paramA continue"; PushParam("paramA"); PushParam("paramB"); strTest = Get("id_test1"); result &= strTest == L"test paramA"; strTest = Get("id_test2"); result &= strTest == L"test2 continue"; Remove(L"id_test1"); Remove(L"id_test2"); return result; }