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