OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4Language.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 
17 /*
18  Language module
19  - handles external language packs
20  - provides info on selectable languages by scanning string tables
21  - loads and sets a language string table (ResStrTable) based on a specified language sequence
22 
23 */
24 
25 #include "C4Include.h"
26 #include "c4group/C4Language.h"
27 
28 #include "game/C4Application.h"
29 #include "c4group/C4Components.h"
30 #include "lib/C4Log.h"
31 #include "config/C4Config.h"
32 #include "game/C4Game.h"
33 
34 template<size_t iBufferSize>
35 static bool GetRelativePath(const char *strPath, const char *strRelativeTo, char(&strBuffer)[iBufferSize])
36 {
37  // Specified path is relative to base path
38  // Copy relative section
39  const char *szCpy;
40  SCopy(szCpy = GetRelativePathS(strPath, strRelativeTo), strBuffer, iBufferSize);
41  // return whether it was made relative
42  return szCpy != strPath;
43 }
44 
46 
48 {
49  Infos = nullptr;
50  PackGroupLocation[0] = 0;
51 }
52 
54 {
55  Clear();
56 }
57 
59 {
60  // Clear (to allow clean re-init)
61  Clear();
62 
63  // Make sure Language.ocg is unpacked (TODO: This won't work properly if Language.ocg is in system data path)
64  // Assume for now that Language.ocg is either at a writable location or unpacked already.
65  // TODO: Use all Language.c4gs that we find, and merge them.
66  // TODO: Use gettext instead?
67  StdStrBuf langPath;
68  C4Reloc::iterator iter;
69  for(iter = Reloc.begin(); iter != Reloc.end(); ++iter)
70  {
71  langPath.Copy((*iter).strBuf + DirSep + C4CFN_Languages);
72  if(ItemExists(langPath.getData()))
73  {
74  if(DirectoryExists(langPath.getData()))
75  break;
76  if(C4Group_UnpackDirectory(langPath.getData()))
77  break;
78  }
79  }
80 
81  // Break if no language.ocg found
82  if(iter != Reloc.end())
83  {
84  // Look for available language packs in Language.ocg
85  C4Group *pPack;
86  char strPackFilename[_MAX_FNAME + 1], strEntry[_MAX_FNAME + 1];
87  if (PackDirectory.Open(langPath.getData()))
88  {
89  while (PackDirectory.FindNextEntry("*.ocg", strEntry))
90  {
91  sprintf(strPackFilename, "%s" DirSep "%s", C4CFN_Languages, strEntry);
92  pPack = new C4Group();
93  if (pPack->Open(strPackFilename))
94  {
95  Packs.RegisterGroup(*pPack, true, C4GSCnt_Language, false);
96  }
97  else
98  {
99  delete pPack;
100  }
101  }
102  }
103 
104  // Now create a pack group for each language pack (these pack groups are child groups
105  // that browse along each pack to access requested data)
106  for (int iPack = 0; (pPack = Packs.GetGroup(iPack)); iPack++)
107  PackGroups.RegisterGroup(*(new C4Group), true, C4GSPrio_Base, C4GSCnt_Language);
108  }
109 
110  // Load language infos by scanning string tables (the engine doesn't really need this at the moment)
111  InitInfos();
112 
113  // Done
114  return true;
115 }
116 
118 {
119  // Clear pack groups
120  PackGroups.Clear();
121  // Clear packs
122  Packs.Clear();
123  // Close pack directory
124  PackDirectory.Close();
125  // Clear infos
126  C4LanguageInfo* pNext;
127  while (Infos)
128  {
129  pNext = Infos->Next;
130  delete Infos;
131  Infos = pNext;
132  }
133  Infos = nullptr;
134 }
135 
137 {
138  return Packs.GetGroupCount();
139 }
140 
142 {
143  int iCount = 0;
144  for (C4LanguageInfo *pInfo = Infos; pInfo; pInfo = pInfo->Next)
145  iCount++;
146  return iCount;
147 }
148 
149 // Returns a set of groups at the specified relative path within all open language packs.
150 
152 {
153  // Build a group set containing the provided group and
154  // alternative groups for cross-loading from a language pack
155  char strRelativePath[_MAX_PATH + 1];
156  char strTargetLocation[_MAX_PATH + 1];
157  char strPackPath[_MAX_PATH + 1];
158  char strPackGroupLocation[_MAX_PATH + 1];
159  char strAdvance[_MAX_PATH + 1];
160 
161  // Store wanted target location
162  SCopy(Config.AtRelativePath(hGroup.GetFullName().getData()), strRelativePath, _MAX_PATH);
163  SCopy(strRelativePath, strTargetLocation, _MAX_PATH);
164 
165  // Adjust location by scenario origin
167  {
168  const char *szScenarioRelativePath = GetRelativePathS(strRelativePath, Config.AtRelativePath(Game.ScenarioFilename));
169  if (szScenarioRelativePath != strRelativePath)
170  {
171  // this is a path within the scenario! Change to origin.
172  size_t iRestPathLen = SLen(szScenarioRelativePath);
173  if (Game.C4S.Head.Origin.getLength() + 1 + iRestPathLen <= _MAX_PATH)
174  {
175  SCopy(Game.C4S.Head.Origin.getData(), strTargetLocation);
176  if (iRestPathLen)
177  {
178  SAppendChar(DirectorySeparator, strTargetLocation);
179  SAppend(szScenarioRelativePath, strTargetLocation);
180  }
181  }
182  }
183  }
184 
185  // Process all language packs (and their respective pack groups)
186  C4Group *pPack, *pPackGroup;
187  for (int iPack = 0; (pPack = Packs.GetGroup(iPack)) && (pPackGroup = PackGroups.GetGroup(iPack)); iPack++)
188  {
189  // Get current pack group position within pack
190  SCopy(pPack->GetFullName().getData(), strPackPath, _MAX_PATH);
191  GetRelativePath(pPackGroup->GetFullName().getData(), strPackPath, strPackGroupLocation);
192 
193  // Pack group is at correct position within pack: continue with next pack
194  if (SEqualNoCase(strPackGroupLocation, strTargetLocation))
195  continue;
196 
197  // Try to backtrack until we can reach the target location as a relative child
198  while ( strPackGroupLocation[0]
199  && !GetRelativePath(strTargetLocation, strPackGroupLocation, strAdvance)
200  && pPackGroup->OpenMother() )
201  {
202  // Update pack group location
203  GetRelativePath(pPackGroup->GetFullName().getData(), strPackPath, strPackGroupLocation);
204  }
205 
206  // We can reach the target location as a relative child
207  if (strPackGroupLocation[0] && GetRelativePath(strTargetLocation, strPackGroupLocation, strAdvance))
208  {
209  // Advance pack group to relative child
210  pPackGroup->OpenChild(strAdvance);
211  }
212 
213  // Cannot reach by advancing: need to close and reopen (rewinding group file)
214  else
215  {
216  // Close pack group (if it is open at all)
217  pPackGroup->Close();
218  // Reopen pack group to relative position in language pack if possible
219  pPackGroup->OpenAsChild(pPack, strTargetLocation);
220  }
221 
222  }
223 
224  // Store new target location
225  SCopy(strTargetLocation, PackGroupLocation, _MAX_FNAME);
226 
227  C4GroupSet r;
228  // Provided group gets highest priority
229  r.RegisterGroup(hGroup, false, 1000, C4GSCnt_Component);
230  // register currently open pack groups
231  r.RegisterGroups(PackGroups, C4GSCnt_Language);
232  return r;
233 }
234 
235 bool C4Language::LoadComponentHost(C4ComponentHost *host, C4Group &hGroup, const char *szFilename, const char *szLanguage)
236 {
237  assert(host);
238  if (!host) return false;
239  C4GroupSet hGroups = ::Languages.GetPackGroups(hGroup);
240  return host->Load(hGroups, szFilename, szLanguage);
241 }
242 
243 void C4Language::InitInfos()
244 {
245  C4Group hGroup;
246  // First, look in System.ocg
247  if (Reloc.Open(hGroup, C4CFN_System))
248  {
249  LoadInfos(hGroup);
250  hGroup.Close();
251  }
252  // Now look through the registered packs
253  C4Group *pPack;
254  for (int iPack = 0; (pPack = Packs.GetGroup(iPack)); iPack++)
255  // Does it contain a System.ocg child group?
256  if (hGroup.OpenAsChild(pPack, C4CFN_System))
257  {
258  LoadInfos(hGroup);
259  hGroup.Close();
260  }
261 }
262 
263 namespace
264 {
265  std::string GetResStr(const char *id, const char *stringtbl)
266  {
267  // The C++11 standard does not specify whether $ and ^ match
268  // the beginning or end of a line, respectively, and it seems
269  // like in some implementations they only match the beginning
270  // or end of the whole string. See also #1127.
271  static std::regex line_pattern("(?:\n|^)([^=]+)=(.*?)\r?(?=\n|$)", static_cast<std::regex::flag_type>(std::regex_constants::optimize | std::regex_constants::ECMAScript));
272 
273  assert(stringtbl);
274  if (!stringtbl)
275  {
276  return std::string();
277  }
278 
279  // Get beginning and end iterators of stringtbl
280  const char *begin = stringtbl;
281  const char *end = begin + std::char_traits<char>::length(begin);
282 
283  for (auto it = std::cregex_iterator(begin, end, line_pattern); it != std::cregex_iterator(); ++it)
284  {
285  assert(it->size() == 3);
286  if (it->size() != 3)
287  continue;
288 
289  std::string key = (*it)[1];
290  if (key != id)
291  continue;
292 
293  std::string val = (*it)[2];
294  return val;
295  }
296 
297  // If we get here, there was no such string in the string table
298  // return the input string so there's at least *something*
299  return id;
300  }
301 
302  template<size_t N>
303  void CopyResStr(const char *id, const char *stringtbl, char (&dest)[N])
304  {
305  std::string value = GetResStr(id, stringtbl);
306  std::strncpy(dest, value.c_str(), N);
307  dest[N - 1] = '\0';
308  }
309 }
310 
311 void C4Language::LoadInfos(C4Group &hGroup)
312 {
313  char strEntry[_MAX_FNAME + 1];
314  char *strTable;
315  // Look for language string tables
316  hGroup.ResetSearch();
317  while (hGroup.FindNextEntry(C4CFN_Language, strEntry))
318  // For now, we will only load info on the first string table found for a given
319  // language code as there is currently no handling for selecting different string tables
320  // of the same code - the system always loads the first string table found for a given code
321  if (!FindInfo(GetFilenameOnly(strEntry) + SLen(GetFilenameOnly(strEntry)) - 2))
322  // Load language string table
323  if (hGroup.LoadEntry(strEntry, &strTable, 0, 1))
324  {
325  // New language info
326  C4LanguageInfo *pInfo = new C4LanguageInfo;
327  // Get language code by entry name
328  SCopy(GetFilenameOnly(strEntry) + SLen(GetFilenameOnly(strEntry)) - 2, pInfo->Code, 2);
329  SCapitalize(pInfo->Code);
330  // Get language name, info, fallback from table
331  CopyResStr("IDS_LANG_NAME", strTable, pInfo->Name);
332  CopyResStr("IDS_LANG_INFO", strTable, pInfo->Info);
333  CopyResStr("IDS_LANG_FALLBACK", strTable, pInfo->Fallback);
334  // Safety: pipe character is not allowed in any language info string
335  SReplaceChar(pInfo->Name, '|', ' ');
336  SReplaceChar(pInfo->Info, '|', ' ');
337  SReplaceChar(pInfo->Fallback, '|', ' ');
338  // Delete table
339  delete [] strTable;
340  // Add info to list
341  pInfo->Next = Infos;
342  Infos = pInfo;
343  }
344 }
345 
347 {
348  for (C4LanguageInfo *pInfo = Infos; pInfo; pInfo = pInfo->Next)
349  if (iIndex <= 0)
350  return pInfo;
351  else
352  iIndex--;
353  return nullptr;
354 }
355 
357 {
358  for (C4LanguageInfo *pInfo = Infos; pInfo; pInfo = pInfo->Next)
359  if (SEqualNoCase(pInfo->Code, strCode, 2))
360  return pInfo;
361  return nullptr;
362 }
363 
364 bool C4Language::LoadLanguage(const char *strLanguages)
365 {
366  // Clear old string table
367  ClearLanguage();
368  // Try to load string table according to language sequence
369  char strLanguageCode[2 + 1];
370  for (int i = 0; SCopySegment(strLanguages, i, strLanguageCode, ',', 2, true); i++)
371  if (InitStringTable(strLanguageCode))
372  return true;
373  // No matching string table found: hardcoded fallback to US
374  if (InitStringTable("US"))
375  return true;
376  // No string table present: this is really bad
377  Log("Error loading language string table.");
378  return false;
379 }
380 
381 bool C4Language::InitStringTable(const char *strCode)
382 {
383  C4Group hGroup;
384  // First, look in System.ocg
385  if (LoadStringTable(Application.SystemGroup, strCode))
386  return true;
387  // Now look through the registered packs
388  C4Group *pPack;
389  for (int iPack = 0; (pPack = Packs.GetGroup(iPack)); iPack++)
390  // Does it contain a System.ocg child group?
391  if (hGroup.OpenAsChild(pPack, C4CFN_System))
392  {
393  if (LoadStringTable(hGroup, strCode))
394  { hGroup.Close(); return true; }
395  hGroup.Close();
396  }
397  // No matching string table found
398  return false;
399 }
400 
401 bool C4Language::LoadStringTable(C4Group &hGroup, const char *strCode)
402 {
403  // Compose entry name
404  char strEntry[_MAX_FNAME + 1];
405  sprintf(strEntry, "Language%s.txt", strCode); // ...should use C4CFN_Language here
406  // Load string table
407  if (!C4LangStringTable::GetSystemStringTable().Load(hGroup, strEntry))
408  return false;
409  // Success
410  return true;
411 }
412 
414 {
415  // Clear resource string table
417 }
418 
419 // Closes any open language pack that has the specified path.
420 
421 bool C4Language::CloseGroup(const char *strPath)
422 {
423  // Check all open language packs
424  C4Group *pPack;
425  for (int iPack = 0; (pPack = Packs.GetGroup(iPack)); iPack++)
426  if (ItemIdentical(strPath, pPack->GetFullName().getData()))
427  {
428  Packs.UnregisterGroup(iPack);
429  return true;
430  }
431  // No pack of that path
432  return false;
433 }
const char * getData() const
Definition: StdBuf.h:450
StdCopyStrBuf Origin
Definition: C4Scenario.h:80
char ScenarioFilename[_MAX_PATH+1]
Definition: C4Game.h:104
C4Config Config
Definition: C4Config.cpp:837
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:122
void SAppendChar(char cChar, char *szStr)
Definition: Standard.cpp:235
bool RegisterGroup(C4Group &rGroup, bool fOwnGrp, int32_t Priority, int32_t Contents, bool fCheckContent=true)
Definition: C4GroupSet.cpp:90
bool Load(C4Group &hGroup, const char *szFilename, const char *szLanguage=nullptr)
void SAppend(const char *szSource, char *szTarget, int iMaxL)
Definition: Standard.cpp:227
bool ItemIdentical(const char *szFilename1, const char *szFilename2)
Definition: StdFile.cpp:865
C4Game Game
Definition: C4Globals.cpp:52
C4Scenario C4S
Definition: C4Game.h:76
#define sprintf
Definition: Standard.h:171
#define C4GSCnt_Component
Definition: C4GroupSet.h:41
bool SCopySegment(const char *szString, int iSegment, char *sTarget, char cSeparator, int iMaxL, bool fSkipWhitespace)
Definition: Standard.cpp:243
void ClearLanguage()
Definition: C4Language.cpp:413
PathList::const_iterator iterator
Definition: C4Reloc.h:38
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:177
bool RegisterGroups(const C4GroupSet &rCopy, int32_t Contents, const char *szFilename=nullptr, int32_t iMaxSkipID=0)
Definition: C4GroupSet.cpp:130
bool LoadEntry(const char *szEntryName, char **lpbpBuf, size_t *ipSize=nullptr, int iAppendZeros=0)
Definition: C4Group.cpp:1893
bool C4Group_UnpackDirectory(const char *szFilename)
Definition: C4Group.cpp:306
void SCapitalize(char *str)
Definition: Standard.cpp:324
char Name[C4MaxLanguageInfo+1]
Definition: C4Language.h:33
int32_t GetGroupCount()
Definition: C4GroupSet.cpp:253
#define _MAX_PATH
const char * GetFilenameOnly(const char *strFilename)
Definition: StdFile.cpp:70
size_t SLen(const char *sptr)
Definition: Standard.h:78
#define C4CFN_Languages
Definition: C4Components.h:32
bool UnregisterGroup(int32_t iIndex)
Definition: C4GroupSet.cpp:276
C4GroupSet GetPackGroups(C4Group &)
Definition: C4Language.cpp:151
bool OpenMother()
Definition: C4Group.cpp:2197
char Code[2+1]
Definition: C4Language.h:32
bool CloseGroup(const char *strPath)
Definition: C4Language.cpp:421
static bool LoadComponentHost(C4ComponentHost *host, C4Group &hGroup, const char *szFilename, const char *szLanguage)
Definition: C4Language.cpp:235
StdStrBuf GetFullName() const
Definition: C4Group.cpp:2078
C4Language Languages
Definition: C4Language.cpp:45
bool Open(const char *szGroupName, bool fCreate=false)
Definition: C4Group.cpp:514
char Fallback[C4MaxLanguageInfo+1]
Definition: C4Language.h:35
static C4LangStringTable & GetSystemStringTable()
C4LanguageInfo * GetInfo(int iIndex)
Definition: C4Language.cpp:346
bool Close()
Definition: C4Group.cpp:755
bool Open(C4Group &hGroup, const char *filename) const
Definition: C4Reloc.cpp:69
#define _MAX_FNAME
bool OpenChild(const char *strEntry)
Definition: C4Group.cpp:2161
char * GetExtension(char *szFilename)
Definition: StdFile.cpp:131
#define C4CFN_Language
Definition: C4Components.h:138
iterator begin() const
Definition: C4Reloc.cpp:59
int GetInfoCount()
Definition: C4Language.cpp:141
C4Reloc Reloc
Definition: C4Reloc.cpp:22
void Clear()
Definition: C4GroupSet.cpp:52
C4LanguageInfo * FindInfo(const char *strCode)
Definition: C4Language.cpp:356
bool DirectoryExists(const char *szFilename)
Definition: StdFile.cpp:694
bool LoadLanguage(const char *strLanguages)
Definition: C4Language.cpp:364
bool FindNextEntry(const char *szWildCard, StdStrBuf *sFileName=nullptr, size_t *iSize=nullptr, bool fStartAtFilename=false)
Definition: C4Group.cpp:1780
bool Init()
Definition: C4Language.cpp:58
#define C4GSCnt_Language
Definition: C4GroupSet.h:40
bool Log(const char *szMessage)
Definition: C4Log.cpp:195
iterator end() const
Definition: C4Reloc.cpp:64
size_t getLength() const
Definition: StdBuf.h:453
void ResetSearch(bool reload_contents=false)
Definition: C4Group.cpp:1013
C4SHead Head
Definition: C4Scenario.h:230
const char * AtRelativePath(const char *szFilename)
Definition: C4Config.cpp:660
void Clear()
Definition: C4Language.cpp:117
#define C4CFN_System
Definition: C4Components.h:29
#define DirSep
bool ItemExists(const char *szItemName)
Definition: StdFile.h:75
int GetPackCount()
Definition: C4Language.cpp:136
#define DirectorySeparator
void Copy()
Definition: StdBuf.h:475
#define C4GSPrio_Base
Definition: C4GroupSet.h:23
C4Group SystemGroup
Definition: C4Application.h:40
C4Application Application
Definition: C4Globals.cpp:44
bool OpenAsChild(C4Group *pMother, const char *szEntryName, bool fExclusive=false, bool fCreate=false)
Definition: C4Group.cpp:1585
void SReplaceChar(char *str, char fc, char tc)
Definition: Standard.cpp:318
const char * GetRelativePathS(const char *strPath, const char *strRelativeTo)
Definition: StdFile.cpp:221
C4Group * GetGroup(int32_t iIndex)
Definition: C4GroupSet.cpp:261
char Info[C4MaxLanguageInfo+1]
Definition: C4Language.h:34