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 |