| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) RedWolf Design |
| 5 | * Copyright (c) 2017-2021, The LegacyClonk 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 | // Loads StringTbl* and replaces $..$-strings by localized versions |
| 18 | |
| 19 | #include "C4Constants.h" |
| 20 | #include "C4LangStringTable.h" |
| 21 | #include "C4Log.h" |
| 22 | |
| 23 | #include <vector> |
| 24 | |
| 25 | struct C4StringTableEntry |
| 26 | { |
| 27 | const char *pName, *pEntry; |
| 28 | |
| 29 | C4StringTableEntry(const char *pName, const char *pEntry) |
| 30 | : pName(pName), pEntry(pEntry) {} |
| 31 | }; |
| 32 | |
| 33 | void C4LangStringTable::ReplaceStrings(const StdStrBuf &rBuf, StdStrBuf &rTarget, const char *szParentFilePath) |
| 34 | { |
| 35 | if (!rBuf.getLength()) |
| 36 | { |
| 37 | return; |
| 38 | } |
| 39 | // grab char ptr from buf |
| 40 | const char *Data = rBuf.getData(); |
| 41 | |
| 42 | // string table |
| 43 | std::vector<C4StringTableEntry> Entries; |
| 44 | |
| 45 | // read string table |
| 46 | char *pStrTblBuf = nullptr; |
| 47 | if (GetData()) |
| 48 | { |
| 49 | // copy data |
| 50 | pStrTblBuf = new char[GetDataSize() + 1]; |
| 51 | SCopy(szSource: GetData(), sTarget: pStrTblBuf, iMaxL: GetDataSize()); |
| 52 | // find entries |
| 53 | const char *pLine = pStrTblBuf; |
| 54 | bool found_eq = false; |
| 55 | for (char *pPos = pStrTblBuf; *pPos; pPos++) |
| 56 | if (*pPos == '\n' || *pPos == '\r') |
| 57 | { |
| 58 | found_eq = false; |
| 59 | *pPos = '\0'; pLine = pPos + 1; |
| 60 | } |
| 61 | else if (*pPos == '=' && !found_eq) |
| 62 | { |
| 63 | *pPos = '\0'; |
| 64 | // We found an '=' sign, so parse everything to end of line from now on, ignoring more '=' signs. Bug #2327. |
| 65 | found_eq = true; |
| 66 | // add entry |
| 67 | Entries.push_back(x: C4StringTableEntry(pLine, pPos + 1)); |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | // Find Replace Positions |
| 72 | auto iScriptLen = SLen(sptr: Data); |
| 73 | struct RP { const char *Pos, *String; unsigned int Len; RP *Next; } *pRPList = nullptr, *pRPListEnd = nullptr; |
| 74 | for (const char *pPos = SSearch(szString: Data, szIndex: "$" ); pPos; pPos = SSearch(szString: pPos, szIndex: "$" )) |
| 75 | { |
| 76 | // Get name |
| 77 | char szStringName[C4MaxName + 1]; |
| 78 | SCopyUntil(szSource: pPos, sTarget: szStringName, cUntil: '$', iMaxL: C4MaxName); pPos += SLen(sptr: szStringName) + 1; |
| 79 | if (*(pPos - 1) != '$') continue; |
| 80 | // valid? |
| 81 | const char *pPos2 = szStringName; |
| 82 | while (*pPos2) |
| 83 | if (!IsIdentifier(cChar: *(pPos2++))) |
| 84 | break; |
| 85 | if (*pPos2) continue; |
| 86 | // check termination |
| 87 | // search in string table |
| 88 | const char *pStrTblEntry = nullptr; |
| 89 | for (unsigned int i = 0; i < Entries.size(); i++) |
| 90 | if (SEqual(szStr1: szStringName, szStr2: Entries[i].pName)) |
| 91 | { |
| 92 | pStrTblEntry = Entries[i].pEntry; break; |
| 93 | } |
| 94 | // found? |
| 95 | if (!pStrTblEntry) |
| 96 | { |
| 97 | LogNTr(level: spdlog::level::warn, fmt: "{}: string table entry not found: \"{}\"" , args: FilePath[0] ? FilePath : (szParentFilePath ? szParentFilePath : "Unknown" ), args: +szStringName); |
| 98 | continue; |
| 99 | } |
| 100 | // add new replace-position entry |
| 101 | RP *pnRP = new RP; |
| 102 | pnRP->Pos = pPos - SLen(sptr: szStringName) - 2; |
| 103 | pnRP->String = pStrTblEntry; |
| 104 | pnRP->Len = SLen(sptr: szStringName) + 2; |
| 105 | pnRP->Next = nullptr; |
| 106 | pRPListEnd = (pRPListEnd ? pRPListEnd->Next : pRPList) = pnRP; |
| 107 | // calculate new script length |
| 108 | iScriptLen += SLen(sptr: pStrTblEntry) - pnRP->Len; |
| 109 | } |
| 110 | // Alloc new Buffer |
| 111 | char *pNewBuf; |
| 112 | StdStrBuf sNewBuf; |
| 113 | sNewBuf.SetLength(iScriptLen); |
| 114 | pNewBuf = sNewBuf.getMData(); |
| 115 | // Copy data |
| 116 | const char *pRPos = Data; char *pWPos = pNewBuf; |
| 117 | for (RP *pRPPos = pRPList; pRPPos; pRPPos = pRPPos->Next) |
| 118 | { |
| 119 | // copy preceding string data |
| 120 | SCopy(szSource: pRPos, sTarget: pWPos, iMaxL: pRPPos->Pos - pRPos); |
| 121 | pWPos += pRPPos->Pos - pRPos; |
| 122 | // copy string |
| 123 | SCopyUntil(szSource: pRPPos->String, sTarget: pWPos, cUntil: '\n'); |
| 124 | SReplaceChar(str: pWPos, fc: '\r', tc: '\0'); |
| 125 | // advance |
| 126 | pRPos = pRPPos->Pos + pRPPos->Len; |
| 127 | pWPos += SLen(sptr: pWPos); |
| 128 | } |
| 129 | SCopy(szSource: pRPos, sTarget: pWPos); |
| 130 | |
| 131 | while (pRPList) |
| 132 | { |
| 133 | RP *pRP = pRPList; |
| 134 | pRPList = pRP->Next; |
| 135 | delete pRP; |
| 136 | } |
| 137 | |
| 138 | // free buffer |
| 139 | delete[] pStrTblBuf; |
| 140 | |
| 141 | // assign this buf |
| 142 | rTarget.Clear(); |
| 143 | rTarget.Take(Buf2&: sNewBuf); |
| 144 | } |
| 145 | |
| 146 | void C4LangStringTable::ReplaceStrings(StdStrBuf &rBuf) |
| 147 | { |
| 148 | ReplaceStrings(rBuf, rTarget&: rBuf, szParentFilePath: nullptr); |
| 149 | } |
| 150 | |