OLD | NEW |
| (Empty) |
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 '''Support for "policy_templates.json" format used by the policy template | |
7 generator as a source for generating ADM,ADMX,etc files.''' | |
8 | |
9 import types | |
10 import sys | |
11 | |
12 from grit.gather import skeleton_gatherer | |
13 from grit import util | |
14 from grit import tclib | |
15 from xml.dom import minidom | |
16 from xml.parsers.expat import ExpatError | |
17 | |
18 | |
19 class PolicyJson(skeleton_gatherer.SkeletonGatherer): | |
20 '''Collects and translates the following strings from policy_templates.json: | |
21 - captions,descriptions and labels of policies | |
22 - captions of enumeration items | |
23 - misc strings from the 'messages' section | |
24 Translatable strings may have untranslateable placeholders with the same | |
25 format that is used in .grd files. | |
26 ''' | |
27 | |
28 def _ParsePlaceholder(self, placeholder, msg): | |
29 '''Extracts a placeholder from a DOM node and adds it to a tclib Message. | |
30 | |
31 Args: | |
32 placeholder: A DOM node of the form: | |
33 <ph name="PLACEHOLDER_NAME">Placeholder text<ex>Example value</ex></ph> | |
34 msg: The placeholder is added to this message. | |
35 ''' | |
36 text = [] | |
37 example_text = [] | |
38 for node1 in placeholder.childNodes: | |
39 if (node1.nodeType == minidom.Node.TEXT_NODE): | |
40 text.append(node1.data) | |
41 elif (node1.nodeType == minidom.Node.ELEMENT_NODE and | |
42 node1.tagName == 'ex'): | |
43 for node2 in node1.childNodes: | |
44 example_text.append(node2.toxml()) | |
45 else: | |
46 raise Exception('Unexpected element inside a placeholder: ' + | |
47 node2.toxml()) | |
48 if example_text == []: | |
49 # In such cases the original text is okay for an example. | |
50 example_text = text | |
51 msg.AppendPlaceholder(tclib.Placeholder( | |
52 placeholder.attributes['name'].value, | |
53 ''.join(text).strip(), | |
54 ''.join(example_text).strip())) | |
55 | |
56 def _ParseMessage(self, string, desc): | |
57 '''Parses a given string and adds it to the output as a translatable chunk | |
58 with a given description. | |
59 | |
60 Args: | |
61 string: The message string to parse. | |
62 desc: The description of the message (for the translators). | |
63 ''' | |
64 msg = tclib.Message(description=desc) | |
65 xml = '<msg>' + string + '</msg>' | |
66 try: | |
67 node = minidom.parseString(xml).childNodes[0] | |
68 except ExpatError: | |
69 reason = '''Input isn't valid XML (has < & > been escaped?): ''' + string | |
70 raise Exception, reason, sys.exc_info()[2] | |
71 | |
72 for child in node.childNodes: | |
73 if child.nodeType == minidom.Node.TEXT_NODE: | |
74 msg.AppendText(child.data) | |
75 elif child.nodeType == minidom.Node.ELEMENT_NODE: | |
76 if child.tagName == 'ph': | |
77 self._ParsePlaceholder(child, msg) | |
78 else: | |
79 raise Exception("Not implemented.") | |
80 else: | |
81 raise Exception("Not implemented.") | |
82 self.skeleton_.append(self.uberclique.MakeClique(msg)) | |
83 | |
84 def _ParseNode(self, node): | |
85 '''Traverses the subtree of a DOM node, and register a tclib message for | |
86 all the <message> nodes. | |
87 ''' | |
88 att_text = [] | |
89 if node.attributes: | |
90 items = node.attributes.items() | |
91 items.sort() | |
92 for key, value in items: | |
93 att_text.append(' %s=\"%s\"' % (key, value)) | |
94 self._AddNontranslateableChunk("<%s%s>" % | |
95 (node.tagName, ''.join(att_text))) | |
96 if node.tagName == 'message': | |
97 msg = tclib.Message(description=node.attributes['desc']) | |
98 for child in node.childNodes: | |
99 if child.nodeType == minidom.Node.TEXT_NODE: | |
100 if msg == None: | |
101 self._AddNontranslateableChunk(child.data) | |
102 else: | |
103 msg.AppendText(child.data) | |
104 elif child.nodeType == minidom.Node.ELEMENT_NODE: | |
105 if child.tagName == 'ph': | |
106 self._ParsePlaceholder(child, msg) | |
107 else: | |
108 assert False | |
109 self.skeleton_.append(self.uberclique.MakeClique(msg)) | |
110 else: | |
111 for child in node.childNodes: | |
112 if child.nodeType == minidom.Node.TEXT_NODE: | |
113 self._AddNontranslateableChunk(child.data) | |
114 elif node.nodeType == minidom.Node.ELEMENT_NODE: | |
115 self._ParseNode(child) | |
116 | |
117 self._AddNontranslateableChunk("</%s>" % node.tagName) | |
118 | |
119 def _AddIndentedNontranslateableChunk(self, depth, string): | |
120 '''Adds a nontranslateable chunk of text to the internally stored output. | |
121 | |
122 Args: | |
123 depth: The number of double spaces to prepend to the next argument string. | |
124 string: The chunk of text to add. | |
125 ''' | |
126 result = [] | |
127 while depth > 0: | |
128 result.append(' ') | |
129 depth = depth - 1 | |
130 result.append(string) | |
131 self._AddNontranslateableChunk(''.join(result)) | |
132 | |
133 def _GetDescription(self, item, item_type, parent_item, key): | |
134 '''Creates a description for a translatable message. The description gives | |
135 some context for the person who will translate this message. | |
136 | |
137 Args: | |
138 item: A policy or an enumeration item. | |
139 item_type: 'enum_item' | 'policy' | |
140 parent_item: The owner of item. (A policy of type group or enum.) | |
141 key: The name of the key to parse. | |
142 depth: The level of indentation. | |
143 ''' | |
144 key_map = { | |
145 'desc': 'Description', | |
146 'caption': 'Caption', | |
147 'label': 'Label', | |
148 } | |
149 if item_type == 'policy': | |
150 return '%s of the policy named %s' % (key_map[key], item['name']) | |
151 elif item_type == 'enum_item': | |
152 return ('%s of the option named %s in policy %s' % | |
153 (key_map[key], item['name'], parent_item['name'])) | |
154 else: | |
155 raise Exception('Unexpected type %s' % item_type) | |
156 | |
157 def _AddPolicyKey(self, item, item_type, parent_item, key, depth): | |
158 '''Given a policy/enumeration item and a key, adds that key and its value | |
159 into the output. | |
160 E.g.: | |
161 'example_value': 123 | |
162 If key indicates that the value is a translatable string, then it is parsed | |
163 as a translatable string. | |
164 | |
165 Args: | |
166 item: A policy or an enumeration item. | |
167 item_type: 'enum_item' | 'policy' | |
168 parent_item: The owner of item. (A policy of type group or enum.) | |
169 key: The name of the key to parse. | |
170 depth: The level of indentation. | |
171 ''' | |
172 self._AddIndentedNontranslateableChunk(depth, "'%s': " % key) | |
173 if key in ('desc', 'caption', 'label'): | |
174 self._AddNontranslateableChunk("'''") | |
175 self._ParseMessage( | |
176 item[key], | |
177 self._GetDescription(item, item_type, parent_item, key)) | |
178 self._AddNontranslateableChunk("''',\n") | |
179 else: | |
180 str_val = item[key] | |
181 if type(str_val) == types.StringType: | |
182 str_val = "'%s'" % self.Escape(str_val) | |
183 else: | |
184 str_val = str(str_val) | |
185 self._AddNontranslateableChunk(str_val + ',\n') | |
186 | |
187 def _AddItems(self, items, item_type, parent_item, depth): | |
188 '''Parses and adds a list of items from the JSON file. Items can be policies | |
189 or parts of an enum policy. | |
190 | |
191 Args: | |
192 items: Either a list of policies or a list of dictionaries. | |
193 item_type: 'enum_item' | 'policy' | |
194 parent_item: If items contains a list of policies, then this is the policy | |
195 group that owns them. If items contains a list of enumeration items, | |
196 then this is the enum policy that holds them. | |
197 depth: Indicates the depth of our position in the JSON hierarchy. Used to | |
198 add nice line-indent to the output. | |
199 ''' | |
200 for item1 in items: | |
201 self._AddIndentedNontranslateableChunk(depth, "{\n") | |
202 for key in item1.keys(): | |
203 if key == 'items': | |
204 self._AddIndentedNontranslateableChunk(depth + 1, "'items': [\n") | |
205 self._AddItems(item1['items'], 'enum_item', item1, depth + 2) | |
206 self._AddIndentedNontranslateableChunk(depth + 1, "],\n") | |
207 elif key == 'policies': | |
208 self._AddIndentedNontranslateableChunk(depth + 1, "'policies': [\n") | |
209 self._AddItems(item1['policies'], 'policy', item1, depth + 2) | |
210 self._AddIndentedNontranslateableChunk(depth + 1, "],\n") | |
211 else: | |
212 self._AddPolicyKey(item1, item_type, parent_item, key, depth + 1) | |
213 self._AddIndentedNontranslateableChunk(depth, "},\n") | |
214 | |
215 def _AddMessages(self): | |
216 '''Processed and adds the 'messages' section to the output.''' | |
217 self._AddNontranslateableChunk(" 'messages': {\n") | |
218 for name, message in self.data['messages'].iteritems(): | |
219 self._AddNontranslateableChunk(" '%s': {\n" % name) | |
220 self._AddNontranslateableChunk(" 'text': '''") | |
221 self._ParseMessage(message['text'], message['desc']) | |
222 self._AddNontranslateableChunk("'''\n") | |
223 self._AddNontranslateableChunk(" },\n") | |
224 self._AddNontranslateableChunk(" },\n") | |
225 | |
226 # Although we use the RegexpGatherer base class, we do not use the | |
227 # _RegExpParse method of that class to implement Parse(). Instead, we | |
228 # parse using a DOM parser. | |
229 def Parse(self): | |
230 if self.have_parsed_: | |
231 return | |
232 self.have_parsed_ = True | |
233 | |
234 self.text_ = self._LoadInputFile() | |
235 if util.IsExtraVerbose(): | |
236 print self.text_ | |
237 | |
238 self.data = eval(self.text_) | |
239 | |
240 self._AddNontranslateableChunk('{\n') | |
241 self._AddNontranslateableChunk(" 'policy_definitions': [\n") | |
242 self._AddItems(self.data['policy_definitions'], 'policy', None, 2) | |
243 self._AddNontranslateableChunk(" ],\n") | |
244 self._AddMessages() | |
245 self._AddNontranslateableChunk('\n}') | |
246 | |
247 def Escape(self, text): | |
248 # \ -> \\ | |
249 # ' -> \' | |
250 # " -> \" | |
251 return text.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'") | |
OLD | NEW |