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