doom3-bfg/neo/idlib/LangDict.cpp

721 lines
16 KiB
C++

/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "precompiled.h"
// This is the default language dict that the entire system uses, but you can instantiate your own idLangDict classes to manipulate a language dictionary in a tool
idLangDict idLocalization::languageDict;
idCVar lang_maskLocalizedStrings( "lang_maskLocalizedStrings", "0", CVAR_BOOL, "Masks all localized strings to help debugging. When set will replace strings with an equal length of W's and ending in an X. Note: The masking occurs at string table load time." );
/*
========================
idLocalization::ClearDictionary
========================
*/
void idLocalization::ClearDictionary()
{
languageDict.Clear();
}
/*
========================
idLocalization::LoadDictionary
========================
*/
bool idLocalization::LoadDictionary( const byte* data, int dataLen, const char* fileName )
{
return languageDict.Load( data, dataLen, fileName );
}
/*
========================
idLocalization::GetString
========================
*/
const char* idLocalization::GetString( const char* inString )
{
return languageDict.GetString( inString );
}
/*
========================
idLocalization::FindString
========================
*/
const char* idLocalization::FindString( const char* inString )
{
return languageDict.FindString( inString );
}
/*
========================
idLocalization::VerifyUTF8
========================
*/
utf8Encoding_t idLocalization::VerifyUTF8( const uint8* buffer, const int bufferLen, const char* name )
{
utf8Encoding_t encoding;
idStr::IsValidUTF8( buffer, bufferLen, encoding );
if( encoding == UTF8_INVALID )
{
idLib::FatalError( "Language file %s is not valid UTF-8 or plain ASCII.", name );
}
else if( encoding == UTF8_INVALID_BOM )
{
idLib::FatalError( "Language file %s is marked as UTF-8 but has invalid encoding.", name );
}
else if( encoding == UTF8_ENCODED_NO_BOM )
{
idLib::FatalError( "Language file %s has no byte order marker. Fix this or roll back to a version that has the marker.", name );
}
else if( encoding != UTF8_ENCODED_BOM && encoding != UTF8_PURE_ASCII )
{
idLib::FatalError( "Language file %s has unknown utf8Encoding_t.", name );
}
return encoding;
}
// string entries can refer to other string entries,
// recursing up to this many times before we decided someone did something stupid
const char* idLangDict::KEY_PREFIX = "#str_"; // all keys should be prefixed with this for redirection to work
const int idLangDict::KEY_PREFIX_LEN = idStr::Length( KEY_PREFIX );
/*
========================
idLangDict::idLangDict
========================
*/
idLangDict::idLangDict() : keyIndex( 4096, 4096 )
{
}
/*
========================
idLangDict::~idLangDict
========================
*/
idLangDict::~idLangDict()
{
Clear();
}
/*
========================
idLangDict::Clear
========================
*/
void idLangDict::Clear()
{
//mem.PushHeap();
for( int i = 0; i < keyVals.Num(); i++ )
{
if( keyVals[i].value == NULL )
{
continue;
}
blockAlloc.Free( keyVals[i].value );
keyVals[i].value = NULL;
}
//mem.PopHeap();
}
/*
========================
idLangDict::Load
========================
*/
bool idLangDict::Load( const byte* buffer, const int bufferLen, const char* name )
{
if( buffer == NULL || bufferLen <= 0 )
{
// let whoever called us deal with the failure (so sys_lang can be reset)
return false;
}
idLib::Printf( "Reading %s", name );
bool utf8 = false;
// in all but retail builds, ensure that the byte-order mark is NOT MISSING so that
// we can avoid debugging UTF-8 code
#ifndef ID_RETAIL
utf8Encoding_t encoding = idLocalization::VerifyUTF8( buffer, bufferLen, name );
if( encoding == UTF8_ENCODED_BOM )
{
utf8 = true;
}
else if( encoding == UTF8_PURE_ASCII )
{
utf8 = false;
}
else
{
assert( false ); // this should have been handled in VerifyUTF8 with a FatalError
return false;
}
#else
// in release we just check the BOM so we're not scanning the lang file twice on startup
if( bufferLen > 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF )
{
utf8 = true;
}
#endif
if( utf8 )
{
idLib::Printf( " as UTF-8\n" );
}
else
{
idLib::Printf( " as ASCII\n" );
}
idStr tempKey;
idStr tempVal;
int line = 0;
int numStrings = 0;
int i = 0;
while( i < bufferLen )
{
uint32 c = buffer[i++];
if( c == '/' ) // comment, read until new line
{
while( i < bufferLen )
{
c = buffer[i++];
if( c == '\n' )
{
line++;
break;
}
}
}
else if( c == '}' )
{
break;
}
else if( c == '\n' )
{
line++;
}
else if( c == '\"' )
{
int keyStart = i;
int keyEnd = -1;
while( i < bufferLen )
{
c = buffer[i++];
if( c == '\"' )
{
keyEnd = i - 1;
break;
}
}
if( keyEnd < keyStart )
{
idLib::FatalError( "%s File ended while reading key at line %d", name, line );
}
tempKey.CopyRange( ( char* )buffer, keyStart, keyEnd );
int valStart = -1;
while( i < bufferLen )
{
c = buffer[i++];
if( c == '\"' )
{
valStart = i;
break;
}
}
if( valStart < 0 )
{
idLib::FatalError( "%s File ended while reading value at line %d", name, line );
}
int valEnd = -1;
tempVal.CapLength( 0 );
while( i < bufferLen )
{
c = utf8 ? idStr::UTF8Char( buffer, i ) : buffer[i++];
if( !utf8 && c >= 0x80 )
{
// this is a serious error and we must check this to avoid accidentally shipping a file where someone squased UTF-8 encodings
idLib::FatalError( "Language file %s is supposed to be plain ASCII, but has byte values > 127!", name );
}
if( c == '\"' )
{
valEnd = i - 1;
continue;
}
if( c == '\n' )
{
line++;
break;
}
if( c == '\r' )
{
continue;
}
if( c == '\\' )
{
c = utf8 ? idStr::UTF8Char( buffer, i ) : buffer[i++];
if( c == 'n' )
{
c = '\n';
}
else if( c == 't' )
{
c = '\t';
}
else if( c == '\"' )
{
c = '\"';
}
else if( c == '\\' )
{
c = '\\';
}
else
{
idLib::Warning( "Unknown escape sequence %x at line %d", c, line );
}
}
tempVal.AppendUTF8Char( c );
}
if( valEnd < valStart )
{
idLib::FatalError( "%s File ended while reading value at line %d", name, line );
}
if( lang_maskLocalizedStrings.GetBool() && tempVal.Length() > 0 && tempKey.Find( "#font_" ) == -1 )
{
int len = tempVal.Length();
if( len > 0 )
{
tempVal.Fill( 'W', len - 1 );
}
else
{
tempVal.Empty();
}
tempVal.Append( 'X' );
}
AddKeyVal( tempKey, tempVal );
numStrings++;
}
}
idLib::Printf( "%i strings read\n", numStrings );
// get rid of any waste due to geometric list growth
//mem.PushHeap();
keyVals.Condense();
//mem.PopHeap();
return true;
}
/*
========================
idLangDict::Save
========================
*/
bool idLangDict::Save( const char* fileName )
{
idFile* outFile = fileSystem->OpenFileWrite( fileName );
if( outFile == NULL )
{
idLib::Warning( "Error saving: %s", fileName );
return false;
}
byte bof[3] = { 0xEF, 0xBB, 0xBF };
outFile->Write( bof, 3 );
outFile->WriteFloatString( "// string table\n//\n\n{\n" );
for( int j = 0; j < keyVals.Num(); j++ )
{
const idLangKeyValue& kvp = keyVals[j];
if( kvp.value == NULL )
{
continue;
}
outFile->WriteFloatString( "\t\"%s\"\t\"", kvp.key );
for( int k = 0; kvp.value[k] != 0; k++ )
{
char ch = kvp.value[k];
if( ch == '\t' )
{
outFile->Write( "\\t", 2 );
}
else if( ch == '\n' || ch == '\r' )
{
outFile->Write( "\\n", 2 );
}
else if( ch == '"' )
{
outFile->Write( "\\\"", 2 );
}
else if( ch == '\\' )
{
outFile->Write( "\\\\", 2 );
}
else
{
outFile->Write( &ch, 1 );
}
}
outFile->WriteFloatString( "\"\n" );
}
outFile->WriteFloatString( "\n}\n" );
delete outFile;
return true;
}
/*
========================
idLangDict::GetString
========================
*/
const char* idLangDict::GetString( const char* str ) const
{
const char* localized = FindString( str );
if( localized == NULL )
{
return str;
}
return localized;
}
/*
========================
idLangDict::FindStringIndex
========================
*/
int idLangDict::FindStringIndex( const char* str ) const
{
if( str == NULL )
{
return -1;
}
int hash = idStr::IHash( str );
for( int i = keyIndex.GetFirst( hash ); i >= 0; i = keyIndex.GetNext( i ) )
{
if( idStr::Icmp( str, keyVals[i].key ) == 0 )
{
return i;
}
}
return -1;
}
/*
========================
idLangDict::FindString_r
========================
*/
const char* idLangDict::FindString_r( const char* str, int& depth ) const
{
depth++;
if( depth > MAX_REDIRECTION_DEPTH )
{
// This isn't an error because we assume the error will be obvious somewhere in a GUI or something,
// and the whole point of tracking the depth is to avoid a crash.
idLib::Warning( "String '%s', indirection depth > %d", str, MAX_REDIRECTION_DEPTH );
return NULL;
}
if( str == NULL || str[0] == '\0' )
{
return NULL;
}
int index = FindStringIndex( str );
if( index < 0 )
{
return NULL;
}
const char* value = keyVals[index].value;
if( value == NULL )
{
return NULL;
}
if( IsStringId( value ) )
{
// this string is re-directed to another entry
return FindString_r( value, depth );
}
return value;
}
/*
========================
idLangDict::FindString
========================
*/
const char* idLangDict::FindString( const char* str ) const
{
int depth = 0;
return FindString_r( str, depth );
}
/*
========================
idLangDict::DeleteString
========================
*/
bool idLangDict::DeleteString( const char* key )
{
return DeleteString( FindStringIndex( key ) );
}
/*
========================
idLangDict::DeleteString
========================
*/
bool idLangDict::DeleteString( const int idx )
{
if( idx < 0 || idx >= keyVals.Num() )
{
return false;
}
//mem.PushHeap();
blockAlloc.Free( keyVals[idx].value );
keyVals[idx].value = NULL;
//mem.PopHeap();
return true;
}
/*
========================
idLangDict::RenameStringKey
========================
*/
bool idLangDict::RenameStringKey( const char* oldKey, const char* newKey )
{
int index = FindStringIndex( oldKey );
if( index < 0 )
{
return false;
}
//mem.PushHeap();
blockAlloc.Free( keyVals[index].key );
int newKeyLen = idStr::Length( newKey );
keyVals[index].key = blockAlloc.Alloc( newKeyLen + 1 );
idStr::Copynz( keyVals[index].key, newKey, newKeyLen + 1 );
int oldHash = idStr::IHash( oldKey );
int newHash = idStr::IHash( newKey );
if( oldHash != newHash )
{
keyIndex.Remove( oldHash, index );
keyIndex.Add( newHash, index );
}
//mem.PopHeap();
return true;
}
/*
========================
idLangDict::SetString
========================
*/
bool idLangDict::SetString( const char* key, const char* val )
{
int index = FindStringIndex( key );
if( index < 0 )
{
return false;
}
//mem.PushHeap();
if( keyVals[index].value != NULL )
{
blockAlloc.Free( keyVals[index].value );
}
int valLen = idStr::Length( val );
keyVals[index].value = blockAlloc.Alloc( valLen + 1 );
idStr::Copynz( keyVals[index].value, val, valLen + 1 );
//mem.PopHeap();
return true;
}
/*
========================
idLangDict::AddKeyVal
========================
*/
void idLangDict::AddKeyVal( const char* key, const char* val )
{
if( SetString( key, val ) )
{
return;
}
//mem.PushHeap();
int keyLen = idStr::Length( key );
char* k = blockAlloc.Alloc( keyLen + 1 );
idStr::Copynz( k, key, keyLen + 1 );
char* v = NULL;
if( val != NULL )
{
int valLen = idStr::Length( val );
v = blockAlloc.Alloc( valLen + 1 );
idStr::Copynz( v, val, valLen + 1 );
}
int index = keyVals.Append( idLangKeyValue( k, v ) );
int hash = idStr::IHash( key );
keyIndex.Add( hash, index );
//mem.PopHeap();
}
/*
========================
idLangDict::AddString
========================
*/
const char* idLangDict::AddString( const char* val )
{
int i = Sys_Milliseconds();
idStr key;
sprintf( key, "#str_%06d", ( i++ % 1000000 ) );
while( FindStringIndex( key ) > 0 )
{
sprintf( key, "#str_%06d", ( i++ % 1000000 ) );
}
AddKeyVal( key, val );
int index = FindStringIndex( key );
return keyVals[index].key;
}
/*
========================
idLangDict::GetNumKeyVals
========================
*/
int idLangDict::GetNumKeyVals() const
{
return keyVals.Num();
}
/*
========================
idLangDict::GetKeyVal
========================
*/
const idLangKeyValue* idLangDict::GetKeyVal( int i ) const
{
return &keyVals[i];
}
/*
========================
idLangDict::IsStringId
========================
*/
bool idLangDict::IsStringId( const char* str )
{
return idStr::Icmpn( str, KEY_PREFIX, KEY_PREFIX_LEN ) == 0;
}
/*
========================
idLangDict::GetLocalizedString
========================
*/
const char* idLangDict::GetLocalizedString( const idStrId& strId ) const
{
if( strId.GetIndex() >= 0 && strId.GetIndex() < keyVals.Num() )
{
if( keyVals[ strId.GetIndex() ].value == NULL )
{
return keyVals[ strId.GetIndex() ].key;
}
else
{
return keyVals[ strId.GetIndex() ].value;
}
}
return "";
}
/*
================================================================================================
idStrId
================================================================================================
*/
/*
========================
idStrId::Set
========================
*/
void idStrId::Set( const char* key )
{
if( key == NULL || key[0] == 0 )
{
index = -1;
}
else
{
index = idLocalization::languageDict.FindStringIndex( key );
if( index < 0 )
{
// don't allow setting of string ID's to an unknown ID... this should only be allowed from
// the string table tool because additions from anywhere else are not guaranteed to be
// saved to the .lang file.
idLib::Warning( "Attempted to set unknown string ID '%s'", key );
}
}
}
/*
========================
idStrId::GetKey
========================
*/
const char* idStrId::GetKey() const
{
if( index >= 0 && index < idLocalization::languageDict.keyVals.Num() )
{
return idLocalization::languageDict.keyVals[index].key;
}
return "";
}
/*
========================
idStrId::GetLocalizedString
========================
*/
const char* idStrId::GetLocalizedString() const
{
return idLocalization::languageDict.GetLocalizedString( *this );
}