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 7generator as a source for generating ADM,ADMX,etc files.''' 8 9import types 10import sys 11 12from grit.gather import skeleton_gatherer 13from grit import util 14from grit import tclib 15from xml.dom import minidom 16from xml.parsers.expat import ExpatError 17 18 19class 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("'", "\\'") 252