Improve readability of version string
[TortoiseGit.git] / src / ResText / POFile.cpp
bloba4a6bfa9147a0b4ef46adc58809339346de82da7
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012-2013 - TortoiseGit
4 // Copyright (C) 2003-2008, 2011-2015 - 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 BOOL CPOFile::ParseFile(LPCTSTR szPath, BOOL bUpdateExisting, bool bAdjustEOLs)
46 if (!PathFileExists(szPath))
47 return FALSE;
49 m_bAdjustEOLs = bAdjustEOLs;
51 if (!m_bQuiet)
52 _ftprintf(stdout, L"parsing file %s...\n", szPath);
54 int nEntries = 0;
55 int nDeleted = 0;
56 int nTranslated = 0;
57 //since stream classes still expect the filepath in char and not wchar_t
58 //we need to convert the filepath to multibyte
59 char filepath[MAX_PATH + 1] = { 0 };
60 WideCharToMultiByte(CP_ACP, 0, szPath, -1, filepath, _countof(filepath) - 1, nullptr, nullptr);
62 std::wifstream File;
63 File.imbue(std::locale(std::locale(), new utf8_conversion()));
64 File.open(filepath);
65 if (!File.good())
67 _ftprintf(stderr, L"can't open input file %s\n", szPath);
68 return FALSE;
70 auto line = std::make_unique<TCHAR[]>(2 * MAX_STRING_LENGTH);
71 std::vector<std::wstring> entry;
74 File.getline(line.get(), 2*MAX_STRING_LENGTH);
75 if (line.get()[0]==0)
77 //empty line means end of entry!
78 RESOURCEENTRY resEntry = {0};
79 std::wstring msgid;
80 int type = 0;
81 for (auto I = entry.cbegin(); I != entry.cend(); ++I)
83 if (wcsncmp(I->c_str(), L"# ", 2)==0)
85 //user comment
86 resEntry.translatorcomments.push_back(I->c_str());
87 type = 0;
89 if (wcsncmp(I->c_str(), L"#.", 2)==0)
91 //automatic comments
92 resEntry.automaticcomments.push_back(I->c_str());
93 type = 0;
95 if (wcsncmp(I->c_str(), L"#,", 2)==0)
97 //flag
98 resEntry.flag = I->c_str();
99 type = 0;
101 if (wcsncmp(I->c_str(), L"msgid", 5)==0)
103 //message id
104 msgid = I->c_str();
105 msgid = std::wstring(msgid.substr(7, msgid.size() - 8));
107 std::wstring s = msgid;
108 s.erase(s.cbegin(), std::find_if(s.cbegin(), s.cend(), std::not1(std::ptr_fun<wint_t, int>(iswspace))));
109 if (s.size())
110 nEntries++;
111 type = 1;
113 if (wcsncmp(I->c_str(), L"msgstr", 6)==0)
115 //message string
116 resEntry.msgstr = I->c_str();
117 resEntry.msgstr = resEntry.msgstr.substr(8, resEntry.msgstr.length() - 9);
118 if (!resEntry.msgstr.empty())
119 nTranslated++;
120 type = 2;
122 if (wcsncmp(I->c_str(), L"\"", 1)==0)
124 if (type == 1)
126 std::wstring temp = I->c_str();
127 temp = temp.substr(1, temp.length()-2);
128 msgid += temp;
130 if (type == 2)
132 if (resEntry.msgstr.empty())
133 nTranslated++;
134 std::wstring temp = I->c_str();
135 temp = temp.substr(1, temp.length()-2);
136 resEntry.msgstr += temp;
140 entry.clear();
141 if ((bUpdateExisting)&&(this->count(msgid) == 0))
142 nDeleted++;
143 else
145 if ((m_bAdjustEOLs)&&(msgid.find(L"\\r\\n") != std::string::npos))
147 AdjustEOLs(resEntry.msgstr);
149 // always use the new data for generated comments/flags
150 auto newEntry = (*this)[msgid];
151 resEntry.automaticcomments = newEntry.automaticcomments;
152 resEntry.flag = newEntry.flag;
153 resEntry.resourceIDs = newEntry.resourceIDs;
155 (*this)[msgid] = resEntry;
157 msgid.clear();
159 else
161 entry.push_back(line.get());
163 } while (File.gcount() > 0);
164 printf(File.getloc().name().c_str());
165 File.close();
166 RESOURCEENTRY emptyentry = {0};
167 (*this)[std::wstring(L"")] = emptyentry;
168 if (!m_bQuiet)
169 _ftprintf(stdout, L"%d Entries found, %d were already translated and %d got deleted\n", nEntries, nTranslated, nDeleted);
170 return TRUE;
173 BOOL CPOFile::SaveFile(LPCTSTR szPath, LPCTSTR lpszHeaderFile)
175 //since stream classes still expect the filepath in char and not wchar_t
176 //we need to convert the filepath to multibyte
177 char filepath[MAX_PATH + 1] = { 0 };
178 int nEntries = 0;
179 WideCharToMultiByte(CP_ACP, 0, szPath, -1, filepath, _countof(filepath) - 1, nullptr, nullptr);
181 std::wofstream File;
182 File.imbue(std::locale(std::locale(), new utf8_conversion()));
183 File.open(filepath, std::ios_base::binary);
185 if ((lpszHeaderFile)&&(lpszHeaderFile[0])&&(PathFileExists(lpszHeaderFile)))
187 // read the header file and save it to the top of the pot file
188 std::wifstream inFile;
189 inFile.imbue(std::locale(std::locale(), new utf8_conversion()));
190 inFile.open(lpszHeaderFile, std::ios_base::binary);
192 wchar_t ch;
193 while(inFile && inFile.get(ch))
194 File.put(ch);
195 inFile.close();
197 else
199 File << L"# SOME DESCRIPTIVE TITLE.\n";
200 File << L"# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n";
201 File << L"# This file is distributed under the same license as the PACKAGE package.\n";
202 File << L"# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n";
203 File << L"#\n";
204 File << L"#, fuzzy\n";
205 File << L"msgid \"\"\n";
206 File << L"msgstr \"\"\n";
207 File << L"\"Project-Id-Version: PACKAGE VERSION\\n\"\n";
208 File << L"\"Report-Msgid-Bugs-To: \\n\"\n";
209 File << L"\"POT-Creation-Date: 1900-01-01 00:00+0000\\n\"\n";
210 File << L"\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n";
211 File << L"\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n";
212 File << L"\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n";
213 File << L"\"MIME-Version: 1.0\\n\"\n";
214 File << L"\"Content-Type: text/plain; charset=UTF-8\\n\"\n";
215 File << L"\"Content-Transfer-Encoding: 8bit\\n\"\n\n";
217 File << L"\n";
218 File << L"# msgid/msgstr fields for Accelerator keys\n";
219 File << L"# Format is: \"ID:xxxxxx:VACS+X\" where:\n";
220 File << L"# ID:xxxxx = the menu ID corresponding to the accelerator\n";
221 File << L"# V = Virtual key (or blank if not used) - nearly always set!\n";
222 File << L"# A = Alt key (or blank if not used)\n";
223 File << L"# C = Ctrl key (or blank if not used)\n";
224 File << L"# S = Shift key (or blank if not used)\n";
225 File << L"# X = upper case character\n";
226 File << L"# e.g. \"V CS+Q\" == Ctrl + Shift + 'Q'\n";
227 File << L"\n";
228 File << L"# ONLY Accelerator Keys with corresponding alphanumeric characters can be\n";
229 File << L"# updated i.e. function keys (F2), special keys (Delete, HoMe) etc. will not.\n";
230 File << L"\n";
231 File << L"# ONLY change the msgstr field. Do NOT change any other.\n";
232 File << L"# If you do not want to change an Accelerator Key, copy msgid to msgstr\n";
233 File << L"\n";
235 for (auto I = this->cbegin(); I != this->cend(); ++I)
237 std::wstring s = I->first;
238 s.erase(s.cbegin(), std::find_if(s.cbegin(), s.cend(), std::not1(std::ptr_fun<wint_t, int>(iswspace))));
239 if (s.empty())
240 continue;
242 RESOURCEENTRY entry = I->second;
243 for (auto II = entry.automaticcomments.cbegin(); II != entry.automaticcomments.cend(); ++II)
245 File << II->c_str() << L"\n";
247 for (auto II = entry.translatorcomments.cbegin(); II != entry.translatorcomments.cend(); ++II)
249 File << II->c_str() << L"\n";
251 if (!I->second.resourceIDs.empty())
253 File << L"#. Resource IDs: (";
255 auto II = I->second.resourceIDs.begin();
256 File << (*II);
257 ++II;
258 while (II != I->second.resourceIDs.end())
260 File << L", ";
261 File << (*II);
262 ++II;
264 File << L")\n";
266 if (I->second.flag.length() > 0)
267 File << (I->second.flag.c_str()) << L"\n";
268 File << (L"msgid \"") << (I->first.c_str()) << L"\"\n";
269 File << (L"msgstr \"") << (I->second.msgstr.c_str()) << L"\"\n\n";
270 nEntries++;
272 File.close();
273 if (!m_bQuiet)
274 _ftprintf(stdout, L"File %s saved, containing %d entries\n", szPath, nEntries);
275 return TRUE;
278 void CPOFile::AdjustEOLs(std::wstring& str)
280 std::wstring result;
281 std::wstring::size_type pos = 0;
282 for ( ; ; ) // while (true)
284 std::wstring::size_type next = str.find(L"\\r\\n", pos);
285 result.append(str, pos, next-pos);
286 if( next != std::string::npos )
288 result.append(L"\\n");
289 pos = next + 4; // 4 = sizeof("\\r\\n")
291 else
293 break; // exit loop
296 str.swap(result);
297 result.clear();
298 pos = 0;
300 for ( ; ; ) // while (true)
302 std::wstring::size_type next = str.find(L"\\n", pos);
303 result.append(str, pos, next-pos);
304 if( next != std::string::npos )
306 result.append(L"\\r\\n");
307 pos = next + 2; // 2 = sizeof("\\n")
309 else
311 break; // exit loop
314 str.swap(result);