xref: /haiku/src/tools/locale/PlainTextCatalog.cpp (revision 68ea01249e1e2088933cb12f9c28d4e5c5d1c9ef)
1 /*
2 ** Copyright 2009 Adrien Destugues, pulkomandy@gmail.com. All rights reserved.
3 ** Distributed under the terms of the MIT License.
4 */
5 
6 #include <PlainTextCatalog.h>
7 
8 #include <assert.h>
9 #include <cstdio>
10 #include <iostream>
11 #include <fstream>
12 #include <new>
13 #include <sstream>
14 #include <string>
15 
16 #include <syslog.h>
17 
18 #include <Application.h>
19 #include <Directory.h>
20 #include <File.h>
21 #include <FindDirectory.h>
22 #include <fs_attr.h>
23 #include <Message.h>
24 #include <Mime.h>
25 #include <Path.h>
26 #include <Resources.h>
27 #include <Roster.h>
28 #include <String.h>
29 
30 #include <Catalog.h>
31 
32 
33 using BPrivate::PlainTextCatalog;
34 using std::min;
35 using std::max;
36 using std::pair;
37 
38 
39 /*
40  *	This file implements the plain text catalog-type for the Haiku
41  *	locale kit. It is not meant to be used in applications like other add ons,
42  *	but only as a way to get an easy to translate file for developers.
43  */
44 
45 
46 namespace BPrivate {
47 
48 
49 const char *PlainTextCatalog::kCatMimeType
50 	= "locale/x-vnd.Be.locale-catalog.plaintext";
51 
52 static int16 kCatArchiveVersion = 1;
53 	// version of the catalog archive structure, bump this if you change it!
54 
55 
56 // Note: \xNN is not replaced back, so you get the unescaped value in the catkey
57 // file. This is fine for regular unicode chars (B_UTF8_ELLIPSIS) but may be
58 // dangerous if you use \x10 as a newline...
59 void
60 escapeQuotedChars(BString& stringToEscape)
61 {
62 	stringToEscape.ReplaceAll("\\", "\\\\");
63 	stringToEscape.ReplaceAll("\n", "\\n");
64 	stringToEscape.ReplaceAll("\t", "\\t");
65 	stringToEscape.ReplaceAll("\"", "\\\"");
66 }
67 
68 
69 /*
70  * constructs a PlainTextCatalog with given signature and language and reads
71  * the catalog from disk.
72  * InitCheck() will be B_OK if catalog could be loaded successfully, it will
73  * give an appropriate error-code otherwise.
74  */
75 PlainTextCatalog::PlainTextCatalog(const entry_ref &signature,
76 	const char *language, uint32 fingerprint)
77 	:
78 	HashMapCatalog("", language, fingerprint)
79 {
80 	// Look for the catalog in the directory we are going to use
81 	// This constructor is not used so lets disable that...
82 
83 	fInitCheck = B_NOT_SUPPORTED;
84 	fprintf(stderr,
85 		"trying to load plaintext-catalog(lang=%s) results in %s\n",
86 		language, strerror(fInitCheck));
87 }
88 
89 
90 /*
91  * constructs an empty PlainTextCatalog with given sig and language.
92  * This is used for editing/testing purposes.
93  * InitCheck() will always be B_OK.
94  */
95 PlainTextCatalog::PlainTextCatalog(const char *path, const char *signature,
96 	const char *language)
97 	:
98 	HashMapCatalog(signature, language, 0),
99 	fPath(path)
100 {
101 	fInitCheck = B_OK;
102 }
103 
104 
105 PlainTextCatalog::~PlainTextCatalog()
106 {
107 }
108 
109 
110 status_t
111 PlainTextCatalog::ReadFromFile(const char *path)
112 {
113 	std::fstream catalogFile;
114 	std::string  currentItem;
115 
116 	if (!path)
117 		path = fPath.String();
118 
119 	catalogFile.open(path, std::fstream::in);
120 	if (!catalogFile.is_open()) {
121 		fprintf(stderr, "couldn't open catalog at %s\n", path);
122 		return B_ENTRY_NOT_FOUND;
123 	}
124 
125 	// Now read all the data from the file
126 
127 	// The first line holds some info about the catalog :
128 	// ArchiveVersion \t LanguageName \t AppSignature \t FingerPrint
129 	if (std::getline(catalogFile, currentItem, '\t').good()) {
130 		// Get the archive version
131 		int arcver= -1;
132 		std::istringstream ss(currentItem);
133 		ss >> arcver;
134 		if (ss.fail()) {
135 	 		// can't convert to int
136 			fprintf(stderr,
137 				"Unable to extract archive version ( string: %s ) from %s\n",
138 				currentItem.c_str(), path);
139 			return B_ERROR;
140 		}
141 
142 		if (arcver != kCatArchiveVersion) {
143 			// wrong version
144 			fprintf(stderr,
145 				"Wrong archive version ! Got %d instead of %d from %s\n", arcver,
146 				kCatArchiveVersion, path);
147 			return B_ERROR;
148 		}
149 	} else {
150 		fprintf(stderr, "Unable to read from catalog %s\n", path);
151 		return B_ERROR;
152 	}
153 
154 	if (std::getline(catalogFile, currentItem, '\t').good()) {
155 		// Get the language
156 		fLanguageName = currentItem.c_str() ;
157 	} else {
158 		fprintf(stderr, "Unable to get language from %s\n", path);
159 		return B_ERROR;
160 	}
161 
162 	if (std::getline(catalogFile, currentItem, '\t').good()) {
163 		// Get the signature
164 		fSignature = currentItem.c_str() ;
165 	} else {
166 		fprintf(stderr, "Unable to get signature from %s\n", path);
167 		return B_ERROR;
168 	}
169 
170 	if (std::getline(catalogFile, currentItem).good()) {
171 		// Get the fingerprint
172 		std::istringstream ss(currentItem);
173 		uint32 foundFingerprint;
174 		ss >> foundFingerprint;
175 		if (ss.fail()) {
176 			fprintf(stderr, "Unable to get fingerprint (%s) from %s\n",
177 					currentItem.c_str(), path);
178 			return B_ERROR;
179 		}
180 
181 		if (fFingerprint == 0)
182 			fFingerprint = foundFingerprint;
183 
184 		if (fFingerprint != foundFingerprint) {
185 			return B_MISMATCHED_VALUES;
186 		}
187 	} else {
188 		fprintf(stderr, "Unable to get fingerprint from %s\n", path);
189 		return B_ERROR;
190 	}
191 
192 	// We managed to open the file, so we remember it's the one we are using
193 	fPath = path;
194 	// fprintf(stderr, "LocaleKit Plaintext: found catalog at %s\n", path);
195 
196 	std::string originalString;
197 	std::string context;
198 	std::string comment;
199 	std::string translated;
200 
201 	while (std::getline(catalogFile, originalString,'\t').good()) {
202 		// Each line is : "original string \t context \t comment \t translation"
203 
204 		if (!std::getline(catalogFile, context,'\t').good()) {
205 			fprintf(stderr, "Unable to get context for string %s from %s\n",
206 				originalString.c_str(), path);
207 			return B_ERROR;
208 		}
209 
210 		if (!std::getline(catalogFile, comment,'\t').good()) {
211 			fprintf(stderr, "Unable to get comment for string %s from %s\n",
212 				originalString.c_str(), path);
213 			return B_ERROR;
214 		}
215 
216 		if (!std::getline(catalogFile, translated).good()) {
217 			fprintf(stderr,
218 				"Unable to get translated text for string %s from %s\n",
219 				originalString.c_str(), path);
220 			return B_ERROR;
221 		}
222 
223 		// We could do that :
224 		// SetString(key, translated.c_str());
225 		// but we want to keep the strings in the new catkey. Hash collisions
226 		// happen, you know. (and CatKey::== will fail)
227 		SetString(originalString.c_str(), translated.c_str(), context.c_str(),
228 			comment.c_str());
229 	}
230 
231 	catalogFile.close();
232 
233 #if 0
234 	uint32 checkFP = ComputeFingerprint();
235 	if (fFingerprint != checkFP) {
236 		fprintf(stderr, "plaintext-catalog(sig=%s, lang=%s) "
237 			"has wrong fingerprint after load (%u instead of %u). "
238 			"The catalog data may be corrupted, so this catalog is "
239 			"skipped.\n",
240 			fSignature.String(), fLanguageName.String(), checkFP,
241 			fFingerprint);
242 		return B_BAD_DATA;
243 	}
244 #endif
245 
246 	// some information living in member variables needs to be copied
247 	// to attributes. Although these attributes should have been written
248 	// when creating the catalog, we make sure that they exist there:
249 	UpdateAttributes(path);
250 	return B_OK;
251 }
252 
253 
254 status_t
255 PlainTextCatalog::WriteToFile(const char *path)
256 {
257 	BString textContent;
258 	BFile catalogFile;
259 
260 	if (path)
261 		fPath = path;
262 	status_t res = catalogFile.SetTo(fPath.String(),
263 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
264 	if (res != B_OK)
265 		return res;
266 
267 	UpdateFingerprint();
268 		// make sure we have the correct fingerprint before we flatten it
269 
270 	textContent << kCatArchiveVersion << "\t" << fLanguageName.String() << "\t"
271 		<< fSignature.String() << "\t" << fFingerprint << "\n";
272 
273 	res = catalogFile.Write(textContent.String(),textContent.Length());
274 	if (res != textContent.Length())
275 		return res;
276 
277 	CatMap::Iterator iter = fCatMap.GetIterator();
278 	CatMap::Entry entry;
279 	BString original;
280 	BString comment;
281 	BString translated;
282 
283 	while (iter.HasNext()) {
284 		entry = iter.Next();
285 		original = entry.key.fString;
286 		comment = entry.key.fComment;
287 		translated = entry.value;
288 
289 		escapeQuotedChars(original);
290 		escapeQuotedChars(comment);
291 		escapeQuotedChars(translated);
292 
293 		textContent.Truncate(0);
294 		textContent << original.String() << "\t"
295 			<< entry.key.fContext.String() << "\t"
296 			<< comment << "\t"
297 			<< translated.String() << "\n";
298 		res = catalogFile.Write(textContent.String(),textContent.Length());
299 		if (res != textContent.Length())
300 			return res;
301 	}
302 
303 	// set mimetype-, language- and signature-attributes:
304 	UpdateAttributes(catalogFile);
305 
306 	return B_OK;
307 }
308 
309 
310 /*
311  * writes mimetype, language-name and signature of catalog into the
312  * catalog-file.
313  */
314 void
315 PlainTextCatalog::UpdateAttributes(BFile& catalogFile)
316 {
317 	// useless on the build-tool-side
318 }
319 
320 
321 void
322 PlainTextCatalog::UpdateAttributes(const char* path)
323 {
324 	BEntry entry(path);
325 	BFile node(&entry, B_READ_WRITE);
326 	UpdateAttributes(node);
327 }
328 
329 
330 BCatalogData *
331 PlainTextCatalog::Instantiate(const entry_ref &owner, const char *language,
332 	uint32 fingerprint)
333 {
334 	PlainTextCatalog *catalog
335 		= new(std::nothrow) PlainTextCatalog(owner, language, fingerprint);
336 	if (catalog && catalog->InitCheck() != B_OK) {
337 		delete catalog;
338 		return NULL;
339 	}
340 	return catalog;
341 }
342 
343 
344 } // namespace BPrivate
345 
346 
347 extern "C" BCatalogData *
348 instantiate_catalog(const entry_ref &owner, const char *language,
349 	uint32 fingerprint)
350 {
351 	return PlainTextCatalog::Instantiate(owner, language, fingerprint);
352 }
353 
354 
355 extern "C" BCatalogData *
356 create_catalog(const char *signature, const char *language)
357 {
358 	PlainTextCatalog *catalog
359 		= new(std::nothrow) PlainTextCatalog("emptycat", signature, language);
360 	return catalog;
361 }
362 
363 
364 uint8 gCatalogAddOnPriority = 99;
365 	// give low priority to this add on, we don't want it to be actually used
366