195640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org#!/usr/bin/env python
295640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org# Copyright (c) 2012 The Chromium Authors. All rights reserved.
301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# Use of this source code is governed by a BSD-style license that can be
401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# found in the LICENSE file.
501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''A baseclass for simple gatherers that store their gathered resource in a
701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orglist.
801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''
901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport types
1101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit.gather import interface
1301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit import clique
1401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit import tclib
1501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass SkeletonGatherer(interface.GathererBase):
1801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  '''Common functionality of gatherers that parse their input as a skeleton of
1901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  translatable and nontranslatable chunks.
2001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  '''
2101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
22ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org  def __init__(self, *args, **kwargs):
23ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org    super(SkeletonGatherer, self).__init__(*args, **kwargs)
2401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # List of parts of the document. Translateable parts are
2501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # clique.MessageClique objects, nontranslateable parts are plain strings.
2601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # Translated messages are inserted back into the skeleton using the quoting
2701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # rules defined by self.Escape()
2801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.skeleton_ = []
2901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # A list of the names of IDs that need to be defined for this resource
3001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # section to compile correctly.
3101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.ids_ = []
3201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # True if Parse() has already been called.
3301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.have_parsed_ = False
3401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # True if a translatable chunk has been added
3501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.translatable_chunk_ = False
3601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # If not None, all parts of the document will be put into this single
3701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # message; otherwise the normal skeleton approach is used.
3801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.single_message_ = None
3901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # Number to use for the next placeholder name.  Used only if single_message
4001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # is not None
4101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.ph_counter_ = 1
4201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
4301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetText(self):
4401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns the original text of the section'''
4501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self.text_
4601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
4701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def Escape(self, text):
4801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Subclasses can override.  Base impl is identity.
4901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
5001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return text
5101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
5201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def UnEscape(self, text):
5301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Subclasses can override. Base impl is identity.
5401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
5501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return text
5601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
5701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetTextualIds(self):
5801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns the list of textual IDs that need to be defined for this
5901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    resource section to compile correctly.'''
6001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self.ids_
6101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
6201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def _AddTextualId(self, id):
6301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.ids_.append(id)
6401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
6501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetCliques(self):
6601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns the message cliques for each translateable message in the
6701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    resource section.'''
68705a118ab4a1f2fe348fccdcd4786a0b5bf426ecjoi@chromium.org    return [x for x in self.skeleton_ if isinstance(x, clique.MessageClique)]
6901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
7001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def Translate(self, lang, pseudo_if_not_available=True,
7101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                skeleton_gatherer=None, fallback_to_english=False):
7201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if len(self.skeleton_) == 0:
7301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      raise exception.NotReady()
7401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if skeleton_gatherer:
7501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      assert len(skeleton_gatherer.skeleton_) == len(self.skeleton_)
7601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
7701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    out = []
7801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for ix in range(len(self.skeleton_)):
7901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if isinstance(self.skeleton_[ix], types.StringTypes):
8001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if skeleton_gatherer:
8101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          # Make sure the skeleton is like the original
8201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          assert(isinstance(skeleton_gatherer.skeleton_[ix], types.StringTypes))
8301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          out.append(skeleton_gatherer.skeleton_[ix])
8401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        else:
8501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          out.append(self.skeleton_[ix])
8601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      else:
8701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        if skeleton_gatherer:  # Make sure the skeleton is like the original
8801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          assert(not isinstance(skeleton_gatherer.skeleton_[ix],
8901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                                types.StringTypes))
9001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        msg = self.skeleton_[ix].MessageForLanguage(lang,
9101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                                                    pseudo_if_not_available,
9201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org                                                    fallback_to_english)
9301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
9401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        def MyEscape(text):
9501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          return self.Escape(text)
9601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        text = msg.GetRealContent(escaping_function=MyEscape)
9701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        out.append(text)
9801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return ''.join(out)
9901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
10001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def Parse(self):
10101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Parses the section.  Implemented by subclasses.  Idempotent.'''
10201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    raise NotImplementedError()
10301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
10401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def _AddNontranslateableChunk(self, chunk):
10501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Adds a nontranslateable chunk.'''
10601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if self.single_message_:
10701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      ph = tclib.Placeholder('XX%02dXX' % self.ph_counter_, chunk, chunk)
10801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.ph_counter_ += 1
10901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.single_message_.AppendPlaceholder(ph)
11001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    else:
11101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.skeleton_.append(chunk)
11201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
11301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def _AddTranslateableChunk(self, chunk):
11401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Adds a translateable chunk.  It will be unescaped before being added.'''
11501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # We don't want empty messages since they are redundant and the TC
11601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # doesn't allow them.
11701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if chunk == '':
11801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      return
11901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
12001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    unescaped_text = self.UnEscape(chunk)
12101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if self.single_message_:
12201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.single_message_.AppendText(unescaped_text)
12301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    else:
12401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.skeleton_.append(self.uberclique.MakeClique(
12501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        tclib.Message(text=unescaped_text)))
12601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.translatable_chunk_ = True
12777cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org
12877cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org  def SubstituteMessages(self, substituter):
12977cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    '''Applies substitutions to all messages in the tree.
13077cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org
13177cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    Goes through the skeleton and finds all MessageCliques.
13277cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org
13377cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    Args:
13477cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org      substituter: a grit.util.Substituter object.
13577cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    '''
13677cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    if self.single_message_:
13777cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org      self.single_message_ = substituter.SubstituteMessage(self.single_message_)
13877cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    new_skel = []
13977cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    for chunk in self.skeleton_:
14077cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org      if isinstance(chunk, clique.MessageClique):
14177cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org        old_message = chunk.GetMessage()
14277cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org        new_message = substituter.SubstituteMessage(old_message)
14377cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org        if new_message is not old_message:
14477cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org          new_skel.append(self.uberclique.MakeClique(new_message))
14577cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org          continue
14677cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org      new_skel.append(chunk)
14777cbaa8b1f1af05d8ba2c2a951c74e7909318830joi@chromium.org    self.skeleton_ = new_skel
148