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