• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
find_id(const set<StringResource> & s,const string & id,int index)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
find_in_xliff(const set<StringResource> & s,const string & filename,const string & id,int index,int version,const Configuration & config)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
printit(const set<StringResource> & s,const set<StringResource>::const_iterator & it)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
convert_resource(const StringResource & s,const string & file,const Configuration & config,int version,const string & versionString)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
resource_has_contents(const StringResource & res)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*
merge_res_and_xliff(const ValuesFile * en_currentFile,const ValuesFile * xx_currentFile,const ValuesFile * xx_oldFile,const string & filename,const XLIFFFile * xliffFile)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 {
operator ()compare_filenames164     bool operator()(const MergedFile& lhs, const MergedFile& rhs) const
165     {
166         return lhs.original < rhs.original;
167     }
168 };
169 
MergedFile()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 
MergedFile(const MergedFile & that)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
do_merge(const vector<string> & xliffFilenames)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