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