/tools/grit/grit/node/message.py
Python | 267 lines | 226 code | 19 blank | 22 comment | 6 complexity | ecf6145e42eb9818a851e9322008968b MD5 | raw file
1#!/usr/bin/python2.4 2# Copyright (c) 2006-2008 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 9import re 10import types 11 12from grit.node import base 13 14import grit.format.rc_header 15import grit.format.rc 16 17from grit import clique 18from grit import exception 19from grit import tclib 20from grit import util 21 22 23# Finds whitespace at the start and end of a string which can be multiline. 24_WHITESPACE = re.compile('(?P<start>\s*)(?P<body>.+?)(?P<end>\s*)\Z', 25 re.DOTALL | re.MULTILINE) 26 27 28class 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 = re.compile('\s*,\s*|\s+') 34 35 def __init__(self): 36 super(type(self), 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 def _IsValidChild(self, child): 52 return isinstance(child, (PhNode)) 53 54 def _IsValidAttribute(self, name, value): 55 if name not in ['name', 'offset', 'translateable', 'desc', 'meaning', 56 'internal_comment', 'shortcut_groups', 'custom_type', 57 'validation_expr', 'use_name_for_id']: 58 return False 59 if name == 'translateable' and value not in ['true', 'false']: 60 return False 61 return True 62 63 def MandatoryAttributes(self): 64 return ['name|offset'] 65 66 def DefaultAttributes(self): 67 return { 68 'translateable' : 'true', 69 'desc' : '', 70 'meaning' : '', 71 'internal_comment' : '', 72 'shortcut_groups' : '', 73 'custom_type' : '', 74 'validation_expr' : '', 75 'use_name_for_id' : 'false', 76 } 77 78 def GetTextualIds(self): 79 ''' 80 Returns the concatenation of the parent's node first_id and 81 this node's offset if it has one, otherwise just call the 82 superclass' implementation 83 ''' 84 if 'offset' in self.attrs: 85 # we search for the first grouping node in the parents' list 86 # to take care of the case where the first parent is an <if> node 87 grouping_parent = self.parent 88 import grit.node.empty 89 while grouping_parent and not isinstance(grouping_parent, 90 grit.node.empty.GroupingNode): 91 grouping_parent = grouping_parent.parent 92 93 assert 'first_id' in grouping_parent.attrs 94 return [grouping_parent.attrs['first_id'] + '_' + self.attrs['offset']] 95 else: 96 return super(type(self), self).GetTextualIds() 97 98 def IsTranslateable(self): 99 return self.attrs['translateable'] == 'true' 100 101 def ItemFormatter(self, t): 102 if t == 'rc_header': 103 return grit.format.rc_header.Item() 104 elif (t in ['rc_all', 'rc_translateable', 'rc_nontranslateable'] and 105 self.SatisfiesOutputCondition()): 106 return grit.format.rc.Message() 107 else: 108 return super(type(self), self).ItemFormatter(t) 109 110 def EndParsing(self): 111 super(type(self), self).EndParsing() 112 113 # Make the text (including placeholder references) and list of placeholders, 114 # then strip and store leading and trailing whitespace and create the 115 # tclib.Message() and a clique to contain it. 116 117 text = '' 118 placeholders = [] 119 for item in self.mixed_content: 120 if isinstance(item, types.StringTypes): 121 text += item 122 else: 123 presentation = item.attrs['name'].upper() 124 text += presentation 125 ex = ' ' 126 if len(item.children): 127 ex = item.children[0].GetCdata() 128 original = item.GetCdata() 129 placeholders.append(tclib.Placeholder(presentation, original, ex)) 130 131 m = _WHITESPACE.match(text) 132 if m: 133 self.ws_at_start = m.group('start') 134 self.ws_at_end = m.group('end') 135 text = m.group('body') 136 137 self.shortcut_groups_ = self._SPLIT_RE.split(self.attrs['shortcut_groups']) 138 self.shortcut_groups_ = [i for i in self.shortcut_groups_ if i != ''] 139 140 description_or_id = self.attrs['desc'] 141 if description_or_id == '' and 'name' in self.attrs: 142 description_or_id = 'ID: %s' % self.attrs['name'] 143 144 assigned_id = None 145 if self.attrs['use_name_for_id'] == 'true': 146 assigned_id = self.attrs['name'] 147 message = tclib.Message(text=text, placeholders=placeholders, 148 description=description_or_id, 149 meaning=self.attrs['meaning'], 150 assigned_id=assigned_id) 151 self.clique = self.UberClique().MakeClique(message, self.IsTranslateable()) 152 for group in self.shortcut_groups_: 153 self.clique.AddToShortcutGroup(group) 154 if self.attrs['custom_type'] != '': 155 self.clique.SetCustomType(util.NewClassInstance(self.attrs['custom_type'], 156 clique.CustomType)) 157 elif self.attrs['validation_expr'] != '': 158 self.clique.SetCustomType( 159 clique.OneOffCustomType(self.attrs['validation_expr'])) 160 161 def GetCliques(self): 162 if self.clique: 163 return [self.clique] 164 else: 165 return [] 166 167 def Translate(self, lang): 168 '''Returns a translated version of this message. 169 ''' 170 assert self.clique 171 msg = self.clique.MessageForLanguage(lang, 172 self.PseudoIsAllowed(), 173 self.ShouldFallbackToEnglish() 174 ).GetRealContent() 175 return msg.replace('[GRITLANGCODE]', lang) 176 177 def NameOrOffset(self): 178 if 'name' in self.attrs: 179 return self.attrs['name'] 180 else: 181 return self.attrs['offset'] 182 183 def GetDataPackPair(self, output_dir, lang): 184 '''Returns a (id, string) pair that represents the string id and the string 185 in utf8. This is used to generate the data pack data file. 186 ''' 187 from grit.format import rc_header 188 id_map = rc_header.Item.tids_ 189 id = id_map[self.GetTextualIds()[0]] 190 191 message = self.ws_at_start + self.Translate(lang) + self.ws_at_end 192 # |message| is a python unicode string, so convert to a utf16 byte stream 193 # because that's the format of datapacks. We skip the first 2 bytes 194 # because it is the BOM. 195 return id, message.encode('utf16')[2:] 196 197 # static method 198 def Construct(parent, message, name, desc='', meaning='', translateable=True): 199 '''Constructs a new message node that is a child of 'parent', with the 200 name, desc, meaning and translateable attributes set using the same-named 201 parameters and the text of the message and any placeholders taken from 202 'message', which must be a tclib.Message() object.''' 203 # Convert type to appropriate string 204 if translateable: 205 translateable = 'true' 206 else: 207 translateable = 'false' 208 209 node = MessageNode() 210 node.StartParsing('message', parent) 211 node.HandleAttribute('name', name) 212 node.HandleAttribute('desc', desc) 213 node.HandleAttribute('meaning', meaning) 214 node.HandleAttribute('translateable', translateable) 215 216 items = message.GetContent() 217 for ix in range(len(items)): 218 if isinstance(items[ix], types.StringTypes): 219 text = items[ix] 220 221 # Ensure whitespace at front and back of message is correctly handled. 222 if ix == 0: 223 text = "'''" + text 224 if ix == len(items) - 1: 225 text = text + "'''" 226 227 node.AppendContent(text) 228 else: 229 phnode = PhNode() 230 phnode.StartParsing('ph', node) 231 phnode.HandleAttribute('name', items[ix].GetPresentation()) 232 phnode.AppendContent(items[ix].GetOriginal()) 233 234 if len(items[ix].GetExample()) and items[ix].GetExample() != ' ': 235 exnode = ExNode() 236 exnode.StartParsing('ex', phnode) 237 exnode.AppendContent(items[ix].GetExample()) 238 exnode.EndParsing() 239 phnode.AddChild(exnode) 240 241 phnode.EndParsing() 242 node.AddChild(phnode) 243 244 node.EndParsing() 245 return node 246 Construct = staticmethod(Construct) 247 248class PhNode(base.ContentNode): 249 '''A <ph> element.''' 250 251 def _IsValidChild(self, child): 252 return isinstance(child, ExNode) 253 254 def MandatoryAttributes(self): 255 return ['name'] 256 257 def EndParsing(self): 258 super(type(self), self).EndParsing() 259 # We only allow a single example for each placeholder 260 if len(self.children) > 1: 261 raise exception.TooManyExamples() 262 263 264class ExNode(base.ContentNode): 265 '''An <ex> element.''' 266 pass 267