LangDict.cpp (15985B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation, either version 3 of the License, or 12 (at your option) any later version. 13 14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 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. 23 24 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. 25 26 =========================================================================== 27 */ 28 #pragma hdrstop 29 #include "precompiled.h" 30 31 // 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 32 idLangDict idLocalization::languageDict; 33 34 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." ); 35 36 /* 37 ======================== 38 idLocalization::ClearDictionary 39 ======================== 40 */ 41 void idLocalization::ClearDictionary() { 42 languageDict.Clear(); 43 } 44 45 /* 46 ======================== 47 idLocalization::LoadDictionary 48 ======================== 49 */ 50 bool idLocalization::LoadDictionary( const byte * data, int dataLen, const char * fileName ) { 51 return languageDict.Load( data, dataLen, fileName ); 52 } 53 54 /* 55 ======================== 56 idLocalization::GetString 57 ======================== 58 */ 59 const char * idLocalization::GetString( const char * inString ) { 60 return languageDict.GetString( inString ); 61 } 62 63 /* 64 ======================== 65 idLocalization::FindString 66 ======================== 67 */ 68 const char * idLocalization::FindString( const char * inString ) { 69 return languageDict.FindString( inString ); 70 } 71 72 /* 73 ======================== 74 idLocalization::VerifyUTF8 75 ======================== 76 */ 77 utf8Encoding_t idLocalization::VerifyUTF8( const uint8 * buffer, const int bufferLen, const char * name ) { 78 utf8Encoding_t encoding; 79 idStr::IsValidUTF8( buffer, bufferLen, encoding ); 80 if ( encoding == UTF8_INVALID ) { 81 idLib::FatalError( "Language file %s is not valid UTF-8 or plain ASCII.", name ); 82 } else if ( encoding == UTF8_INVALID_BOM ) { 83 idLib::FatalError( "Language file %s is marked as UTF-8 but has invalid encoding.", name ); 84 } else if ( encoding == UTF8_ENCODED_NO_BOM ) { 85 idLib::FatalError( "Language file %s has no byte order marker. Fix this or roll back to a version that has the marker.", name ); 86 } else if ( encoding != UTF8_ENCODED_BOM && encoding != UTF8_PURE_ASCII ) { 87 idLib::FatalError( "Language file %s has unknown utf8Encoding_t.", name ); 88 } 89 return encoding; 90 } 91 92 // string entries can refer to other string entries, 93 // recursing up to this many times before we decided someone did something stupid 94 const char * idLangDict::KEY_PREFIX = "#str_"; // all keys should be prefixed with this for redirection to work 95 const int idLangDict::KEY_PREFIX_LEN = idStr::Length( KEY_PREFIX ); 96 97 /* 98 ======================== 99 idLangDict::idLangDict 100 ======================== 101 */ 102 idLangDict::idLangDict() : keyIndex( 4096, 4096 ) { 103 } 104 105 /* 106 ======================== 107 idLangDict::~idLangDict 108 ======================== 109 */ 110 idLangDict::~idLangDict() { 111 Clear(); 112 } 113 114 /* 115 ======================== 116 idLangDict::Clear 117 ======================== 118 */ 119 void idLangDict::Clear() { 120 //mem.PushHeap(); 121 for ( int i = 0; i < keyVals.Num(); i++ ) { 122 if ( keyVals[i].value == NULL ) { 123 continue; 124 } 125 blockAlloc.Free( keyVals[i].value ); 126 keyVals[i].value = NULL; 127 } 128 //mem.PopHeap(); 129 } 130 131 /* 132 ======================== 133 idLangDict::Load 134 ======================== 135 */ 136 bool idLangDict::Load( const byte * buffer, const int bufferLen, const char *name ) { 137 138 if ( buffer == NULL || bufferLen <= 0 ) { 139 // let whoever called us deal with the failure (so sys_lang can be reset) 140 return false; 141 } 142 143 idLib::Printf( "Reading %s", name ); 144 145 bool utf8 = false; 146 147 // in all but retail builds, ensure that the byte-order mark is NOT MISSING so that 148 // we can avoid debugging UTF-8 code 149 #ifndef ID_RETAIL 150 utf8Encoding_t encoding = idLocalization::VerifyUTF8( buffer, bufferLen, name ); 151 if ( encoding == UTF8_ENCODED_BOM ) { 152 utf8 = true; 153 } else if ( encoding == UTF8_PURE_ASCII ) { 154 utf8 = false; 155 } else { 156 assert( false ); // this should have been handled in VerifyUTF8 with a FatalError 157 return false; 158 } 159 #else 160 // in release we just check the BOM so we're not scanning the lang file twice on startup 161 if ( bufferLen > 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF ) { 162 utf8 = true; 163 } 164 #endif 165 166 if ( utf8 ) { 167 idLib::Printf( " as UTF-8\n" ); 168 } else { 169 idLib::Printf( " as ASCII\n" ); 170 } 171 172 idStr tempKey; 173 idStr tempVal; 174 175 int line = 0; 176 int numStrings = 0; 177 178 int i = 0; 179 while ( i < bufferLen ) { 180 uint32 c = buffer[i++]; 181 if ( c == '/' ) { // comment, read until new line 182 while ( i < bufferLen ) { 183 c = buffer[i++]; 184 if ( c == '\n' ) { 185 line++; 186 break; 187 } 188 } 189 } else if ( c == '}' ) { 190 break; 191 } else if ( c == '\n' ) { 192 line++; 193 } else if ( c == '\"' ) { 194 int keyStart = i; 195 int keyEnd = -1; 196 while ( i < bufferLen ) { 197 c = buffer[i++]; 198 if ( c == '\"' ) { 199 keyEnd = i - 1; 200 break; 201 } 202 } 203 if ( keyEnd < keyStart ) { 204 idLib::FatalError( "%s File ended while reading key at line %d", name, line ); 205 } 206 tempKey.CopyRange( (char *)buffer, keyStart, keyEnd ); 207 208 int valStart = -1; 209 while ( i < bufferLen ) { 210 c = buffer[i++]; 211 if ( c == '\"' ) { 212 valStart = i; 213 break; 214 } 215 } 216 if ( valStart < 0 ) { 217 idLib::FatalError( "%s File ended while reading value at line %d", name, line ); 218 } 219 int valEnd = -1; 220 tempVal.CapLength( 0 ); 221 while ( i < bufferLen ) { 222 c = utf8 ? idStr::UTF8Char( buffer, i ) : buffer[i++]; 223 if ( !utf8 && c >= 0x80 ) { 224 // this is a serious error and we must check this to avoid accidentally shipping a file where someone squased UTF-8 encodings 225 idLib::FatalError( "Language file %s is supposed to be plain ASCII, but has byte values > 127!", name ); 226 } 227 if ( c == '\"' ) { 228 valEnd = i - 1; 229 continue; 230 } 231 if ( c == '\n' ) { 232 line++; 233 break; 234 } 235 if ( c == '\r' ) { 236 continue; 237 } 238 if ( c == '\\' ) { 239 c = utf8 ? idStr::UTF8Char( buffer, i ) : buffer[i++]; 240 if ( c == 'n' ) { 241 c = '\n'; 242 } else if ( c == 't' ) { 243 c = '\t'; 244 } else if ( c == '\"' ) { 245 c = '\"'; 246 } else if ( c == '\\' ) { 247 c = '\\'; 248 } else { 249 idLib::Warning( "Unknown escape sequence %x at line %d", c, line ); 250 } 251 } 252 tempVal.AppendUTF8Char( c ); 253 } 254 if ( valEnd < valStart ) { 255 idLib::FatalError( "%s File ended while reading value at line %d", name, line ); 256 } 257 if ( lang_maskLocalizedStrings.GetBool() && tempVal.Length() > 0 && tempKey.Find( "#font_" ) == -1 ) { 258 int len = tempVal.Length(); 259 if ( len > 0 ) { 260 tempVal.Fill( 'W', len - 1 ); 261 } else { 262 tempVal.Empty(); 263 } 264 tempVal.Append( 'X' ); 265 } 266 AddKeyVal( tempKey, tempVal ); 267 numStrings++; 268 } 269 } 270 271 idLib::Printf( "%i strings read\n", numStrings ); 272 273 // get rid of any waste due to geometric list growth 274 //mem.PushHeap(); 275 keyVals.Condense(); 276 //mem.PopHeap(); 277 278 return true; 279 } 280 281 /* 282 ======================== 283 idLangDict::Save 284 ======================== 285 */ 286 bool idLangDict::Save( const char * fileName ) { 287 idFile * outFile = fileSystem->OpenFileWrite( fileName ); 288 if ( outFile == NULL ) { 289 idLib::Warning( "Error saving: %s", fileName ); 290 return false; 291 } 292 byte bof[3] = { 0xEF, 0xBB, 0xBF }; 293 outFile->Write( bof, 3 ); 294 outFile->WriteFloatString( "// string table\n//\n\n{\n" ); 295 for ( int j = 0; j < keyVals.Num(); j++ ) { 296 const idLangKeyValue & kvp = keyVals[j]; 297 if ( kvp.value == NULL ) { 298 continue; 299 } 300 outFile->WriteFloatString( "\t\"%s\"\t\"", kvp.key ); 301 for ( int k = 0; kvp.value[k] != 0; k++ ) { 302 char ch = kvp.value[k]; 303 if ( ch == '\t' ) { 304 outFile->Write( "\\t", 2 ); 305 } else if ( ch == '\n' || ch == '\r' ) { 306 outFile->Write( "\\n", 2 ); 307 } else if ( ch == '"' ) { 308 outFile->Write( "\\\"", 2 ); 309 } else if ( ch == '\\' ) { 310 outFile->Write( "\\\\", 2 ); 311 } else { 312 outFile->Write( &ch, 1 ); 313 } 314 } 315 outFile->WriteFloatString( "\"\n" ); 316 } 317 outFile->WriteFloatString( "\n}\n" ); 318 delete outFile; 319 return true; 320 } 321 322 /* 323 ======================== 324 idLangDict::GetString 325 ======================== 326 */ 327 const char * idLangDict::GetString( const char * str ) const { 328 const char * localized = FindString( str ); 329 if ( localized == NULL ) { 330 return str; 331 } 332 return localized; 333 } 334 335 /* 336 ======================== 337 idLangDict::FindStringIndex 338 ======================== 339 */ 340 int idLangDict::FindStringIndex( const char * str ) const { 341 if ( str == NULL ) { 342 return -1; 343 } 344 int hash = idStr::IHash( str ); 345 for ( int i = keyIndex.GetFirst( hash ); i >= 0; i = keyIndex.GetNext( i ) ) { 346 if ( idStr::Icmp( str, keyVals[i].key ) == 0 ) { 347 return i; 348 } 349 } 350 return -1; 351 } 352 353 /* 354 ======================== 355 idLangDict::FindString_r 356 ======================== 357 */ 358 const char * idLangDict::FindString_r( const char * str, int & depth ) const { 359 depth++; 360 if ( depth > MAX_REDIRECTION_DEPTH ) { 361 // This isn't an error because we assume the error will be obvious somewhere in a GUI or something, 362 // and the whole point of tracking the depth is to avoid a crash. 363 idLib::Warning( "String '%s', indirection depth > %d", str, MAX_REDIRECTION_DEPTH ); 364 return NULL; 365 } 366 367 if ( str == NULL || str[0] == '\0' ) { 368 return NULL; 369 } 370 371 int index = FindStringIndex( str ); 372 if ( index < 0 ) { 373 return NULL; 374 } 375 const char * value = keyVals[index].value; 376 if ( value == NULL ) { 377 return NULL; 378 } 379 if ( IsStringId( value ) ) { 380 // this string is re-directed to another entry 381 return FindString_r( value, depth ); 382 } 383 return value; 384 } 385 386 /* 387 ======================== 388 idLangDict::FindString 389 ======================== 390 */ 391 const char * idLangDict::FindString( const char * str ) const { 392 int depth = 0; 393 return FindString_r( str, depth ); 394 } 395 396 /* 397 ======================== 398 idLangDict::DeleteString 399 ======================== 400 */ 401 bool idLangDict::DeleteString( const char * key ) { 402 return DeleteString( FindStringIndex( key ) ); 403 } 404 405 /* 406 ======================== 407 idLangDict::DeleteString 408 ======================== 409 */ 410 bool idLangDict::DeleteString( const int idx ) { 411 if ( idx < 0 || idx >= keyVals.Num() ) { 412 return false; 413 } 414 415 //mem.PushHeap(); 416 blockAlloc.Free( keyVals[idx].value ); 417 keyVals[idx].value = NULL; 418 //mem.PopHeap(); 419 420 return true; 421 } 422 423 /* 424 ======================== 425 idLangDict::RenameStringKey 426 ======================== 427 */ 428 bool idLangDict::RenameStringKey( const char * oldKey, const char * newKey ) { 429 int index = FindStringIndex( oldKey ); 430 if ( index < 0 ) { 431 return false; 432 } 433 //mem.PushHeap(); 434 blockAlloc.Free( keyVals[index].key ); 435 int newKeyLen = idStr::Length( newKey ); 436 keyVals[index].key = blockAlloc.Alloc( newKeyLen + 1 ); 437 idStr::Copynz( keyVals[index].key, newKey, newKeyLen + 1 ); 438 int oldHash = idStr::IHash( oldKey ); 439 int newHash = idStr::IHash( newKey ); 440 if ( oldHash != newHash ) { 441 keyIndex.Remove( oldHash, index ); 442 keyIndex.Add( newHash, index ); 443 } 444 //mem.PopHeap(); 445 446 return true; 447 } 448 449 /* 450 ======================== 451 idLangDict::SetString 452 ======================== 453 */ 454 bool idLangDict::SetString( const char * key, const char * val ) { 455 int index = FindStringIndex( key ); 456 if ( index < 0 ) { 457 return false; 458 } 459 //mem.PushHeap(); 460 if ( keyVals[index].value != NULL ) { 461 blockAlloc.Free( keyVals[index].value ); 462 } 463 int valLen = idStr::Length( val ); 464 keyVals[index].value = blockAlloc.Alloc( valLen + 1 ); 465 idStr::Copynz( keyVals[index].value, val, valLen + 1 ); 466 //mem.PopHeap(); 467 return true; 468 } 469 470 /* 471 ======================== 472 idLangDict::AddKeyVal 473 ======================== 474 */ 475 void idLangDict::AddKeyVal( const char * key, const char * val ) { 476 if ( SetString( key, val ) ) { 477 return; 478 } 479 //mem.PushHeap(); 480 int keyLen = idStr::Length( key ); 481 char * k = blockAlloc.Alloc( keyLen + 1 ); 482 idStr::Copynz( k, key, keyLen + 1 ); 483 char * v = NULL; 484 if ( val != NULL ) { 485 int valLen = idStr::Length( val ); 486 v = blockAlloc.Alloc( valLen + 1 ); 487 idStr::Copynz( v, val, valLen + 1 ); 488 } 489 int index = keyVals.Append( idLangKeyValue( k, v ) ); 490 int hash = idStr::IHash( key ); 491 keyIndex.Add( hash, index ); 492 //mem.PopHeap(); 493 } 494 495 /* 496 ======================== 497 idLangDict::AddString 498 ======================== 499 */ 500 const char * idLangDict::AddString( const char * val ) { 501 int i = Sys_Milliseconds(); 502 idStr key; 503 sprintf( key, "#str_%06d", ( i++ % 1000000 ) ); 504 while ( FindStringIndex( key ) > 0 ) { 505 sprintf( key, "#str_%06d", ( i++ % 1000000 ) ); 506 } 507 AddKeyVal( key, val ); 508 int index = FindStringIndex( key ); 509 return keyVals[index].key; 510 } 511 512 /* 513 ======================== 514 idLangDict::GetNumKeyVals 515 ======================== 516 */ 517 int idLangDict::GetNumKeyVals() const { 518 return keyVals.Num(); 519 } 520 521 /* 522 ======================== 523 idLangDict::GetKeyVal 524 ======================== 525 */ 526 const idLangKeyValue * idLangDict::GetKeyVal( int i ) const { 527 return &keyVals[i]; 528 } 529 530 /* 531 ======================== 532 idLangDict::IsStringId 533 ======================== 534 */ 535 bool idLangDict::IsStringId( const char * str ) { 536 return idStr::Icmpn( str, KEY_PREFIX, KEY_PREFIX_LEN ) == 0; 537 } 538 539 /* 540 ======================== 541 idLangDict::GetLocalizedString 542 ======================== 543 */ 544 const char * idLangDict::GetLocalizedString( const idStrId & strId ) const { 545 if ( strId.GetIndex() >= 0 && strId.GetIndex() < keyVals.Num() ) { 546 if ( keyVals[ strId.GetIndex() ].value == NULL ) { 547 return keyVals[ strId.GetIndex() ].key; 548 } else { 549 return keyVals[ strId.GetIndex() ].value; 550 } 551 } 552 return ""; 553 } 554 555 /* 556 ================================================================================================ 557 idStrId 558 ================================================================================================ 559 */ 560 561 /* 562 ======================== 563 idStrId::Set 564 ======================== 565 */ 566 void idStrId::Set( const char * key ) { 567 if ( key == NULL || key[0] == 0 ) { 568 index = -1; 569 } else { 570 index = idLocalization::languageDict.FindStringIndex( key ); 571 if ( index < 0 ) { 572 // don't allow setting of string ID's to an unknown ID... this should only be allowed from 573 // the string table tool because additions from anywhere else are not guaranteed to be 574 // saved to the .lang file. 575 idLib::Warning( "Attempted to set unknown string ID '%s'", key ); 576 } 577 } 578 } 579 580 /* 581 ======================== 582 idStrId::GetKey 583 ======================== 584 */ 585 const char * idStrId::GetKey() const { 586 if ( index >= 0 && index < idLocalization::languageDict.keyVals.Num() ) { 587 return idLocalization::languageDict.keyVals[index].key; 588 } 589 return ""; 590 } 591 592 /* 593 ======================== 594 idStrId::GetLocalizedString 595 ======================== 596 */ 597 const char * idStrId::GetLocalizedString() const { 598 return idLocalization::languageDict.GetLocalizedString( *this ); 599 } 600