| 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 |