| 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 '''Handling of the <message> element. | |
| 7 ''' | |
| 8 | |
| 9 import re | |
| 10 import types | |
| 11 | |
| 12 from grit.node import base | |
| 13 | |
| 14 import grit.format.rc_header | |
| 15 import grit.format.rc | |
| 16 | |
| 17 from grit import clique | |
| 18 from grit import exception | |
| 19 from grit import lazy_re | |
| 20 from grit import tclib | |
| 21 from grit import util | |
| 22 | |
| 23 # Finds whitespace at the start and end of a string which can be multiline. | |
| 24 _WHITESPACE = lazy_re.compile('(?P<start>\s*)(?P<body>.+?)(?P<end>\s*)\Z', | |
| 25 re.DOTALL | re.MULTILINE) | |
| 26 | |
| 27 | |
| 28 class MessageNode(base.ContentNode): | |
| 29 '''A <message> element.''' | |
| 30 | |
| 31 # For splitting a list of things that can be separated by commas or | |
| 32 # whitespace | |
| 33 _SPLIT_RE = lazy_re.compile('\s*,\s*|\s+') | |
| 34 | |
| 35 def __init__(self): | |
| 36 super(MessageNode, self).__init__() | |
| 37 # Valid after EndParsing, this is the MessageClique that contains the | |
| 38 # source message and any translations of it that have been loaded. | |
| 39 self.clique = None | |
| 40 | |
| 41 # We don't send leading and trailing whitespace into the translation | |
| 42 # console, but rather tack it onto the source message and any | |
| 43 # translations when formatting them into RC files or what have you. | |
| 44 self.ws_at_start = '' # Any whitespace characters at the start of the text | |
| 45 self.ws_at_end = '' # --"-- at the end of the text | |
| 46 | |
| 47 # A list of "shortcut groups" this message is in. We check to make sure | |
| 48 # that shortcut keys (e.g. &J) within each shortcut group are unique. | |
| 49 self.shortcut_groups_ = [] | |
| 50 | |
| 51 # Formatter-specific data used to control the output of individual strings. | |
| 52 # formatter_data is a space separated list of C preprocessor-style | |
| 53 # definitions. Names without values are given the empty string value. | |
| 54 # Example: "foo=5 bar baz=100" | |
| 55 self.formatter_data = {} | |
| 56 | |
| 57 def _IsValidChild(self, child): | |
| 58 return isinstance(child, (PhNode)) | |
| 59 | |
| 60 def _IsValidAttribute(self, name, value): | |
| 61 if name not in ['name', 'offset', 'translateable', 'desc', 'meaning', | |
| 62 'internal_comment', 'shortcut_groups', 'custom_type', | |
| 63 'validation_expr', 'use_name_for_id', 'sub_variable', | |
| 64 'formatter_data']: | |
| 65 return False | |
| 66 if (name in ('translateable', 'sub_variable') and | |
| 67 value not in ['true', 'false']): | |
| 68 return False | |
| 69 return True | |
| 70 | |
| 71 def MandatoryAttributes(self): | |
| 72 return ['name|offset'] | |
| 73 | |
| 74 def DefaultAttributes(self): | |
| 75 return { | |
| 76 'custom_type' : '', | |
| 77 'desc' : '', | |
| 78 'formatter_data' : '', | |
| 79 'internal_comment' : '', | |
| 80 'meaning' : '', | |
| 81 'shortcut_groups' : '', | |
| 82 'sub_variable' : 'false', | |
| 83 'translateable' : 'true', | |
| 84 'use_name_for_id' : 'false', | |
| 85 'validation_expr' : '', | |
| 86 } | |
| 87 | |
| 88 def HandleAttribute(self, attrib, value): | |
| 89 base.ContentNode.HandleAttribute(self, attrib, value) | |
| 90 if attrib == 'formatter_data': | |
| 91 # Parse value, a space-separated list of defines, into a dict. | |
| 92 # Example: "foo=5 bar" -> {'foo':'5', 'bar':''} | |
| 93 for item in value.split(): | |
| 94 name, sep, val = item.partition('=') | |
| 95 self.formatter_data[name] = val | |
| 96 | |
| 97 def GetTextualIds(self): | |
| 98 ''' | |
| 99 Returns the concatenation of the parent's node first_id and | |
| 100 this node's offset if it has one, otherwise just call the | |
| 101 superclass' implementation | |
| 102 ''' | |
| 103 if 'offset' in self.attrs: | |
| 104 # we search for the first grouping node in the parents' list | |
| 105 # to take care of the case where the first parent is an <if> node | |
| 106 grouping_parent = self.parent | |
| 107 import grit.node.empty | |
| 108 while grouping_parent and not isinstance(grouping_parent, | |
| 109 grit.node.empty.GroupingNode): | |
| 110 grouping_parent = grouping_parent.parent | |
| 111 | |
| 112 assert 'first_id' in grouping_parent.attrs | |
| 113 return [grouping_parent.attrs['first_id'] + '_' + self.attrs['offset']] | |
| 114 else: | |
| 115 return super(MessageNode, self).GetTextualIds() | |
| 116 | |
| 117 def IsTranslateable(self): | |
| 118 return self.attrs['translateable'] == 'true' | |
| 119 | |
| 120 def EndParsing(self): | |
| 121 super(MessageNode, self).EndParsing() | |
| 122 | |
| 123 # Make the text (including placeholder references) and list of placeholders, | |
| 124 # then strip and store leading and trailing whitespace and create the | |
| 125 # tclib.Message() and a clique to contain it. | |
| 126 | |
| 127 text = '' | |
| 128 placeholders = [] | |
| 129 for item in self.mixed_content: | |
| 130 if isinstance(item, types.StringTypes): | |
| 131 text += item | |
| 132 else: | |
| 133 presentation = item.attrs['name'].upper() | |
| 134 text += presentation | |
| 135 ex = ' ' | |
| 136 if len(item.children): | |
| 137 ex = item.children[0].GetCdata() | |
| 138 original = item.GetCdata() | |
| 139 placeholders.append(tclib.Placeholder(presentation, original, ex)) | |
| 140 | |
| 141 m = _WHITESPACE.match(text) | |
| 142 if m: | |
| 143 self.ws_at_start = m.group('start') | |
| 144 self.ws_at_end = m.group('end') | |
| 145 text = m.group('body') | |
| 146 | |
| 147 self.shortcut_groups_ = self._SPLIT_RE.split(self.attrs['shortcut_groups']) | |
| 148 self.shortcut_groups_ = [i for i in self.shortcut_groups_ if i != ''] | |
| 149 | |
| 150 description_or_id = self.attrs['desc'] | |
| 151 if description_or_id == '' and 'name' in self.attrs: | |
| 152 description_or_id = 'ID: %s' % self.attrs['name'] | |
| 153 | |
| 154 assigned_id = None | |
| 155 if self.attrs['use_name_for_id'] == 'true': | |
| 156 assigned_id = self.attrs['name'] | |
| 157 message = tclib.Message(text=text, placeholders=placeholders, | |
| 158 description=description_or_id, | |
| 159 meaning=self.attrs['meaning'], | |
| 160 assigned_id=assigned_id) | |
| 161 self.InstallMessage(message) | |
| 162 | |
| 163 def InstallMessage(self, message): | |
| 164 '''Sets this node's clique from a tclib.Message instance. | |
| 165 | |
| 166 Args: | |
| 167 message: A tclib.Message. | |
| 168 ''' | |
| 169 self.clique = self.UberClique().MakeClique(message, self.IsTranslateable()) | |
| 170 for group in self.shortcut_groups_: | |
| 171 self.clique.AddToShortcutGroup(group) | |
| 172 if self.attrs['custom_type'] != '': | |
| 173 self.clique.SetCustomType(util.NewClassInstance(self.attrs['custom_type'], | |
| 174 clique.CustomType)) | |
| 175 elif self.attrs['validation_expr'] != '': | |
| 176 self.clique.SetCustomType( | |
| 177 clique.OneOffCustomType(self.attrs['validation_expr'])) | |
| 178 | |
| 179 def SubstituteMessages(self, substituter): | |
| 180 '''Applies substitution to this message. | |
| 181 | |
| 182 Args: | |
| 183 substituter: a grit.util.Substituter object. | |
| 184 ''' | |
| 185 message = substituter.SubstituteMessage(self.clique.GetMessage()) | |
| 186 if message is not self.clique.GetMessage(): | |
| 187 self.InstallMessage(message) | |
| 188 | |
| 189 def GetCliques(self): | |
| 190 if self.clique: | |
| 191 return [self.clique] | |
| 192 else: | |
| 193 return [] | |
| 194 | |
| 195 def Translate(self, lang): | |
| 196 '''Returns a translated version of this message. | |
| 197 ''' | |
| 198 assert self.clique | |
| 199 msg = self.clique.MessageForLanguage(lang, | |
| 200 self.PseudoIsAllowed(), | |
| 201 self.ShouldFallbackToEnglish() | |
| 202 ).GetRealContent() | |
| 203 return msg.replace('[GRITLANGCODE]', lang) | |
| 204 | |
| 205 def NameOrOffset(self): | |
| 206 if 'name' in self.attrs: | |
| 207 return self.attrs['name'] | |
| 208 else: | |
| 209 return self.attrs['offset'] | |
| 210 | |
| 211 def ExpandVariables(self): | |
| 212 '''We always expand variables on Messages.''' | |
| 213 return True | |
| 214 | |
| 215 def GetDataPackPair(self, lang, encoding): | |
| 216 '''Returns a (id, string) pair that represents the string id and the string | |
| 217 in the specified encoding, where |encoding| is one of the encoding values | |
| 218 accepted by util.Encode. This is used to generate the data pack data file. | |
| 219 ''' | |
| 220 from grit.format import rc_header | |
| 221 id_map = rc_header.GetIds(self.GetRoot()) | |
| 222 id = id_map[self.GetTextualIds()[0]] | |
| 223 | |
| 224 message = self.ws_at_start + self.Translate(lang) + self.ws_at_end | |
| 225 return id, util.Encode(message, encoding) | |
| 226 | |
| 227 def IsResourceMapSource(self): | |
| 228 return True | |
| 229 | |
| 230 def GeneratesResourceMapEntry(self, output_all_resource_defines, | |
| 231 is_active_descendant): | |
| 232 return is_active_descendant | |
| 233 | |
| 234 @staticmethod | |
| 235 def Construct(parent, message, name, desc='', meaning='', translateable=True): | |
| 236 '''Constructs a new message node that is a child of 'parent', with the | |
| 237 name, desc, meaning and translateable attributes set using the same-named | |
| 238 parameters and the text of the message and any placeholders taken from | |
| 239 'message', which must be a tclib.Message() object.''' | |
| 240 # Convert type to appropriate string | |
| 241 translateable = 'true' if translateable else 'false' | |
| 242 | |
| 243 node = MessageNode() | |
| 244 node.StartParsing('message', parent) | |
| 245 node.HandleAttribute('name', name) | |
| 246 node.HandleAttribute('desc', desc) | |
| 247 node.HandleAttribute('meaning', meaning) | |
| 248 node.HandleAttribute('translateable', translateable) | |
| 249 | |
| 250 items = message.GetContent() | |
| 251 for ix, item in enumerate(items): | |
| 252 if isinstance(item, types.StringTypes): | |
| 253 # Ensure whitespace at front and back of message is correctly handled. | |
| 254 if ix == 0: | |
| 255 item = "'''" + item | |
| 256 if ix == len(items) - 1: | |
| 257 item = item + "'''" | |
| 258 | |
| 259 node.AppendContent(item) | |
| 260 else: | |
| 261 phnode = PhNode() | |
| 262 phnode.StartParsing('ph', node) | |
| 263 phnode.HandleAttribute('name', item.GetPresentation()) | |
| 264 phnode.AppendContent(item.GetOriginal()) | |
| 265 | |
| 266 if len(item.GetExample()) and item.GetExample() != ' ': | |
| 267 exnode = ExNode() | |
| 268 exnode.StartParsing('ex', phnode) | |
| 269 exnode.AppendContent(item.GetExample()) | |
| 270 exnode.EndParsing() | |
| 271 phnode.AddChild(exnode) | |
| 272 | |
| 273 phnode.EndParsing() | |
| 274 node.AddChild(phnode) | |
| 275 | |
| 276 node.EndParsing() | |
| 277 return node | |
| 278 | |
| 279 class PhNode(base.ContentNode): | |
| 280 '''A <ph> element.''' | |
| 281 | |
| 282 def _IsValidChild(self, child): | |
| 283 return isinstance(child, ExNode) | |
| 284 | |
| 285 def MandatoryAttributes(self): | |
| 286 return ['name'] | |
| 287 | |
| 288 def EndParsing(self): | |
| 289 super(PhNode, self).EndParsing() | |
| 290 # We only allow a single example for each placeholder | |
| 291 if len(self.children) > 1: | |
| 292 raise exception.TooManyExamples() | |
| 293 | |
| 294 def GetTextualIds(self): | |
| 295 # The 'name' attribute is not an ID. | |
| 296 return [] | |
| 297 | |
| 298 | |
| 299 class ExNode(base.ContentNode): | |
| 300 '''An <ex> element.''' | |
| 301 pass | |
| OLD | NEW |