Home | History | Annotate | Download | only in localize
      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 
      8 static set<StringResource>::const_iterator
      9 find_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 
     19 static set<StringResource>::const_iterator
     20 find_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 
     33 static void
     34 printit(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 
     45 StringResource
     46 convert_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 
     53 static bool
     54 resource_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 
     64 ValuesFile*
     65 merge_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 
    145 struct 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 
    163 struct compare_filenames {
    164     bool operator()(const MergedFile& lhs, const MergedFile& rhs) const
    165     {
    166         return lhs.original < rhs.original;
    167     }
    168 };
    169 
    170 MergedFile::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 
    186 MergedFile::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 
    203 typedef set<MergedFile, compare_filenames> MergedFileSet;
    204 
    205 int
    206 do_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