Use StartsWith to reduce magic numbers in code
[TortoiseGit.git] / src / ResText / POFile.cpp
blob2e70a35adef9d9a4f53c407a313d720a351e4ee3
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012-2013 - TortoiseGit
4 // Copyright (C) 2003-2008, 2011-2016 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include <Shlwapi.h>
21 #include <fstream>
22 #include "codecvt.h"
23 #include "Utils.h"
24 #include "ResModule.h"
25 #include "POFile.h"
27 #include <algorithm>
28 #include <cctype>
29 #include <memory>
30 #include <functional>
32 #define MYERROR {CUtils::Error(); return FALSE;}
34 CPOFile::CPOFile()
35 : m_bQuiet(false)
36 , m_bAdjustEOLs(false)
40 CPOFile::~CPOFile(void)
44 static bool StartsWith(const wchar_t* heystacl, const wchar_t* needle)
46 return wcsncmp(heystacl, needle, wcslen(needle)) == 0;
49 BOOL CPOFile::ParseFile(LPCTSTR szPath, BOOL bUpdateExisting, bool bAdjustEOLs)
51 if (!PathFileExists(szPath))
52 return FALSE;
54 m_bAdjustEOLs = bAdjustEOLs;
56 if (!m_bQuiet)
57 _ftprintf(stdout, L"parsing file %s...\n", szPath);
59 int nEntries = 0;
60 int nDeleted = 0;
61 int nTranslated = 0;
62 //since stream classes still expect the filepath in char and not wchar_t
63 //we need to convert the filepath to multibyte
64 char filepath[MAX_PATH + 1] = { 0 };
65 WideCharToMultiByte(CP_ACP, 0, szPath, -1, filepath, _countof(filepath) - 1, nullptr, nullptr);
67 std::wifstream File;
68 File.imbue(std::locale(std::locale(), new utf8_conversion()));
69 File.open(filepath);
70 if (!File.good())
72 _ftprintf(stderr, L"can't open input file %s\n", szPath);
73 return FALSE;
75 auto line = std::make_unique<TCHAR[]>(2 * MAX_STRING_LENGTH);
76 std::vector<std::wstring> entry;
79 File.getline(line.get(), 2*MAX_STRING_LENGTH);
80 if (line.get()[0]==0)
82 //empty line means end of entry!
83 RESOURCEENTRY resEntry = {0};
84 std::wstring msgid;
85 std::wstring regexsearch, regexreplace;
86 int type = 0;
87 for (auto I = entry.cbegin(); I != entry.cend(); ++I)
89 if (StartsWith(I->c_str(), L"# "))
91 //user comment
92 if (StartsWith(I->c_str(), L"# regexsearch="))
93 regexsearch = I->substr(14);
94 else if (StartsWith(I->c_str(), L"# regexreplace="))
95 regexreplace = I->substr(15);
96 else
97 resEntry.translatorcomments.push_back(I->c_str());
98 if (!regexsearch.empty() && !regexreplace.empty())
100 m_regexes.push_back(std::make_tuple(regexsearch, regexreplace));
101 regexsearch.clear();
102 regexreplace.clear();
104 type = 0;
106 if (StartsWith(I->c_str(), L"#."))
108 //automatic comments
109 resEntry.automaticcomments.push_back(I->c_str());
110 type = 0;
112 if (StartsWith(I->c_str(), L"#,"))
114 //flag
115 resEntry.flag = I->c_str();
116 type = 0;
118 if (StartsWith(I->c_str(), L"msgid"))
120 //message id
121 msgid = I->c_str();
122 msgid = std::wstring(msgid.substr(7, msgid.size() - 8));
124 std::wstring s = msgid;
125 s.erase(s.cbegin(), std::find_if(s.cbegin(), s.cend(), std::not1(std::ptr_fun<wint_t, int>(iswspace))));
126 if (s.size())
127 nEntries++;
128 type = 1;
130 if (StartsWith(I->c_str(), L"msgstr"))
132 //message string
133 resEntry.msgstr = I->c_str();
134 resEntry.msgstr = resEntry.msgstr.substr(8, resEntry.msgstr.length() - 9);
135 if (!resEntry.msgstr.empty())
136 nTranslated++;
137 type = 2;
139 if (StartsWith(I->c_str(), L"\""))
141 if (type == 1)
143 std::wstring temp = I->c_str();
144 temp = temp.substr(1, temp.length()-2);
145 msgid += temp;
147 if (type == 2)
149 if (resEntry.msgstr.empty())
150 nTranslated++;
151 std::wstring temp = I->c_str();
152 temp = temp.substr(1, temp.length()-2);
153 resEntry.msgstr += temp;
157 entry.clear();
158 if ((bUpdateExisting)&&(this->count(msgid) == 0))
159 nDeleted++;
160 else
162 if ((m_bAdjustEOLs)&&(msgid.find(L"\\r\\n") != std::string::npos))
164 AdjustEOLs(resEntry.msgstr);
166 // always use the new data for generated comments/flags
167 auto newEntry = (*this)[msgid];
168 resEntry.automaticcomments = newEntry.automaticcomments;
169 resEntry.flag = newEntry.flag;
170 resEntry.resourceIDs = newEntry.resourceIDs;
172 (*this)[msgid] = resEntry;
174 msgid.clear();
176 else
178 entry.push_back(line.get());
180 } while (File.gcount() > 0);
181 printf("%s", File.getloc().name().c_str());
182 File.close();
183 RESOURCEENTRY emptyentry = {0};
184 (*this)[std::wstring(L"")] = emptyentry;
185 if (!m_bQuiet)
186 _ftprintf(stdout, L"%d Entries found, %d were already translated and %d got deleted\n", nEntries, nTranslated, nDeleted);
187 return TRUE;
190 BOOL CPOFile::SaveFile(LPCTSTR szPath, LPCTSTR lpszHeaderFile)
192 //since stream classes still expect the filepath in char and not wchar_t
193 //we need to convert the filepath to multibyte
194 char filepath[MAX_PATH + 1] = { 0 };
195 int nEntries = 0;
196 WideCharToMultiByte(CP_ACP, 0, szPath, -1, filepath, _countof(filepath) - 1, nullptr, nullptr);
198 std::wofstream File;
199 File.imbue(std::locale(std::locale(), new utf8_conversion()));
200 File.open(filepath, std::ios_base::binary);
202 if ((lpszHeaderFile)&&(lpszHeaderFile[0])&&(PathFileExists(lpszHeaderFile)))
204 // read the header file and save it to the top of the pot file
205 std::wifstream inFile;
206 inFile.imbue(std::locale(std::locale(), new utf8_conversion()));
207 inFile.open(lpszHeaderFile, std::ios_base::binary);
209 wchar_t ch;
210 while(inFile && inFile.get(ch))
211 File.put(ch);
212 inFile.close();
214 else
216 File << L"# SOME DESCRIPTIVE TITLE.\n";
217 File << L"# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n";
218 File << L"# This file is distributed under the same license as the PACKAGE package.\n";
219 File << L"# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n";
220 File << L"#\n";
221 File << L"#, fuzzy\n";
222 File << L"msgid \"\"\n";
223 File << L"msgstr \"\"\n";
224 File << L"\"Project-Id-Version: PACKAGE VERSION\\n\"\n";
225 File << L"\"Report-Msgid-Bugs-To: \\n\"\n";
226 File << L"\"POT-Creation-Date: 1900-01-01 00:00+0000\\n\"\n";
227 File << L"\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n";
228 File << L"\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n";
229 File << L"\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n";
230 File << L"\"MIME-Version: 1.0\\n\"\n";
231 File << L"\"Content-Type: text/plain; charset=UTF-8\\n\"\n";
232 File << L"\"Content-Transfer-Encoding: 8bit\\n\"\n\n";
234 File << L"\n";
235 File << L"# msgid/msgstr fields for Accelerator keys\n";
236 File << L"# Format is: \"ID:xxxxxx:VACS+X\" where:\n";
237 File << L"# ID:xxxxx = the menu ID corresponding to the accelerator\n";
238 File << L"# V = Virtual key (or blank if not used) - nearly always set!\n";
239 File << L"# A = Alt key (or blank if not used)\n";
240 File << L"# C = Ctrl key (or blank if not used)\n";
241 File << L"# S = Shift key (or blank if not used)\n";
242 File << L"# X = upper case character\n";
243 File << L"# e.g. \"V CS+Q\" == Ctrl + Shift + 'Q'\n";
244 File << L"\n";
245 File << L"# ONLY Accelerator Keys with corresponding alphanumeric characters can be\n";
246 File << L"# updated i.e. function keys (F2), special keys (Delete, HoMe) etc. will not.\n";
247 File << L"\n";
248 File << L"# ONLY change the msgstr field. Do NOT change any other.\n";
249 File << L"# If you do not want to change an Accelerator Key, copy msgid to msgstr\n";
250 File << L"\n";
252 for (auto I = this->cbegin(); I != this->cend(); ++I)
254 std::wstring s = I->first;
255 s.erase(s.cbegin(), std::find_if(s.cbegin(), s.cend(), std::not1(std::ptr_fun<wint_t, int>(iswspace))));
256 if (s.empty())
257 continue;
259 RESOURCEENTRY entry = I->second;
260 for (auto II = entry.automaticcomments.cbegin(); II != entry.automaticcomments.cend(); ++II)
262 File << II->c_str() << L"\n";
264 for (auto II = entry.translatorcomments.cbegin(); II != entry.translatorcomments.cend(); ++II)
266 File << II->c_str() << L"\n";
268 if (!I->second.resourceIDs.empty())
270 File << L"#. Resource IDs: (";
272 auto II = I->second.resourceIDs.begin();
273 File << (*II);
274 ++II;
275 while (II != I->second.resourceIDs.end())
277 File << L", ";
278 File << (*II);
279 ++II;
281 File << L")\n";
283 if (I->second.flag.length() > 0)
284 File << (I->second.flag.c_str()) << L"\n";
285 File << (L"msgid \"") << (I->first.c_str()) << L"\"\n";
286 File << (L"msgstr \"") << (I->second.msgstr.c_str()) << L"\"\n\n";
287 nEntries++;
289 File.close();
290 if (!m_bQuiet)
291 _ftprintf(stdout, L"File %s saved, containing %d entries\n", szPath, nEntries);
292 return TRUE;
295 void CPOFile::AdjustEOLs(std::wstring& str)
297 std::wstring result;
298 std::wstring::size_type pos = 0;
299 for ( ; ; ) // while (true)
301 std::wstring::size_type next = str.find(L"\\r\\n", pos);
302 result.append(str, pos, next-pos);
303 if( next != std::string::npos )
305 result.append(L"\\n");
306 pos = next + 4; // 4 = sizeof("\\r\\n")
308 else
310 break; // exit loop
313 str.swap(result);
314 result.clear();
315 pos = 0;
317 for ( ; ; ) // while (true)
319 std::wstring::size_type next = str.find(L"\\n", pos);
320 result.append(str, pos, next-pos);
321 if( next != std::string::npos )
323 result.append(L"\\r\\n");
324 pos = next + 2; // 2 = sizeof("\\n")
326 else
328 break; // exit loop
331 str.swap(result);