PageRenderTime 46ms CodeModel.GetById 33ms app.highlight 11ms RepoModel.GetById 0ms app.codeStats 0ms

/tools/grit/grit/node/message.py

https://github.com/akesling/chromium
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