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