1#include "merge_res_and_xliff.h" 2 3#include "file_utils.h" 4#include "Perforce.h" 5#include "log.h" 6#include <stdio.h> 7 8static set<StringResource>::const_iterator 9find_id(const set<StringResource>& s, const string& id, int index) 10{ 11 for (set<StringResource>::const_iterator it = s.begin(); it != s.end(); it++) { 12 if (it->id == id && it->index == index) { 13 return it; 14 } 15 } 16 return s.end(); 17} 18 19static set<StringResource>::const_iterator 20find_in_xliff(const set<StringResource>& s, const string& filename, const string& id, int index, 21 int version, const Configuration& config) 22{ 23 for (set<StringResource>::const_iterator it = s.begin(); it != s.end(); it++) { 24 if (it->file == filename && it->id == id && it->index == index && it->version == version 25 && it->config == config) { 26 return it; 27 } 28 } 29 return s.end(); 30} 31 32 33static void 34printit(const set<StringResource>& s, const set<StringResource>::const_iterator& it) 35{ 36 if (it == s.end()) { 37 printf("(none)\n"); 38 } else { 39 printf("id=%s index=%d config=%s file=%s value='%s'\n", it->id.c_str(), it->index, 40 it->config.ToString().c_str(), it->file.c_str(), 41 it->value->ToString(ANDROID_NAMESPACES).c_str()); 42 } 43} 44 45StringResource 46convert_resource(const StringResource& s, const string& file, const Configuration& config, 47 int version, const string& versionString) 48{ 49 return StringResource(s.pos, file, config, s.id, s.index, s.value ? s.value->Clone() : NULL, 50 version, versionString, s.comment); 51} 52 53static bool 54resource_has_contents(const StringResource& res) 55{ 56 XMLNode* value = res.value; 57 if (value == NULL) { 58 return false; 59 } 60 string contents = value->ContentsToString(ANDROID_NAMESPACES); 61 return contents != ""; 62} 63 64ValuesFile* 65merge_res_and_xliff(const ValuesFile* en_currentFile, 66 const ValuesFile* xx_currentFile, const ValuesFile* xx_oldFile, 67 const string& filename, const XLIFFFile* xliffFile) 68{ 69 bool success = true; 70 71 Configuration en_config = xliffFile->SourceConfig(); 72 Configuration xx_config = xliffFile->TargetConfig(); 73 string currentVersion = xliffFile->CurrentVersion(); 74 75 ValuesFile* result = new ValuesFile(xx_config); 76 77 set<StringResource> en_cur = en_currentFile->GetStrings(); 78 set<StringResource> xx_cur = xx_currentFile->GetStrings(); 79 set<StringResource> xx_old = xx_oldFile->GetStrings(); 80 set<StringResource> xliff = xliffFile->GetStringResources(); 81 82 // for each string in en_current 83 for (set<StringResource>::const_iterator en_c = en_cur.begin(); 84 en_c != en_cur.end(); en_c++) { 85 set<StringResource>::const_iterator xx_c = find_id(xx_cur, en_c->id, en_c->index); 86 set<StringResource>::const_iterator xx_o = find_id(xx_old, en_c->id, en_c->index); 87 set<StringResource>::const_iterator xlf = find_in_xliff(xliff, en_c->file, en_c->id, 88 en_c->index, CURRENT_VERSION, xx_config); 89 90 if (false) { 91 printf("\nen_c: "); printit(en_cur, en_c); 92 printf("xx_c: "); printit(xx_cur, xx_c); 93 printf("xx_o: "); printit(xx_old, xx_o); 94 printf("xlf: "); printit(xliff, xlf); 95 } 96 97 // if it changed between xx_old and xx_current, use xx_current 98 // (someone changed it by hand) 99 if (xx_o != xx_old.end() && xx_c != xx_cur.end()) { 100 string xx_o_value = xx_o->value->ToString(ANDROID_NAMESPACES); 101 string xx_c_value = xx_c->value->ToString(ANDROID_NAMESPACES); 102 if (xx_o_value != xx_c_value && xx_c_value != "") { 103 StringResource r(convert_resource(*xx_c, filename, xx_config, 104 CURRENT_VERSION, currentVersion)); 105 if (resource_has_contents(r)) { 106 result->AddString(r); 107 } 108 continue; 109 } 110 } 111 112 // if it is present in xliff, use that 113 // (it just got translated) 114 if (xlf != xliff.end() && xlf->value->ToString(ANDROID_NAMESPACES) != "") { 115 StringResource r(convert_resource(*xlf, filename, xx_config, 116 CURRENT_VERSION, currentVersion)); 117 if (resource_has_contents(r)) { 118 result->AddString(r); 119 } 120 } 121 122 // if it is present in xx_current, use that 123 // (it was already translated, and not retranslated) 124 // don't filter out empty strings if they were added by hand, the above code just 125 // guarantees that this tool never adds an empty one. 126 if (xx_c != xx_cur.end()) { 127 StringResource r(convert_resource(*xx_c, filename, xx_config, 128 CURRENT_VERSION, currentVersion)); 129 result->AddString(r); 130 } 131 132 // othwerwise, leave it out. The resource fall-through code will use the English 133 // one at runtime, and the xliff export code will pick it up for translation next time. 134 } 135 136 if (success) { 137 return result; 138 } else { 139 delete result; 140 return NULL; 141 } 142} 143 144 145struct MergedFile { 146 XLIFFFile* xliff; 147 string xliffFilename; 148 string original; 149 string translated; 150 ValuesFile* en_current; 151 ValuesFile* xx_current; 152 ValuesFile* xx_old; 153 ValuesFile* xx_new; 154 string xx_new_text; 155 string xx_new_filename; 156 bool new_file; 157 bool deleted_file; 158 159 MergedFile(); 160 MergedFile(const MergedFile&); 161}; 162 163struct compare_filenames { 164 bool operator()(const MergedFile& lhs, const MergedFile& rhs) const 165 { 166 return lhs.original < rhs.original; 167 } 168}; 169 170MergedFile::MergedFile() 171 :xliff(NULL), 172 xliffFilename(), 173 original(), 174 translated(), 175 en_current(NULL), 176 xx_current(NULL), 177 xx_old(NULL), 178 xx_new(NULL), 179 xx_new_text(), 180 xx_new_filename(), 181 new_file(false), 182 deleted_file(false) 183{ 184} 185 186MergedFile::MergedFile(const MergedFile& that) 187 :xliff(that.xliff), 188 xliffFilename(that.xliffFilename), 189 original(that.original), 190 translated(that.translated), 191 en_current(that.en_current), 192 xx_current(that.xx_current), 193 xx_old(that.xx_old), 194 xx_new(that.xx_new), 195 xx_new_text(that.xx_new_text), 196 xx_new_filename(that.xx_new_filename), 197 new_file(that.new_file), 198 deleted_file(that.deleted_file) 199{ 200} 201 202 203typedef set<MergedFile, compare_filenames> MergedFileSet; 204 205int 206do_merge(const vector<string>& xliffFilenames) 207{ 208 int err = 0; 209 MergedFileSet files; 210 211 printf("\rPreparing..."); fflush(stdout); 212 string currentChange = Perforce::GetCurrentChange(true); 213 214 // for each xliff, make a MergedFile record and do a little error checking 215 for (vector<string>::const_iterator xliffFilename=xliffFilenames.begin(); 216 xliffFilename!=xliffFilenames.end(); xliffFilename++) { 217 XLIFFFile* xliff = XLIFFFile::Parse(*xliffFilename); 218 if (xliff == NULL) { 219 fprintf(stderr, "localize import: unable to read file %s\n", xliffFilename->c_str()); 220 err = 1; 221 continue; 222 } 223 224 set<string> xf = xliff->Files(); 225 for (set<string>::const_iterator f=xf.begin(); f!=xf.end(); f++) { 226 MergedFile mf; 227 mf.xliff = xliff; 228 mf.xliffFilename = *xliffFilename; 229 mf.original = *f; 230 mf.translated = translated_file_name(mf.original, xliff->TargetConfig().locale); 231 log_printf("mf.translated=%s mf.original=%s locale=%s\n", mf.translated.c_str(), 232 mf.original.c_str(), xliff->TargetConfig().locale.c_str()); 233 234 if (files.find(mf) != files.end()) { 235 fprintf(stderr, "%s: duplicate string resources for file %s\n", 236 xliffFilename->c_str(), f->c_str()); 237 fprintf(stderr, "%s: previously defined here.\n", 238 files.find(mf)->xliffFilename.c_str()); 239 err = 1; 240 continue; 241 } 242 files.insert(mf); 243 } 244 } 245 246 size_t deletedFileCount = 0; 247 size_t J = files.size() * 3; 248 size_t j = 1; 249 // Read all of the files from perforce. 250 for (MergedFileSet::iterator mf = files.begin(); mf != files.end(); mf++) { 251 MergedFile* file = const_cast<MergedFile*>(&(*mf)); 252 // file->en_current 253 print_file_status(j++, J); 254 file->en_current = get_values_file(file->original, file->xliff->SourceConfig(), 255 CURRENT_VERSION, currentChange, true); 256 if (file->en_current == NULL) { 257 // deleted file 258 file->deleted_file = true; 259 deletedFileCount++; 260 continue; 261 } 262 263 // file->xx_current; 264 print_file_status(j++, J); 265 file->xx_current = get_values_file(file->translated, file->xliff->TargetConfig(), 266 CURRENT_VERSION, currentChange, false); 267 if (file->xx_current == NULL) { 268 file->xx_current = new ValuesFile(file->xliff->TargetConfig()); 269 file->new_file = true; 270 } 271 272 // file->xx_old (note that the xliff's current version is our old version, because that 273 // was the current version when it was exported) 274 print_file_status(j++, J); 275 file->xx_old = get_values_file(file->translated, file->xliff->TargetConfig(), 276 OLD_VERSION, file->xliff->CurrentVersion(), false); 277 if (file->xx_old == NULL) { 278 file->xx_old = new ValuesFile(file->xliff->TargetConfig()); 279 file->new_file = true; 280 } 281 } 282 283 // merge them 284 for (MergedFileSet::iterator mf = files.begin(); mf != files.end(); mf++) { 285 MergedFile* file = const_cast<MergedFile*>(&(*mf)); 286 if (file->deleted_file) { 287 continue; 288 } 289 file->xx_new = merge_res_and_xliff(file->en_current, file->xx_current, file->xx_old, 290 file->original, file->xliff); 291 } 292 293 // now is a good time to stop if there was an error 294 if (err != 0) { 295 return err; 296 } 297 298 // locate the files 299 j = 1; 300 for (MergedFileSet::iterator mf = files.begin(); mf != files.end(); mf++) { 301 MergedFile* file = const_cast<MergedFile*>(&(*mf)); 302 print_file_status(j++, J, "Locating"); 303 304 file->xx_new_filename = Perforce::Where(file->translated, true); 305 if (file->xx_new_filename == "") { 306 fprintf(stderr, "\nWas not able to determine the location of depot file %s\n", 307 file->translated.c_str()); 308 err = 1; 309 } 310 } 311 312 if (err != 0) { 313 return err; 314 } 315 316 // p4 edit the files 317 // only do this if it changed - no need to submit files that haven't changed meaningfully 318 vector<string> filesToEdit; 319 vector<string> filesToAdd; 320 vector<string> filesToDelete; 321 for (MergedFileSet::iterator mf = files.begin(); mf != files.end(); mf++) { 322 MergedFile* file = const_cast<MergedFile*>(&(*mf)); 323 if (file->deleted_file) { 324 filesToDelete.push_back(file->xx_new_filename); 325 continue; 326 } 327 string xx_current_text = file->xx_current->ToString(); 328 string xx_new_text = file->xx_new->ToString(); 329 if (xx_current_text != xx_new_text) { 330 if (file->xx_new->GetStrings().size() == 0) { 331 file->deleted_file = true; 332 filesToDelete.push_back(file->xx_new_filename); 333 } else { 334 file->xx_new_text = xx_new_text; 335 if (file->new_file) { 336 filesToAdd.push_back(file->xx_new_filename); 337 } else { 338 filesToEdit.push_back(file->xx_new_filename); 339 } 340 } 341 } 342 } 343 if (filesToAdd.size() == 0 && filesToEdit.size() == 0 && deletedFileCount == 0) { 344 printf("\nAll of the files are the same. Nothing to change.\n"); 345 return 0; 346 } 347 if (filesToEdit.size() > 0) { 348 printf("\np4 editing files...\n"); 349 if (0 != Perforce::EditFiles(filesToEdit, true)) { 350 return 1; 351 } 352 } 353 354 355 printf("\n"); 356 357 for (MergedFileSet::iterator mf = files.begin(); mf != files.end(); mf++) { 358 MergedFile* file = const_cast<MergedFile*>(&(*mf)); 359 if (file->deleted_file) { 360 continue; 361 } 362 if (file->xx_new_text != "" && file->xx_new_filename != "") { 363 if (0 != write_to_file(file->xx_new_filename, file->xx_new_text)) { 364 err = 1; 365 } 366 } 367 } 368 369 if (err != 0) { 370 return err; 371 } 372 373 if (filesToAdd.size() > 0) { 374 printf("p4 adding %zd new files...\n", filesToAdd.size()); 375 err = Perforce::AddFiles(filesToAdd, true); 376 } 377 378 if (filesToDelete.size() > 0) { 379 printf("p4 deleting %zd removed files...\n", filesToDelete.size()); 380 err = Perforce::DeleteFiles(filesToDelete, true); 381 } 382 383 if (err != 0) { 384 return err; 385 } 386 387 printf("\n" 388 "Theoretically, this merge was successfull. Next you should\n" 389 "review the diffs, get a code review, and submit it. Enjoy.\n\n"); 390 return 0; 391} 392 393