blob: 1fdaa0e7df53e4e87df88f5b67d2cfff0ff800aa [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001#include "merge_res_and_xliff.h"
2
3#include "file_utils.h"
4#include "Perforce.h"
5#include "log.h"
Jack Palevichbdb087c2009-06-24 19:01:27 -07006#include <stdio.h>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08007
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