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 #if 0 232 uint32 checkFP = ComputeFingerprint(); 233 if (fFingerprint != checkFP) { 234 fprintf(stderr, "plaintext-catalog(sig=%s, lang=%s) " 235 "has wrong fingerprint after load (%u instead of %u). " 236 "The catalog data may be corrupted, so this catalog is " 237 "skipped.\n", 238 fSignature.String(), fLanguageName.String(), checkFP, 239 fFingerprint); 240 return B_BAD_DATA; 241 } 242 #endif 243 244 // some information living in member variables needs to be copied 245 // to attributes. Although these attributes should have been written 246 // when creating the catalog, we make sure that they exist there: 247 UpdateAttributes(path); 248 return B_OK; 249 } 250 251 252 status_t 253 PlainTextCatalog::WriteToFile(const char *path) 254 { 255 BString textContent; 256 BFile catalogFile; 257 258 if (path) 259 fPath = path; 260 status_t res = catalogFile.SetTo(fPath.String(), 261 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 262 if (res != B_OK) 263 return res; 264 265 UpdateFingerprint(); 266 // make sure we have the correct fingerprint before we flatten it 267 268 textContent << kCatArchiveVersion << "\t" << fLanguageName.String() << "\t" 269 << fSignature.String() << "\t" << fFingerprint << "\n"; 270 271 res = catalogFile.Write(textContent.String(),textContent.Length()); 272 if (res != textContent.Length()) 273 return res; 274 275 CatMap::Iterator iter = fCatMap.GetIterator(); 276 CatMap::Entry entry; 277 BString original; 278 BString translated; 279 280 while (iter.HasNext()) { 281 entry = iter.Next(); 282 original = entry.key.fString; 283 translated = entry.value; 284 escapeQuotedChars(original); 285 escapeQuotedChars(translated); 286 textContent.Truncate(0); 287 textContent << original.String() << "\t" 288 << entry.key.fContext.String() << "\t" 289 << entry.key.fComment.String() << "\t" 290 << translated.String() << "\n"; 291 res = catalogFile.Write(textContent.String(),textContent.Length()); 292 if (res != textContent.Length()) 293 return res; 294 } 295 296 // set mimetype-, language- and signature-attributes: 297 UpdateAttributes(catalogFile); 298 299 return B_OK; 300 } 301 302 303 /* 304 * writes mimetype, language-name and signature of catalog into the 305 * catalog-file. 306 */ 307 void 308 PlainTextCatalog::UpdateAttributes(BFile& catalogFile) 309 { 310 // useless on the build-tool-side 311 } 312 313 314 void 315 PlainTextCatalog::UpdateAttributes(const char* path) 316 { 317 BEntry entry(path); 318 BFile node(&entry, B_READ_WRITE); 319 UpdateAttributes(node); 320 } 321 322 323 BCatalogAddOn * 324 PlainTextCatalog::Instantiate(const char *signature, const char *language, 325 uint32 fingerprint) 326 { 327 PlainTextCatalog *catalog 328 = new(std::nothrow) PlainTextCatalog(signature, language, fingerprint); 329 if (catalog && catalog->InitCheck() != B_OK) { 330 delete catalog; 331 return NULL; 332 } 333 return catalog; 334 } 335 336 337 extern "C" BCatalogAddOn * 338 instantiate_catalog(const char *signature, const char *language, 339 uint32 fingerprint) 340 { 341 PlainTextCatalog *catalog 342 = new(std::nothrow) PlainTextCatalog(signature, language, fingerprint); 343 if (catalog && catalog->InitCheck() != B_OK) { 344 delete catalog; 345 return NULL; 346 } 347 return catalog; 348 } 349 350 351 extern "C" 352 BCatalogAddOn *create_catalog(const char *signature, 353 const char *language) 354 { 355 PlainTextCatalog *catalog 356 = new(std::nothrow) PlainTextCatalog("emptycat", signature, language); 357 return catalog; 358 } 359 360 361 uint8 gCatalogAddOnPriority = 99; 362 // give low priority to this add on, we don't want it to be actually used 363