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