Merge tag 'upstream/2.9.1+ds' into experimental
[debian/jabref.git] / syncLang.py
1 import sys\r
2 import os\r
3 import re\r
4 \r
5 keyFiles = {}\r
6 \r
7 # Builds a list of all translation keys in the list of lines.\r
8 def indexFile(lines):\r
9     allKeys = []\r
10     for line in lines:\r
11         comment = line.find("#")\r
12         if (comment != 0):\r
13             index = line.find("=")\r
14             while ((index > 0) and (line[index-1]=="\\")):\r
15                 index = line.find("=", index+1)\r
16             if (index > 0):\r
17                 allKeys.append(line[0:index])\r
18     allKeys.sort()\r
19     return allKeys\r
20         \r
21 \r
22 # Finds all keys in the first list that are not present in the second list:\r
23 def findMissingKeys(first, second):\r
24     missing = []\r
25     for key in first:\r
26         if not key in second:\r
27             missing.append(key)\r
28     return missing\r
29 \r
30 \r
31 # Appends all the given keys to the file:\r
32 def appendMissingKeys(filename, keys):\r
33     file = open(filename, "a")\r
34     file.write("\n")\r
35     for key in keys:\r
36         file.write(key+"=\n")\r
37 \r
38 def handleFileSet(mainFile, files, changeFiles):\r
39     f1 = open(mainFile)\r
40     lines = f1.readlines()\r
41     f1.close()\r
42     keys = indexFile(lines)\r
43     keysPresent = []\r
44     for i in range(0, len(files)):\r
45         f2 = open(files[i])\r
46         lines = f2.readlines()\r
47         f2.close()\r
48         keysPresent.append(indexFile(lines))\r
49         missing = findMissingKeys(keys, keysPresent[i])\r
50         \r
51         print "\n\nFile '"+files[i]+"'\n"\r
52         if len(missing) == 0:\r
53             print "----> No missing keys."\r
54         else:\r
55             print "----> Missing keys:"\r
56             for key in missing:\r
57                 print key\r
58 \r
59             if changeFiles == 1:\r
60                 print "Update file?",\r
61                 if raw_input() in ['y', 'Y']:\r
62                     appendMissingKeys(files[i], missing)\r
63             print ""\r
64         \r
65         # See if this file has keys that are not in the main file:\r
66         redundant = findMissingKeys(keysPresent[i], keys)\r
67         if len(redundant) > 0:\r
68             print "----> Possible obsolete keys (not in English language file):"\r
69             for key in redundant:\r
70                 print key\r
71                 \r
72             #if changeFiles == 1:\r
73             #   print "Update file?",\r
74             #   if raw_input() in ['y', 'Y']:\r
75             #       removeRedundantKeys(files[i], redundant)\r
76             print ""\r
77         \r
78 def handleJavaCode(filename, lines, keyList, notTermList):\r
79     #Extract first string parameter from Globals.lang call. E.g., Globals.lang("Default")\r
80     reOnlyString = r'"((\\"|[^"])*)"[^"]*'\r
81     patt = re.compile(r'Globals\s*\.\s*lang\s*\(\s*' + reOnlyString)\r
82     #second pattern as Mr Dlib contribution indirectly uses Global.lang\r
83     patta = re.compile(r'LocalizationSupport.message\(' + reOnlyString)\r
84     pattOnlyString = re.compile(reOnlyString)\r
85 \r
86     #Find multiline Globals lang statements. E.g.:\r
87     #Globals.lang("This is my string" +\r
88     # "with a long text")\r
89     patt2 = re.compile(r'Globals\s*\.\s*lang\s*\(([^)])*$')\r
90 \r
91     pattPlus = re.compile(r'^\s*\+')\r
92 \r
93     eList = list(enumerate(lines.split("\n")))\r
94     i = 0\r
95     while i < len(eList):\r
96         linenum, curline = eList[i]\r
97         \r
98         #Remove Java single line comments\r
99         if curline.find("http://") < 0:\r
100             curline = re.sub("//.*", "", curline)\r
101 \r
102         while (curline != ""):\r
103             result = patt.search(curline)\r
104             if (not result):\r
105                 result = patta.search(curline)\r
106             result2 = patt2.search(curline)\r
107 \r
108             found = ""\r
109 \r
110             if result2 and curline.find('",') < 0:\r
111                 # not terminated\r
112                 # but it could be a multiline string\r
113                 if result:\r
114                     curText = result.group(1)\r
115                     searchForPlus = True\r
116                 else:\r
117                     curText = ""\r
118                     searchForPlus = False\r
119                 origI = i\r
120                 #inspect next line\r
121                 while i+1 < len(eList):\r
122                     linenum2, curline2 = eList[i+1]\r
123                     if (not searchForPlus) or pattPlus.search(curline2):\r
124                         #from now on, we always have to search for a plus\r
125                         searchForPlus = True\r
126 \r
127                         #The current line has been handled here, therefore indicate to handle the next line\r
128                         i = i+1\r
129                         linenum = linenum2\r
130                         curline = curline2\r
131 \r
132                         #Search for the occurence of a string\r
133                         result = pattOnlyString.search(curline2)\r
134                         if result:\r
135                             curText = curText + result.group(1)\r
136                             #check for several strings in this line\r
137                             if curline2.count('\"') > 2:\r
138                                 break\r
139                             #check for several arguments in the line\r
140                             if curline2.find('",') > 0:\r
141                                 break\r
142                             if curline2.endswith(")"):\r
143                                 break\r
144                         else:\r
145                             #plus sign at the beginning found, but no string\r
146                             break\r
147                     else:\r
148                         #no continuation found\r
149                         break\r
150 \r
151                 if origI == i:\r
152                     print "%s:%d: Not terminated: %s"%(filename, linenum+1, curline)\r
153                 else:\r
154                     found = curText\r
155 \r
156             if result or (found != ""):\r
157                 if (found == ""):\r
158                     #not a multiline string, found via the single line matching\r
159                     #full string in one line\r
160                     found = result.group(1)\r
161 \r
162                 found = found.replace(" ", "_")\r
163                 #replace characters that need to be escaped in the language file\r
164                 found = found.replace("=", r"\=").replace(":",r"\:")\r
165                 #replace Java-escaped " to plain "\r
166                 found = found.replace(r'\"','"')\r
167                 #Java-escaped \ to plain \ need not to be converted - they have to be kept\r
168                 #e.g., "\\#" has to be contained as "\\#" in the key\r
169                 #found = found.replace('\\\\','\\')\r
170                 if (found != "") and (found not in keyList):\r
171                     keyList.append(found)\r
172                     keyFiles[found] = (filename, linenum)\r
173                     #print "Adding ", found\r
174                 #else:\r
175                 #   print "Not adding: "+found\r
176                     \r
177             #Prepare a possible second run (multiple Globals.lang on this line)\r
178             if result:\r
179                 lastPos = result.span()[1]\r
180                 #regular expression is greedy. It will match until Globals.lang("\r
181                 #therefore, we have to adjust lastPos\r
182                 lastPos = lastPos - 14\r
183                 if len(curline) <= lastPos:\r
184                     curline = ""\r
185                 else:\r
186                     curline = curline[lastPos:]\r
187             else:\r
188                 #terminate processing of this line, continue to next line\r
189                 curline = ""\r
190                 \r
191         i = i+1\r
192             \r
193 # Find all Java source files in the given directory, and read the lines of each,\r
194 # calling handleJavaCode on the contents:   \r
195 def handleDir(lists, dirname, fnames):\r
196     keyList, notTermList = lists    \r
197     for file in fnames:\r
198         if len(file) > 6 and file[(len(file)-5):len(file)] == ".java":\r
199             fl = open(dirname+os.sep+file)\r
200             lines = fl.read()\r
201             fl.close()\r
202             handleJavaCode(dirname + os.sep + file, lines, keyList, notTermList)\r
203             \r
204 # Go through subdirectories and call handleDir on all diroctories:                      \r
205 def traverseFileTree(dir):\r
206     keyList = []\r
207     notTermList = []\r
208     os.path.walk(dir, handleDir, (keyList, notTermList))\r
209     print "Keys found: "+str(len(keyList))\r
210     return keyList\r
211 \r
212     \r
213 # Searches out all translation calls in the Java source files, and reports which\r
214 # are not present in the given resource file.\r
215 #\r
216 # arg: mainFile: a .properties file with the keys to sync with\r
217 def findNewKeysInJavaCode(mainFile, dir, update):\r
218     keystempo = []\r
219     keyListtempo = []\r
220     f1 = open(mainFile)\r
221     lines = f1.readlines()\r
222     f1.close()\r
223     keys = indexFile(lines)\r
224     keyList = traverseFileTree(dir)\r
225    \r
226     # Open the file again, for appending:\r
227     if update:\r
228         f1 = open(mainFile, "a")\r
229         f1.write("\n")\r
230         \r
231     # Look for keys that are used in the code, but not present in the language file:\r
232     for key in keyList:\r
233         value = key.replace("\\:",":").replace("\\=", "=")\r
234         if key not in keys:\r
235             fileName, lineNum = keyFiles[key]\r
236             print "%s:%i:Missing key: %s"%(fileName, lineNum + 1, value)\r
237             if update:\r
238                 f1.write(key+"="+value+"\n")\r
239     \r
240     # Look for keys in the language file that are not used in the code:\r
241     for key in keys:\r
242         if key not in keyList:\r
243             print "Possible obsolete key: "+key\r
244         \r
245     if update:\r
246         f1.close()\r
247     \r
248     \r
249 def lookForDuplicates(file, displayKeys):\r
250     duplicount = 0\r
251     f1 = open(file)\r
252     lines = f1.readlines()\r
253     f1.close()\r
254     mappings = {}\r
255     emptyVals = 0\r
256     for line in lines:\r
257         comment = line.find("#")\r
258         index = line.find("=")\r
259         if (comment != 0) and (index > 0):\r
260             key = line[0:index]\r
261             value = line[index+1:].strip()\r
262             if key in mappings:\r
263                 mappings[key].append(value)\r
264                 duplicount += 1\r
265                 if displayKeys:\r
266                   print "Duplicate: "+file+": "+key+" =",\r
267                   print mappings[key]\r
268             else:\r
269                 mappings[key] = [value]\r
270                 if value == "":\r
271                     emptyVals = emptyVals + 1\r
272                     if displayKeys:\r
273                       print "Empty value: "+file+": "+key\r
274                 #print "New: "+value\r
275     if duplicount > 0:\r
276         dupstring = str(duplicount)+" duplicates. "\r
277     else:\r
278         dupstring = ""\r
279     if emptyVals > 0:\r
280         emptStr = str(emptyVals)+" empty values. "\r
281     else:\r
282         emptStr = ""\r
283     if duplicount+emptyVals > 0:\r
284         okString = ""\r
285     else:\r
286         okString = "ok"\r
287     print file+": "+dupstring+emptStr+okString\r
288     #print file+": "+str(emptyVals)+" empty values."\r
289         \r
290         \r
291 ############# Main part ###################\r
292 \r
293 if len(sys.argv) == 1:\r
294     print """This program must be run from the "src" directory right below the jabref base directory.\r
295     \r
296 Usage: syncLang.py option   \r
297 Option can be one of the following:\r
298  \r
299     -c: Search the language files for empty and duplicate translations. Display only\r
300         counts for duplicated and empty values in each language file.\r
301 \r
302     -d: Search the language files for empty and duplicate translations. \r
303         For each duplicate set found, a list will be printed showing the various \r
304         translations for the same key. There is currently to option to remove duplicates\r
305         automatically.\r
306         \r
307     -s [-u]: Search the Java source files for language keys. All keys that are found in the source files\r
308         but not in "JabRef_en.properties" are listed. If the -u option is specified, these keys will\r
309         automatically be added to "JabRef_en.properties".\r
310         \r
311         The program will also list "Not terminated" keys. These are keys that are concatenated over \r
312         more than one line, that the program is not (currently) able to resolve.\r
313         \r
314         Finally, the program will list "Possible obsolete keys". These are keys that are present in\r
315         "JabRef_en.properties", but could not be found in the Java source code. Note that the \r
316         "Not terminated" keys will be likely to appear here, since they were not resolved.\r
317         \r
318     -t [-u]: Compare the contents of "JabRef_en.properties" and "Menu_en.properties" against the other \r
319         language files. The program will list for all the other files which keys from the English\r
320         file are missing. Additionally, the program will list keys in the other files which are\r
321         not present in the English file - possible obsolete keys.\r
322         \r
323         If the -u option is specified, all missing keys will automatically be added to the files.\r
324         There is currently no option to remove obsolete keys automatically.\r
325 """\r
326     \r
327 elif (len(sys.argv) >= 2) and (sys.argv[1] == "-s"):\r
328     if (len(sys.argv) >= 3) and (sys.argv[2] == "-u"):\r
329         update = 1\r
330     else:\r
331         update = 0\r
332     findNewKeysInJavaCode("resource/JabRef_en.properties", ".", update)\r
333     \r
334 elif (len(sys.argv) >= 2) and (sys.argv[1] == "-t"):\r
335     if (len(sys.argv) >= 3) and (sys.argv[2] == "-u"):\r
336         changeFiles = 1\r
337     else:\r
338         changeFiles = 0\r
339         \r
340     handleFileSet("resource/JabRef_en.properties", ("resource/JabRef_de.properties",\\r
341         "resource/JabRef_fr.properties", "resource/JabRef_it.properties",\\r
342         "resource/JabRef_ja.properties", "resource/JabRef_pt_BR.properties",\\r
343         "resource/JabRef_nl.properties", "resource/JabRef_da.properties",\\r
344         "resource/JabRef_no.properties", "resource/JabRef_tr.properties",\\r
345         "resource/JabRef_vi.properties", "resource/JabRef_in.properties", \\r
346         "resource/JabRef_zh.properties"), changeFiles)\r
347     handleFileSet("resource/Menu_en.properties", ("resource/Menu_de.properties",\\r
348         "resource/Menu_fr.properties", "resource/Menu_it.properties",\\r
349         "resource/Menu_ja.properties", "resource/Menu_pt_BR.properties",\\r
350         "resource/Menu_nl.properties", "resource/Menu_da.properties",\\r
351         "resource/Menu_es.properties",\\r
352         "resource/Menu_no.properties", "resource/Menu_tr.properties",\\r
353         "resource/Menu_vi.properties", "resource/Menu_in.properties",\\r
354         "resource/Menu_zh.properties"), changeFiles)\r
355         \r
356 elif (len(sys.argv) >= 2) and ((sys.argv[1] == "-d") or (sys.argv[1] == "-c")):\r
357     files = ("resource/JabRef_en.properties", "resource/JabRef_de.properties",\\r
358         "resource/JabRef_fr.properties", "resource/JabRef_it.properties",\\r
359         "resource/JabRef_ja.properties", "resource/JabRef_pt_BR.properties",\\r
360         "resource/JabRef_no.properties", "resource/JabRef_nl.properties",\\r
361         "resource/JabRef_da.properties",\\r
362         "resource/JabRef_tr.properties",\\r
363         "resource/JabRef_vi.properties", "resource/JabRef_in.properties",\\r
364         "resource/JabRef_zh.properties",\\r
365         "resource/Menu_en.properties", "resource/Menu_de.properties",\\r
366         "resource/Menu_fr.properties", "resource/Menu_it.properties",\\r
367         "resource/Menu_ja.properties", "resource/Menu_pt_BR.properties",\\r
368         "resource/Menu_no.properties", "resource/Menu_nl.properties",\\r
369         "resource/Menu_da.properties", "resource/Menu_es.properties", \\r
370         "resource/Menu_tr.properties",\\r
371         "resource/Menu_vi.properties", "resource/Menu_in.properties",\\r
372         "resource/Menu_zh.properties")\r
373     for file in files:\r
374         lookForDuplicates(file, sys.argv[1] == "-d")\r