#include "merge_res_and_xliff.h" #include "file_utils.h" #include "Perforce.h" #include "log.h" #include static set::const_iterator find_id(const set& s, const string& id, int index) { for (set::const_iterator it = s.begin(); it != s.end(); it++) { if (it->id == id && it->index == index) { return it; } } return s.end(); } static set::const_iterator find_in_xliff(const set& s, const string& filename, const string& id, int index, int version, const Configuration& config) { for (set::const_iterator it = s.begin(); it != s.end(); it++) { if (it->file == filename && it->id == id && it->index == index && it->version == version && it->config == config) { return it; } } return s.end(); } static void printit(const set& s, const set::const_iterator& it) { if (it == s.end()) { printf("(none)\n"); } else { printf("id=%s index=%d config=%s file=%s value='%s'\n", it->id.c_str(), it->index, it->config.ToString().c_str(), it->file.c_str(), it->value->ToString(ANDROID_NAMESPACES).c_str()); } } StringResource convert_resource(const StringResource& s, const string& file, const Configuration& config, int version, const string& versionString) { return StringResource(s.pos, file, config, s.id, s.index, s.value ? s.value->Clone() : NULL, version, versionString, s.comment); } static bool resource_has_contents(const StringResource& res) { XMLNode* value = res.value; if (value == NULL) { return false; } string contents = value->ContentsToString(ANDROID_NAMESPACES); return contents != ""; } ValuesFile* merge_res_and_xliff(const ValuesFile* en_currentFile, const ValuesFile* xx_currentFile, const ValuesFile* xx_oldFile, const string& filename, const XLIFFFile* xliffFile) { bool success = true; Configuration en_config = xliffFile->SourceConfig(); Configuration xx_config = xliffFile->TargetConfig(); string currentVersion = xliffFile->CurrentVersion(); ValuesFile* result = new ValuesFile(xx_config); set en_cur = en_currentFile->GetStrings(); set xx_cur = xx_currentFile->GetStrings(); set xx_old = xx_oldFile->GetStrings(); set xliff = xliffFile->GetStringResources(); // for each string in en_current for (set::const_iterator en_c = en_cur.begin(); en_c != en_cur.end(); en_c++) { set::const_iterator xx_c = find_id(xx_cur, en_c->id, en_c->index); set::const_iterator xx_o = find_id(xx_old, en_c->id, en_c->index); set::const_iterator xlf = find_in_xliff(xliff, en_c->file, en_c->id, en_c->index, CURRENT_VERSION, xx_config); if (false) { printf("\nen_c: "); printit(en_cur, en_c); printf("xx_c: "); printit(xx_cur, xx_c); printf("xx_o: "); printit(xx_old, xx_o); printf("xlf: "); printit(xliff, xlf); } // if it changed between xx_old and xx_current, use xx_current // (someone changed it by hand) if (xx_o != xx_old.end() && xx_c != xx_cur.end()) { string xx_o_value = xx_o->value->ToString(ANDROID_NAMESPACES); string xx_c_value = xx_c->value->ToString(ANDROID_NAMESPACES); if (xx_o_value != xx_c_value && xx_c_value != "") { StringResource r(convert_resource(*xx_c, filename, xx_config, CURRENT_VERSION, currentVersion)); if (resource_has_contents(r)) { result->AddString(r); } continue; } } // if it is present in xliff, use that // (it just got translated) if (xlf != xliff.end() && xlf->value->ToString(ANDROID_NAMESPACES) != "") { StringResource r(convert_resource(*xlf, filename, xx_config, CURRENT_VERSION, currentVersion)); if (resource_has_contents(r)) { result->AddString(r); } } // if it is present in xx_current, use that // (it was already translated, and not retranslated) // don't filter out empty strings if they were added by hand, the above code just // guarantees that this tool never adds an empty one. if (xx_c != xx_cur.end()) { StringResource r(convert_resource(*xx_c, filename, xx_config, CURRENT_VERSION, currentVersion)); result->AddString(r); } // othwerwise, leave it out. The resource fall-through code will use the English // one at runtime, and the xliff export code will pick it up for translation next time. } if (success) { return result; } else { delete result; return NULL; } } struct MergedFile { XLIFFFile* xliff; string xliffFilename; string original; string translated; ValuesFile* en_current; ValuesFile* xx_current; ValuesFile* xx_old; ValuesFile* xx_new; string xx_new_text; string xx_new_filename; bool new_file; bool deleted_file; MergedFile(); MergedFile(const MergedFile&); }; struct compare_filenames { bool operator()(const MergedFile& lhs, const MergedFile& rhs) const { return lhs.original < rhs.original; } }; MergedFile::MergedFile() :xliff(NULL), xliffFilename(), original(), translated(), en_current(NULL), xx_current(NULL), xx_old(NULL), xx_new(NULL), xx_new_text(), xx_new_filename(), new_file(false), deleted_file(false) { } MergedFile::MergedFile(const MergedFile& that) :xliff(that.xliff), xliffFilename(that.xliffFilename), original(that.original), translated(that.translated), en_current(that.en_current), xx_current(that.xx_current), xx_old(that.xx_old), xx_new(that.xx_new), xx_new_text(that.xx_new_text), xx_new_filename(that.xx_new_filename), new_file(that.new_file), deleted_file(that.deleted_file) { } typedef set MergedFileSet; int do_merge(const vector& xliffFilenames) { int err = 0; MergedFileSet files; printf("\rPreparing..."); fflush(stdout); string currentChange = Perforce::GetCurrentChange(true); // for each xliff, make a MergedFile record and do a little error checking for (vector::const_iterator xliffFilename=xliffFilenames.begin(); xliffFilename!=xliffFilenames.end(); xliffFilename++) { XLIFFFile* xliff = XLIFFFile::Parse(*xliffFilename); if (xliff == NULL) { fprintf(stderr, "localize import: unable to read file %s\n", xliffFilename->c_str()); err = 1; continue; } set xf = xliff->Files(); for (set::const_iterator f=xf.begin(); f!=xf.end(); f++) { MergedFile mf; mf.xliff = xliff; mf.xliffFilename = *xliffFilename; mf.original = *f; mf.translated = translated_file_name(mf.original, xliff->TargetConfig().locale); log_printf("mf.translated=%s mf.original=%s locale=%s\n", mf.translated.c_str(), mf.original.c_str(), xliff->TargetConfig().locale.c_str()); if (files.find(mf) != files.end()) { fprintf(stderr, "%s: duplicate string resources for file %s\n", xliffFilename->c_str(), f->c_str()); fprintf(stderr, "%s: previously defined here.\n", files.find(mf)->xliffFilename.c_str()); err = 1; continue; } files.insert(mf); } } size_t deletedFileCount = 0; size_t J = files.size() * 3; size_t j = 1; // Read all of the files from perforce. for (MergedFileSet::iterator mf = files.begin(); mf != files.end(); mf++) { MergedFile* file = const_cast(&(*mf)); // file->en_current print_file_status(j++, J); file->en_current = get_values_file(file->original, file->xliff->SourceConfig(), CURRENT_VERSION, currentChange, true); if (file->en_current == NULL) { // deleted file file->deleted_file = true; deletedFileCount++; continue; } // file->xx_current; print_file_status(j++, J); file->xx_current = get_values_file(file->translated, file->xliff->TargetConfig(), CURRENT_VERSION, currentChange, false); if (file->xx_current == NULL) { file->xx_current = new ValuesFile(file->xliff->TargetConfig()); file->new_file = true; } // file->xx_old (note that the xliff's current version is our old version, because that // was the current version when it was exported) print_file_status(j++, J); file->xx_old = get_values_file(file->translated, file->xliff->TargetConfig(), OLD_VERSION, file->xliff->CurrentVersion(), false); if (file->xx_old == NULL) { file->xx_old = new ValuesFile(file->xliff->TargetConfig()); file->new_file = true; } } // merge them for (MergedFileSet::iterator mf = files.begin(); mf != files.end(); mf++) { MergedFile* file = const_cast(&(*mf)); if (file->deleted_file) { continue; } file->xx_new = merge_res_and_xliff(file->en_current, file->xx_current, file->xx_old, file->original, file->xliff); } // now is a good time to stop if there was an error if (err != 0) { return err; } // locate the files j = 1; for (MergedFileSet::iterator mf = files.begin(); mf != files.end(); mf++) { MergedFile* file = const_cast(&(*mf)); print_file_status(j++, J, "Locating"); file->xx_new_filename = Perforce::Where(file->translated, true); if (file->xx_new_filename == "") { fprintf(stderr, "\nWas not able to determine the location of depot file %s\n", file->translated.c_str()); err = 1; } } if (err != 0) { return err; } // p4 edit the files // only do this if it changed - no need to submit files that haven't changed meaningfully vector filesToEdit; vector filesToAdd; vector filesToDelete; for (MergedFileSet::iterator mf = files.begin(); mf != files.end(); mf++) { MergedFile* file = const_cast(&(*mf)); if (file->deleted_file) { filesToDelete.push_back(file->xx_new_filename); continue; } string xx_current_text = file->xx_current->ToString(); string xx_new_text = file->xx_new->ToString(); if (xx_current_text != xx_new_text) { if (file->xx_new->GetStrings().size() == 0) { file->deleted_file = true; filesToDelete.push_back(file->xx_new_filename); } else { file->xx_new_text = xx_new_text; if (file->new_file) { filesToAdd.push_back(file->xx_new_filename); } else { filesToEdit.push_back(file->xx_new_filename); } } } } if (filesToAdd.size() == 0 && filesToEdit.size() == 0 && deletedFileCount == 0) { printf("\nAll of the files are the same. Nothing to change.\n"); return 0; } if (filesToEdit.size() > 0) { printf("\np4 editing files...\n"); if (0 != Perforce::EditFiles(filesToEdit, true)) { return 1; } } printf("\n"); for (MergedFileSet::iterator mf = files.begin(); mf != files.end(); mf++) { MergedFile* file = const_cast(&(*mf)); if (file->deleted_file) { continue; } if (file->xx_new_text != "" && file->xx_new_filename != "") { if (0 != write_to_file(file->xx_new_filename, file->xx_new_text)) { err = 1; } } } if (err != 0) { return err; } if (filesToAdd.size() > 0) { printf("p4 adding %zd new files...\n", filesToAdd.size()); err = Perforce::AddFiles(filesToAdd, true); } if (filesToDelete.size() > 0) { printf("p4 deleting %zd removed files...\n", filesToDelete.size()); err = Perforce::DeleteFiles(filesToDelete, true); } if (err != 0) { return err; } printf("\n" "Theoretically, this merge was successfull. Next you should\n" "review the diffs, get a code review, and submit it. Enjoy.\n\n"); return 0; }