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