When extracting resources, always use the new data for generated comments/flags
[TortoiseGit.git] / src / ResText / POFile.cpp
blobca4379e62491bb8d73c115db8274ca0020edbb43
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, _T("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];
60 SecureZeroMemory(filepath, sizeof(filepath));
61 WideCharToMultiByte(CP_ACP, NULL, szPath, -1, filepath, _countof(filepath)-1, NULL, NULL);
63 std::wifstream File;
64 File.imbue(std::locale(std::locale(), new utf8_conversion()));
65 File.open(filepath);
66 if (!File.good())
68 _ftprintf(stderr, _T("can't open input file %s\n"), szPath);
69 return FALSE;
71 std::unique_ptr<TCHAR[]> line(new TCHAR[2*MAX_STRING_LENGTH]);
72 std::vector<std::wstring> entry;
75 File.getline(line.get(), 2*MAX_STRING_LENGTH);
76 if (line.get()[0]==0)
78 //empty line means end of entry!
79 RESOURCEENTRY resEntry;
80 std::wstring msgid;
81 int type = 0;
82 for (std::vector<std::wstring>::iterator I = entry.begin(); I != entry.end(); ++I)
84 if (_tcsncmp(I->c_str(), _T("# "), 2)==0)
86 //user comment
87 resEntry.translatorcomments.push_back(I->c_str());
88 type = 0;
90 if (_tcsncmp(I->c_str(), _T("#."), 2)==0)
92 //automatic comments
93 resEntry.automaticcomments.push_back(I->c_str());
94 type = 0;
96 if (_tcsncmp(I->c_str(), _T("#,"), 2)==0)
98 //flag
99 resEntry.flag = I->c_str();
100 type = 0;
102 if (_tcsncmp(I->c_str(), _T("msgid"), 5)==0)
104 //message id
105 msgid = I->c_str();
106 msgid = std::wstring(msgid.substr(7, msgid.size() - 8));
108 std::wstring s = msgid;
109 s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<wint_t, int>(iswspace))));
110 if (s.size())
111 nEntries++;
112 type = 1;
114 if (_tcsncmp(I->c_str(), _T("msgstr"), 6)==0)
116 //message string
117 resEntry.msgstr = I->c_str();
118 resEntry.msgstr = resEntry.msgstr.substr(8, resEntry.msgstr.length() - 9);
119 if (!resEntry.msgstr.empty())
120 nTranslated++;
121 type = 2;
123 if (_tcsncmp(I->c_str(), _T("\""), 1)==0)
125 if (type == 1)
127 std::wstring temp = I->c_str();
128 temp = temp.substr(1, temp.length()-2);
129 msgid += temp;
131 if (type == 2)
133 if (resEntry.msgstr.empty())
134 nTranslated++;
135 std::wstring temp = I->c_str();
136 temp = temp.substr(1, temp.length()-2);
137 resEntry.msgstr += temp;
141 entry.clear();
142 if ((bUpdateExisting)&&(this->count(msgid) == 0))
143 nDeleted++;
144 else
146 if ((m_bAdjustEOLs)&&(msgid.find(L"\\r\\n") != std::string::npos))
148 AdjustEOLs(resEntry.msgstr);
150 // always use the new data for generated comments/flags
151 auto newEntry = (*this)[msgid];
152 resEntry.automaticcomments = newEntry.automaticcomments;
153 resEntry.flag = newEntry.flag;
154 resEntry.resourceIDs = newEntry.resourceIDs;
156 (*this)[msgid] = resEntry;
158 msgid.clear();
160 else
162 entry.push_back(line.get());
164 } while (File.gcount() > 0);
165 printf(File.getloc().name().c_str());
166 File.close();
167 RESOURCEENTRY emptyentry = {0};
168 (*this)[std::wstring(_T(""))] = emptyentry;
169 if (!m_bQuiet)
170 _ftprintf(stdout, _T("%d Entries found, %d were already translated and %d got deleted\n"), nEntries, nTranslated, nDeleted);
171 return TRUE;
174 BOOL CPOFile::SaveFile(LPCTSTR szPath, LPCTSTR lpszHeaderFile)
176 //since stream classes still expect the filepath in char and not wchar_t
177 //we need to convert the filepath to multibyte
178 char filepath[MAX_PATH+1];
179 int nEntries = 0;
180 SecureZeroMemory(filepath, sizeof(filepath));
181 WideCharToMultiByte(CP_ACP, NULL, szPath, -1, filepath, _countof(filepath)-1, NULL, NULL);
183 std::wofstream File;
184 File.imbue(std::locale(std::locale(), new utf8_conversion()));
185 File.open(filepath, std::ios_base::binary);
187 if ((lpszHeaderFile)&&(lpszHeaderFile[0])&&(PathFileExists(lpszHeaderFile)))
189 // read the header file and save it to the top of the pot file
190 std::wifstream inFile;
191 inFile.imbue(std::locale(std::locale(), new utf8_conversion()));
192 inFile.open(lpszHeaderFile, std::ios_base::binary);
194 wchar_t ch;
195 while(inFile && inFile.get(ch))
196 File.put(ch);
197 inFile.close();
199 else
201 File << _T("# SOME DESCRIPTIVE TITLE.\n");
202 File << _T("# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n");
203 File << _T("# This file is distributed under the same license as the PACKAGE package.\n");
204 File << _T("# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n");
205 File << _T("#\n");
206 File << _T("#, fuzzy\n");
207 File << _T("msgid \"\"\n");
208 File << _T("msgstr \"\"\n");
209 File << _T("\"Project-Id-Version: PACKAGE VERSION\\n\"\n");
210 File << _T("\"Report-Msgid-Bugs-To: \\n\"\n");
211 File << _T("\"POT-Creation-Date: 1900-01-01 00:00+0000\\n\"\n");
212 File << _T("\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n");
213 File << _T("\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n");
214 File << _T("\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n");
215 File << _T("\"MIME-Version: 1.0\\n\"\n");
216 File << _T("\"Content-Type: text/plain; charset=UTF-8\\n\"\n");
217 File << _T("\"Content-Transfer-Encoding: 8bit\\n\"\n\n");
219 File << _T("\n");
220 File << _T("# msgid/msgstr fields for Accelerator keys\n");
221 File << _T("# Format is: \"ID:xxxxxx:VACS+X\" where:\n");
222 File << _T("# ID:xxxxx = the menu ID corresponding to the accelerator\n");
223 File << _T("# V = Virtual key (or blank if not used) - nearly always set!\n");
224 File << _T("# A = Alt key (or blank if not used)\n");
225 File << _T("# C = Ctrl key (or blank if not used)\n");
226 File << _T("# S = Shift key (or blank if not used)\n");
227 File << _T("# X = upper case character\n");
228 File << _T("# e.g. \"V CS+Q\" == Ctrl + Shift + 'Q'\n");
229 File << _T("\n");
230 File << _T("# ONLY Accelerator Keys with corresponding alphanumeric characters can be\n");
231 File << _T("# updated i.e. function keys (F2), special keys (Delete, HoMe) etc. will not.\n");
232 File << _T("\n");
233 File << _T("# ONLY change the msgstr field. Do NOT change any other.\n");
234 File << _T("# If you do not want to change an Accelerator Key, copy msgid to msgstr\n");
235 File << _T("\n");
237 for (std::map<std::wstring, RESOURCEENTRY>::iterator I = this->begin(); I != this->end(); ++I)
239 std::wstring s = I->first;
240 s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<wint_t, int>(iswspace))));
241 if (s.empty())
242 continue;
244 RESOURCEENTRY entry = I->second;
245 for (std::vector<std::wstring>::iterator II = entry.automaticcomments.begin(); II != entry.automaticcomments.end(); ++II)
247 File << II->c_str() << _T("\n");
249 for (std::vector<std::wstring>::iterator II = entry.translatorcomments.begin(); II != entry.translatorcomments.end(); ++II)
251 File << II->c_str() << _T("\n");
253 if (!I->second.resourceIDs.empty())
255 File << _T("#. Resource IDs: (");
257 std::set<INT_PTR>::const_iterator II = I->second.resourceIDs.begin();
258 File << (*II);
259 ++II;
260 while (II != I->second.resourceIDs.end())
262 File << _T(", ");
263 File << (*II);
264 ++II;
266 File << _T(")\n");
268 if (I->second.flag.length() > 0)
269 File << (I->second.flag.c_str()) << _T("\n");
270 File << (_T("msgid \"")) << (I->first.c_str()) << _T("\"\n");
271 File << (_T("msgstr \"")) << (I->second.msgstr.c_str()) << _T("\"\n\n");
272 nEntries++;
274 File.close();
275 if (!m_bQuiet)
276 _ftprintf(stdout, _T("File %s saved, containing %d entries\n"), szPath, nEntries);
277 return TRUE;
280 void CPOFile::AdjustEOLs(std::wstring& str)
282 std::wstring result;
283 std::wstring::size_type pos = 0;
284 for ( ; ; ) // while (true)
286 std::wstring::size_type next = str.find(L"\\r\\n", pos);
287 result.append(str, pos, next-pos);
288 if( next != std::string::npos )
290 result.append(L"\\n");
291 pos = next + 4; // 4 = sizeof("\\r\\n")
293 else
295 break; // exit loop
298 str.swap(result);
299 result.clear();
300 pos = 0;
302 for ( ; ; ) // while (true)
304 std::wstring::size_type next = str.find(L"\\n", pos);
305 result.append(str, pos, next-pos);
306 if( next != std::string::npos )
308 result.append(L"\\r\\n");
309 pos = next + 2; // 2 = sizeof("\\n")
311 else
313 break; // exit loop
316 str.swap(result);