Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / remoting / tools / verify_resources.py
1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Verifies that GRD resource files define all the strings used by a given
7 set of source files. For file formats where it is not possible to infer which
8 strings represent message identifiers, localized strings should be explicitly
9 annotated with the string "i18n-content", for example:
10
11   LocalizeString(/*i18n-content*/"PRODUCT_NAME");
12
13 This script also recognises localized strings in HTML and manifest.json files:
14
15   HTML:          i18n-content="PRODUCT_NAME"
16               or i18n-value-name-1="BUTTON_NAME"
17               or i18n-title="TOOLTIP_NAME"
18   manifest.json: __MSG_PRODUCT_NAME__
19
20 Note that these forms must be exact; extra spaces are not permitted, though
21 either single or double quotes are recognized.
22
23 In addition, the script checks that all the messages are still in use; if
24 this is not the case then a warning is issued, but the script still succeeds.
25 """
26
27 import json
28 import os
29 import optparse
30 import re
31 import sys
32 import xml.dom.minidom as minidom
33
34 WARNING_MESSAGE = """
35 To remove this warning, either remove the unused tags from
36 resource files, add the files that use the tags listed above to
37 remoting.gyp, or annotate existing uses of those tags with the
38 prefix /*i18n-content*/
39 """
40
41 def LoadTagsFromGrd(filename):
42   xml = minidom.parse(filename)
43   android_tags = []
44   other_tags = []
45   msgs_and_structs = xml.getElementsByTagName("message")
46   msgs_and_structs.extend(xml.getElementsByTagName("structure"))
47   for res in msgs_and_structs:
48     name = res.getAttribute("name")
49     if not name or not name.startswith("IDS_"):
50       raise Exception("Tag name doesn't start with IDS_: %s" % name)
51     name = name[4:]
52     if 'android_java' in res.getAttribute('formatter_data'):
53       android_tags.append(name)
54     else:
55       other_tags.append(name)
56   return android_tags, other_tags
57
58
59 def ExtractTagFromLine(file_type, line):
60   """Extract a tag from a line of HTML, C++, JS or JSON."""
61   if file_type == "html":
62     # HTML-style (tags)
63     m = re.search('i18n-content=[\'"]([^\'"]*)[\'"]', line)
64     if m: return m.group(1)
65     # HTML-style (titles)
66     m = re.search('i18n-title=[\'"]([^\'"]*)[\'"]', line)
67     if m: return m.group(1)
68     # HTML-style (substitutions)
69     m = re.search('i18n-value-name-[1-9]=[\'"]([^\'"]*)[\'"]', line)
70     if m: return m.group(1)
71   elif file_type == 'js':
72     # Javascript style
73     m = re.search('/\*i18n-content\*/[\'"]([^\`"]*)[\'"]', line)
74     if m: return m.group(1)
75   elif file_type == 'cc' or file_type == 'mm':
76     # C++ style
77     m = re.search('IDS_([A-Z0-9_]*)', line)
78     if m: return m.group(1)
79     m = re.search('/\*i18n-content\*/["]([^\`"]*)["]', line)
80     if m: return m.group(1)
81   elif file_type == 'json.jinja2':
82     # Manifest style
83     m = re.search('__MSG_(.*)__', line)
84     if m: return m.group(1)
85   elif file_type == 'jinja2':
86     # Jinja2 template file
87     m = re.search('\{\%\s+trans\s+\%\}([A-Z0-9_]+)\{\%\s+endtrans\s+\%\}', line)
88     if m: return m.group(1)
89   return None
90
91
92 def VerifyFile(filename, messages, used_tags):
93   """
94   Parse |filename|, looking for tags and report any that are not included in
95   |messages|. Return True if all tags are present and correct, or False if
96   any are missing.
97   """
98
99   base_name, file_type = os.path.splitext(filename)
100   file_type = file_type[1:]
101   if file_type == 'jinja2' and base_name.endswith('.json'):
102     file_type = 'json.jinja2'
103   if file_type not in ['js', 'cc', 'html', 'json.jinja2', 'jinja2', 'mm']:
104     raise Exception("Unknown file type: %s" % file_type)
105
106   result = True
107   matches = False
108   f = open(filename, 'r')
109   lines = f.readlines()
110   for i in xrange(0, len(lines)):
111     tag = ExtractTagFromLine(file_type, lines[i])
112     if tag:
113       tag = tag.upper()
114       used_tags.add(tag)
115       matches = True
116       if not tag in messages:
117         result = False
118         print '%s/%s:%d: error: Undefined tag: %s' % \
119             (os.getcwd(), filename, i + 1, tag)
120   f.close()
121   return result
122
123
124 def main():
125   parser = optparse.OptionParser(
126       usage='Usage: %prog [options...] [source_file...]')
127   parser.add_option('-t', '--touch', dest='touch',
128                     help='File to touch when finished.')
129   parser.add_option('-r', '--grd', dest='grd', action='append',
130                     help='grd file')
131
132   options, args = parser.parse_args()
133   if not options.touch:
134     print '-t is not specified.'
135     return 1
136   if len(options.grd) == 0 or len(args) == 0:
137     print 'At least one GRD file needs to be specified.'
138     return 1
139
140   all_resources = []
141   non_android_resources = []
142   for f in options.grd:
143     android_tags, other_tags = LoadTagsFromGrd(f)
144     all_resources.extend(android_tags + other_tags)
145     non_android_resources.extend(other_tags)
146
147   used_tags = set([])
148   exit_code = 0
149   for f in args:
150     if not VerifyFile(f, all_resources, used_tags):
151       exit_code = 1
152
153   # Determining if a resource is being used in the Android app is tricky
154   # because it requires annotating and parsing Android XML layout files.
155   # For now, exclude Android strings from this check.
156   warnings = False
157   for tag in non_android_resources:
158     if tag not in used_tags:
159       print ('%s/%s:0: warning: %s is defined but not used') % \
160           (os.getcwd(), sys.argv[2], tag)
161       warnings = True
162   if warnings:
163     print WARNING_MESSAGE
164
165   if exit_code == 0:
166     f = open(options.touch, 'a')
167     f.close()
168     os.utime(options.touch, None)
169
170   return exit_code
171
172
173 if __name__ == '__main__':
174   sys.exit(main())