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