| Index: grit/node/message.py
 | 
| ===================================================================
 | 
| --- grit/node/message.py	(revision 0)
 | 
| +++ grit/node/message.py	(revision 0)
 | 
| @@ -0,0 +1,282 @@
 | 
| +#!/usr/bin/python2.4
 | 
| +# Copyright (c) 2011 The Chromium Authors. All rights reserved.
 | 
| +# Use of this source code is governed by a BSD-style license that can be
 | 
| +# found in the LICENSE file.
 | 
| +
 | 
| +'''Handling of the <message> element.
 | 
| +'''
 | 
| +
 | 
| +import re
 | 
| +import types
 | 
| +
 | 
| +from grit.node import base
 | 
| +
 | 
| +import grit.format.rc_header
 | 
| +import grit.format.rc
 | 
| +
 | 
| +from grit import clique
 | 
| +from grit import exception
 | 
| +from grit import tclib
 | 
| +from grit import util
 | 
| +
 | 
| +BINARY, UTF8, UTF16 = range(3)
 | 
| +
 | 
| +# Finds whitespace at the start and end of a string which can be multiline.
 | 
| +_WHITESPACE = re.compile('(?P<start>\s*)(?P<body>.+?)(?P<end>\s*)\Z',
 | 
| +                         re.DOTALL | re.MULTILINE)
 | 
| +
 | 
| +
 | 
| +class MessageNode(base.ContentNode):
 | 
| +  '''A <message> element.'''
 | 
| +
 | 
| +  # For splitting a list of things that can be separated by commas or
 | 
| +  # whitespace
 | 
| +  _SPLIT_RE = re.compile('\s*,\s*|\s+')
 | 
| +
 | 
| +  def __init__(self):
 | 
| +    super(type(self), self).__init__()
 | 
| +    # Valid after EndParsing, this is the MessageClique that contains the
 | 
| +    # source message and any translations of it that have been loaded.
 | 
| +    self.clique = None
 | 
| +
 | 
| +    # We don't send leading and trailing whitespace into the translation
 | 
| +    # console, but rather tack it onto the source message and any
 | 
| +    # translations when formatting them into RC files or what have you.
 | 
| +    self.ws_at_start = ''  # Any whitespace characters at the start of the text
 | 
| +    self.ws_at_end = ''  # --"-- at the end of the text
 | 
| +
 | 
| +    # A list of "shortcut groups" this message is in.  We check to make sure
 | 
| +    # that shortcut keys (e.g. &J) within each shortcut group are unique.
 | 
| +    self.shortcut_groups_ = []
 | 
| +
 | 
| +  def _IsValidChild(self, child):
 | 
| +    return isinstance(child, (PhNode))
 | 
| +
 | 
| +  def _IsValidAttribute(self, name, value):
 | 
| +    if name not in ['name', 'offset', 'translateable', 'desc', 'meaning',
 | 
| +                    'internal_comment', 'shortcut_groups', 'custom_type',
 | 
| +                    'validation_expr', 'use_name_for_id']:
 | 
| +      return False
 | 
| +    if name == 'translateable' and value not in ['true', 'false']:
 | 
| +      return False
 | 
| +    return True
 | 
| +
 | 
| +  def MandatoryAttributes(self):
 | 
| +    return ['name|offset']
 | 
| +
 | 
| +  def DefaultAttributes(self):
 | 
| +    return {
 | 
| +      'translateable' : 'true',
 | 
| +      'desc' : '',
 | 
| +      'meaning' : '',
 | 
| +      'internal_comment' : '',
 | 
| +      'shortcut_groups' : '',
 | 
| +      'custom_type' : '',
 | 
| +      'validation_expr' : '',
 | 
| +      'use_name_for_id' : 'false',
 | 
| +    }
 | 
| +
 | 
| +  def GetTextualIds(self):
 | 
| +    '''
 | 
| +    Returns the concatenation of the parent's node first_id and
 | 
| +    this node's offset if it has one, otherwise just call the
 | 
| +    superclass' implementation
 | 
| +    '''
 | 
| +    if 'offset' in self.attrs:
 | 
| +      # we search for the first grouping node in the parents' list
 | 
| +      # to take care of the case where the first parent is an <if> node
 | 
| +      grouping_parent = self.parent
 | 
| +      import grit.node.empty
 | 
| +      while grouping_parent and not isinstance(grouping_parent,
 | 
| +                                               grit.node.empty.GroupingNode):
 | 
| +        grouping_parent = grouping_parent.parent
 | 
| +
 | 
| +      assert 'first_id' in grouping_parent.attrs
 | 
| +      return [grouping_parent.attrs['first_id'] + '_' + self.attrs['offset']]
 | 
| +    else:
 | 
| +      return super(type(self), self).GetTextualIds()
 | 
| +
 | 
| +  def IsTranslateable(self):
 | 
| +    return self.attrs['translateable'] == 'true'
 | 
| +
 | 
| +  def ItemFormatter(self, t):
 | 
| +    # Only generate an output if the if condition is satisfied.
 | 
| +    if not self.SatisfiesOutputCondition():
 | 
| +      return super(type(self), self).ItemFormatter(t)
 | 
| +
 | 
| +    if t == 'rc_header':
 | 
| +      return grit.format.rc_header.Item()
 | 
| +    elif t in ('rc_all', 'rc_translateable', 'rc_nontranslateable'):
 | 
| +      return grit.format.rc.Message()
 | 
| +    elif t == 'js_map_format':
 | 
| +        return grit.format.js_map_format.Message()
 | 
| +    else:
 | 
| +      return super(type(self), self).ItemFormatter(t)
 | 
| +
 | 
| +  def EndParsing(self):
 | 
| +    super(type(self), self).EndParsing()
 | 
| +
 | 
| +    # Make the text (including placeholder references) and list of placeholders,
 | 
| +    # then strip and store leading and trailing whitespace and create the
 | 
| +    # tclib.Message() and a clique to contain it.
 | 
| +
 | 
| +    text = ''
 | 
| +    placeholders = []
 | 
| +    for item in self.mixed_content:
 | 
| +      if isinstance(item, types.StringTypes):
 | 
| +        text += item
 | 
| +      else:
 | 
| +        presentation = item.attrs['name'].upper()
 | 
| +        text += presentation
 | 
| +        ex = ' '
 | 
| +        if len(item.children):
 | 
| +          ex = item.children[0].GetCdata()
 | 
| +        original = item.GetCdata()
 | 
| +        placeholders.append(tclib.Placeholder(presentation, original, ex))
 | 
| +
 | 
| +    m = _WHITESPACE.match(text)
 | 
| +    if m:
 | 
| +      self.ws_at_start = m.group('start')
 | 
| +      self.ws_at_end = m.group('end')
 | 
| +      text = m.group('body')
 | 
| +
 | 
| +    self.shortcut_groups_ = self._SPLIT_RE.split(self.attrs['shortcut_groups'])
 | 
| +    self.shortcut_groups_ = [i for i in self.shortcut_groups_ if i != '']
 | 
| +
 | 
| +    description_or_id = self.attrs['desc']
 | 
| +    if description_or_id == '' and 'name' in self.attrs:
 | 
| +      description_or_id = 'ID: %s' % self.attrs['name']
 | 
| +
 | 
| +    assigned_id = None
 | 
| +    if (self.attrs['use_name_for_id'] == 'true' and
 | 
| +        self.SatisfiesOutputCondition()):
 | 
| +      assigned_id = self.attrs['name']
 | 
| +    message = tclib.Message(text=text, placeholders=placeholders,
 | 
| +                            description=description_or_id,
 | 
| +                            meaning=self.attrs['meaning'],
 | 
| +                            assigned_id=assigned_id)
 | 
| +    self.clique = self.UberClique().MakeClique(message, self.IsTranslateable())
 | 
| +    for group in self.shortcut_groups_:
 | 
| +      self.clique.AddToShortcutGroup(group)
 | 
| +    if self.attrs['custom_type'] != '':
 | 
| +      self.clique.SetCustomType(util.NewClassInstance(self.attrs['custom_type'],
 | 
| +                                                      clique.CustomType))
 | 
| +    elif self.attrs['validation_expr'] != '':
 | 
| +      self.clique.SetCustomType(
 | 
| +        clique.OneOffCustomType(self.attrs['validation_expr']))
 | 
| +
 | 
| +  def GetCliques(self):
 | 
| +    if self.clique:
 | 
| +      return [self.clique]
 | 
| +    else:
 | 
| +      return []
 | 
| +
 | 
| +  def Translate(self, lang):
 | 
| +    '''Returns a translated version of this message.
 | 
| +    '''
 | 
| +    assert self.clique
 | 
| +    msg = self.clique.MessageForLanguage(lang,
 | 
| +                                         self.PseudoIsAllowed(),
 | 
| +                                         self.ShouldFallbackToEnglish()
 | 
| +                                         ).GetRealContent()
 | 
| +    return msg.replace('[GRITLANGCODE]', lang)
 | 
| +
 | 
| +  def NameOrOffset(self):
 | 
| +    if 'name' in self.attrs:
 | 
| +      return self.attrs['name']
 | 
| +    else:
 | 
| +      return self.attrs['offset']
 | 
| +
 | 
| +  def GetDataPackPair(self, lang, encoding):
 | 
| +    '''Returns a (id, string) pair that represents the string id and the string
 | 
| +    in utf8.  This is used to generate the data pack data file.
 | 
| +    '''
 | 
| +    from grit.format import rc_header
 | 
| +    id_map = rc_header.Item.tids_
 | 
| +    id = id_map[self.GetTextualIds()[0]]
 | 
| +
 | 
| +    message = self.ws_at_start + self.Translate(lang) + self.ws_at_end
 | 
| +    if "\\n" in message:
 | 
| +      # Windows automatically translates \n to a new line, but GTK+ doesn't.
 | 
| +      # Manually do the conversion here rather than at run time.
 | 
| +      message = message.replace("\\n", "\n")
 | 
| +    # |message| is a python unicode string, so convert to a byte stream that
 | 
| +    # has the correct encoding requested for the datapacks. We skip the first
 | 
| +    # 2 bytes of text resources because it is the BOM.
 | 
| +    if encoding == UTF8:
 | 
| +      return id, message.encode('utf8')
 | 
| +    if encoding == UTF16:
 | 
| +      return id, message.encode('utf16')[2:]
 | 
| +    # Default is BINARY
 | 
| +    return id, message
 | 
| +
 | 
| +  # static method
 | 
| +  def Construct(parent, message, name, desc='', meaning='', translateable=True):
 | 
| +    '''Constructs a new message node that is a child of 'parent', with the
 | 
| +    name, desc, meaning and translateable attributes set using the same-named
 | 
| +    parameters and the text of the message and any placeholders taken from
 | 
| +    'message', which must be a tclib.Message() object.'''
 | 
| +    # Convert type to appropriate string
 | 
| +    if translateable:
 | 
| +      translateable = 'true'
 | 
| +    else:
 | 
| +      translateable = 'false'
 | 
| +
 | 
| +    node = MessageNode()
 | 
| +    node.StartParsing('message', parent)
 | 
| +    node.HandleAttribute('name', name)
 | 
| +    node.HandleAttribute('desc', desc)
 | 
| +    node.HandleAttribute('meaning', meaning)
 | 
| +    node.HandleAttribute('translateable', translateable)
 | 
| +
 | 
| +    items = message.GetContent()
 | 
| +    for ix in range(len(items)):
 | 
| +      if isinstance(items[ix], types.StringTypes):
 | 
| +        text = items[ix]
 | 
| +
 | 
| +        # Ensure whitespace at front and back of message is correctly handled.
 | 
| +        if ix == 0:
 | 
| +          text = "'''" + text
 | 
| +        if ix == len(items) - 1:
 | 
| +          text = text + "'''"
 | 
| +
 | 
| +        node.AppendContent(text)
 | 
| +      else:
 | 
| +        phnode = PhNode()
 | 
| +        phnode.StartParsing('ph', node)
 | 
| +        phnode.HandleAttribute('name', items[ix].GetPresentation())
 | 
| +        phnode.AppendContent(items[ix].GetOriginal())
 | 
| +
 | 
| +        if len(items[ix].GetExample()) and items[ix].GetExample() != ' ':
 | 
| +          exnode = ExNode()
 | 
| +          exnode.StartParsing('ex', phnode)
 | 
| +          exnode.AppendContent(items[ix].GetExample())
 | 
| +          exnode.EndParsing()
 | 
| +          phnode.AddChild(exnode)
 | 
| +
 | 
| +        phnode.EndParsing()
 | 
| +        node.AddChild(phnode)
 | 
| +
 | 
| +    node.EndParsing()
 | 
| +    return node
 | 
| +  Construct = staticmethod(Construct)
 | 
| +
 | 
| +class PhNode(base.ContentNode):
 | 
| +  '''A <ph> element.'''
 | 
| +
 | 
| +  def _IsValidChild(self, child):
 | 
| +    return isinstance(child, ExNode)
 | 
| +
 | 
| +  def MandatoryAttributes(self):
 | 
| +    return ['name']
 | 
| +
 | 
| +  def EndParsing(self):
 | 
| +    super(type(self), self).EndParsing()
 | 
| +    # We only allow a single example for each placeholder
 | 
| +    if len(self.children) > 1:
 | 
| +      raise exception.TooManyExamples()
 | 
| +
 | 
| +
 | 
| +class ExNode(base.ContentNode):
 | 
| +  '''An <ex> element.'''
 | 
| +  pass
 | 
| 
 | 
| Property changes on: grit/node/message.py
 | 
| ___________________________________________________________________
 | 
| Added: svn:eol-style
 | 
|    + LF
 | 
| 
 | 
| 
 |