1#!/usr/bin/env python
2# Copyright (c) 2012 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'''A baseclass for simple gatherers that store their gathered resource in a
7list.
8'''
9
10import types
11
12from grit.gather import interface
13from grit import clique
14from grit import tclib
15
16
17class SkeletonGatherer(interface.GathererBase):
18  '''Common functionality of gatherers that parse their input as a skeleton of
19  translatable and nontranslatable chunks.
20  '''
21
22  def __init__(self, *args, **kwargs):
23    super(SkeletonGatherer, self).__init__(*args, **kwargs)
24    # List of parts of the document. Translateable parts are
25    # clique.MessageClique objects, nontranslateable parts are plain strings.
26    # Translated messages are inserted back into the skeleton using the quoting
27    # rules defined by self.Escape()
28    self.skeleton_ = []
29    # A list of the names of IDs that need to be defined for this resource
30    # section to compile correctly.
31    self.ids_ = []
32    # True if Parse() has already been called.
33    self.have_parsed_ = False
34    # True if a translatable chunk has been added
35    self.translatable_chunk_ = False
36    # If not None, all parts of the document will be put into this single
37    # message; otherwise the normal skeleton approach is used.
38    self.single_message_ = None
39    # Number to use for the next placeholder name.  Used only if single_message
40    # is not None
41    self.ph_counter_ = 1
42
43  def GetText(self):
44    '''Returns the original text of the section'''
45    return self.text_
46
47  def Escape(self, text):
48    '''Subclasses can override.  Base impl is identity.
49    '''
50    return text
51
52  def UnEscape(self, text):
53    '''Subclasses can override. Base impl is identity.
54    '''
55    return text
56
57  def GetTextualIds(self):
58    '''Returns the list of textual IDs that need to be defined for this
59    resource section to compile correctly.'''
60    return self.ids_
61
62  def _AddTextualId(self, id):
63    self.ids_.append(id)
64
65  def GetCliques(self):
66    '''Returns the message cliques for each translateable message in the
67    resource section.'''
68    return [x for x in self.skeleton_ if isinstance(x, clique.MessageClique)]
69
70  def Translate(self, lang, pseudo_if_not_available=True,
71                skeleton_gatherer=None, fallback_to_english=False):
72    if len(self.skeleton_) == 0:
73      raise exception.NotReady()
74    if skeleton_gatherer:
75      assert len(skeleton_gatherer.skeleton_) == len(self.skeleton_)
76
77    out = []
78    for ix in range(len(self.skeleton_)):
79      if isinstance(self.skeleton_[ix], types.StringTypes):
80        if skeleton_gatherer:
81          # Make sure the skeleton is like the original
82          assert(isinstance(skeleton_gatherer.skeleton_[ix], types.StringTypes))
83          out.append(skeleton_gatherer.skeleton_[ix])
84        else:
85          out.append(self.skeleton_[ix])
86      else:
87        if skeleton_gatherer:  # Make sure the skeleton is like the original
88          assert(not isinstance(skeleton_gatherer.skeleton_[ix],
89                                types.StringTypes))
90        msg = self.skeleton_[ix].MessageForLanguage(lang,
91                                                    pseudo_if_not_available,
92                                                    fallback_to_english)
93
94        def MyEscape(text):
95          return self.Escape(text)
96        text = msg.GetRealContent(escaping_function=MyEscape)
97        out.append(text)
98    return ''.join(out)
99
100  def Parse(self):
101    '''Parses the section.  Implemented by subclasses.  Idempotent.'''
102    raise NotImplementedError()
103
104  def _AddNontranslateableChunk(self, chunk):
105    '''Adds a nontranslateable chunk.'''
106    if self.single_message_:
107      ph = tclib.Placeholder('XX%02dXX' % self.ph_counter_, chunk, chunk)
108      self.ph_counter_ += 1
109      self.single_message_.AppendPlaceholder(ph)
110    else:
111      self.skeleton_.append(chunk)
112
113  def _AddTranslateableChunk(self, chunk):
114    '''Adds a translateable chunk.  It will be unescaped before being added.'''
115    # We don't want empty messages since they are redundant and the TC
116    # doesn't allow them.
117    if chunk == '':
118      return
119
120    unescaped_text = self.UnEscape(chunk)
121    if self.single_message_:
122      self.single_message_.AppendText(unescaped_text)
123    else:
124      self.skeleton_.append(self.uberclique.MakeClique(
125        tclib.Message(text=unescaped_text)))
126      self.translatable_chunk_ = True
127
128  def SubstituteMessages(self, substituter):
129    '''Applies substitutions to all messages in the tree.
130
131    Goes through the skeleton and finds all MessageCliques.
132
133    Args:
134      substituter: a grit.util.Substituter object.
135    '''
136    if self.single_message_:
137      self.single_message_ = substituter.SubstituteMessage(self.single_message_)
138    new_skel = []
139    for chunk in self.skeleton_:
140      if isinstance(chunk, clique.MessageClique):
141        old_message = chunk.GetMessage()
142        new_message = substituter.SubstituteMessage(old_message)
143        if new_message is not old_message:
144          new_skel.append(self.uberclique.MakeClique(new_message))
145          continue
146      new_skel.append(chunk)
147    self.skeleton_ = new_skel
148