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