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 |