text: # Copyright (c) 2012 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.
  
  # The tclib module contains tools for aggregating, verifying, and storing
  # messages destined for the Translation Console, as well as for reading
  # translations back and outputting them in some desired format.
  #
  # This has been stripped down to include only the functionality needed by grit
  # for creating Windows .rc and .h files.  These are the only parts needed by
  # the Chrome build process.
  
  from __future__ import print_function
  
  from grit.extern import FP
  
  # This module assumes that within a bundle no two messages can have the
  # same id unless they're identical.
  
  # The basic classes defined here for external use are Message and Translation,
  # where the former is used for English messages and the latter for
  # translations. These classes have a lot of common functionality, as expressed
  # by the common parent class BaseMessage. Perhaps the most important
  # distinction is that translated text is stored in UTF-8, whereas original text
  # is stored in whatever encoding the client uses (presumably Latin-1).
  
  # --------------------
  # The public interface
  # --------------------
  
  # Generate message id from message text and meaning string (optional),
  # both in utf-8 encoding
  #
  def GenerateMessageId(message, meaning=''):
    fp = FP.FingerPrint(message)
    if meaning:
      # combine the fingerprints of message and meaning
      fp2 = FP.FingerPrint(meaning)
      if fp < 0:
        fp = fp2 + (fp << 1) + 1
      else:
        fp = fp2 + (fp << 1)
    # To avoid negative ids we strip the high-order bit
    return str(fp & 0x7fffffffffffffff)
  
  # -------------------------------------------------------------------------
  # The MessageTranslationError class is used to signal tclib-specific errors.
  
  
  class MessageTranslationError(Exception):
  
    def __init__(self, args = ''):
      self.args = args
  
  
  # -----------------------------------------------------------
  # The Placeholder class represents a placeholder in a message.
  
  class Placeholder(object):
    # String representation
    def __str__(self):
      return '%s, "%s", "%s"' % \
             (self.__presentation, self.__original, self.__example)
  
    # Getters
    def GetOriginal(self):
      return self.__original
  
    def GetPresentation(self):
      return self.__presentation
  
    def GetExample(self):
      return self.__example
  
    def __eq__(self, other):
      return self.EqualTo(other, strict=1, ignore_trailing_spaces=0)
  
    # Equality test
    #
    # ignore_trailing_spaces: TC is using varchar to store the
    # phrwr fields, as a result of that, the trailing spaces
    # are removed by MySQL when the strings are stored into TC:-(
    # ignore_trailing_spaces parameter is used to ignore
    # trailing spaces during equivalence comparison.
    #
    def EqualTo(self, other, strict = 1, ignore_trailing_spaces = 1):
      if type(other) is not Placeholder:
        return 0
      if StringEquals(self.__presentation, other.__presentation,
                      ignore_trailing_spaces):
        if not strict or (StringEquals(self.__original, other.__original,
                                       ignore_trailing_spaces)  and
                          StringEquals(self.__example, other.__example,
                                       ignore_trailing_spaces)):
          return 1
      return 0
  
  
  # -----------------------------------------------------------------
  # BaseMessage is the common parent class of Message and Translation.
  # It is not meant for direct use.
  
  class BaseMessage(object):
    # Three types of message construction is supported. If the message text is a
    # simple string with no dynamic content, you can pass it to the constructor
    # as the "text" parameter. Otherwise, you can omit "text" and assemble the
    # message step by step using AppendText() and AppendPlaceholder(). Or, as an
    # alternative, you can give the constructor the "presentable" version of the
    # message and a list of placeholders; it will then parse the presentation and
    # build the message accordingly. For example:
    # Message(text = "There are NUM_BUGS bugs in your code",
    #         placeholders = [Placeholder("NUM_BUGS", "%d", "33")],
    #         description = "Bla bla bla")
    def __eq__(self, other):
      # "source encoding" is nonsense, so ignore it
      return _ObjectEquals(self, other, ['_BaseMessage__source_encoding'])
  
    def GetName(self):
      return self.__name
  
    def GetSourceEncoding(self):
      return self.__source_encoding
  
    # Append a placeholder to the message
    def AppendPlaceholder(self, placeholder):
      if not isinstance(placeholder, Placeholder):
        raise MessageTranslationError("Invalid message placeholder %s in "
                                      "message %s" % (placeholder, self.GetId()))
      # Are there other placeholders with the same presentation?
      # If so, they need to be the same.
      for other in self.GetPlaceholders():
        if placeholder.GetPresentation() == other.GetPresentation():
          if not placeholder.EqualTo(other):
            raise MessageTranslationError(
                "Conflicting declarations of %s within message" %
                placeholder.GetPresentation())
      # update placeholder list
      dup = 0
      for item in self.__content:
        if isinstance(item, Placeholder) and placeholder.EqualTo(item):
          dup = 1
          break
      if not dup:
        self.__placeholders.append(placeholder)
  
      # update content
      self.__content.append(placeholder)
  
    # Strips leading and trailing whitespace, and returns a tuple
    # containing the leading and trailing space that was removed.
    def Strip(self):
      leading = trailing = ''
      if len(self.__content) > 0:
        s0 = self.__content[0]
        if not isinstance(s0, Placeholder):
          s = s0.lstrip()
          leading = s0[:-len(s)]
          self.__content[0] = s
  
        s0 = self.__content[-1]
        if not isinstance(s0, Placeholder):
          s = s0.rstrip()
          trailing = s0[len(s):]
          self.__content[-1] = s
      return leading, trailing
  
    # Return the id of this message
    def GetId(self):
      if self.__id is None:
        return self.GenerateId()
      return self.__id
  
    # Set the id of this message
    def SetId(self, id):
      if id is None:
        self.__id = None
      else:
        self.__id = str(id)  # Treat numerical ids as strings
  
    # Return content of this message as a list (internal use only)
    def GetContent(self):
      return self.__content
  
    # Return a human-readable version of this message
    def GetPresentableContent(self):
      presentable_content = ""
      for item in self.__content:
        if isinstance(item, Placeholder):
          presentable_content += item.GetPresentation()
        else:
          presentable_content += item
  
      return presentable_content
  
    # Return a fragment of a message in escaped format
    def EscapeFragment(self, fragment):
      return fragment.replace('%', '%%')
  
    # Return the "original" version of this message, doing %-escaping
    # properly.  If source_msg is specified, the placeholder original
    # information inside source_msg will be used instead.
    def GetOriginalContent(self, source_msg = None):
      original_content = ""
      for item in self.__content:
        if isinstance(item, Placeholder):
          if source_msg:
            ph = source_msg.GetPlaceholder(item.GetPresentation())
            if not ph:
              raise MessageTranslationError(
                  "Placeholder %s doesn't exist in message: %s" %
                  (item.GetPresentation(), source_msg))
            original_content += ph.GetOriginal()
          else:
            original_content += item.GetOriginal()
        else:
          original_content += self.EscapeFragment(item)
      return original_content
  
    # Return the example of this message
    def GetExampleContent(self):
      example_content = ""
      for item in self.__content:
        if isinstance(item, Placeholder):
          example_content += item.GetExample()
        else:
          example_content += item
      return example_content
  
    # Return a list of all unique placeholders in this message
    def GetPlaceholders(self):
      return self.__placeholders
  
    # Return a placeholder in this message
    def GetPlaceholder(self, presentation):
      for item in self.__content:
        if (isinstance(item, Placeholder) and
            item.GetPresentation() == presentation):
          return item
      return None
  
    # Return this message's description
    def GetDescription(self):
      return self.__description
  
    # Add a message source
    def AddSource(self, source):
      self.__sources.append(source)
  
    # Return this message's sources as a list
    def GetSources(self):
      return self.__sources
  
    # Return this message's sources as a string
    def GetSourcesAsText(self, delimiter = "; "):
      return delimiter.join(self.__sources)
  
    # Set the obsolete flag for a message (internal use only)
    def SetObsolete(self):
      self.__obsolete = 1
  
    # Get the obsolete flag for a message (internal use only)
    def IsObsolete(self):
      return self.__obsolete
  
    # Get the sequence number (0 by default)
    def GetSequenceNumber(self):
      return self.__sequence_number
  
    # Set the sequence number
    def SetSequenceNumber(self, number):
      self.__sequence_number = number
  
    # Increment instance counter
    def AddInstance(self):
      self.__num_instances += 1
  
    # Return instance count
    def GetNumInstances(self):
      return self.__num_instances
  
    def GetErrors(self, from_tc=0):
      """
      Returns a description of the problem if the message is not
      syntactically valid, or None if everything is fine.
  
      Args:
        from_tc: indicates whether this message came from the TC. We let
        the TC get away with some things we normally wouldn't allow for
        historical reasons.
      """
      # check that placeholders are unambiguous
      pos = 0
      phs = {}
      for item in self.__content:
        if isinstance(item, Placeholder):
          phs[pos] = item
          pos += len(item.GetPresentation())
        else:
          pos += len(item)
      presentation = self.GetPresentableContent()
      for ph in self.GetPlaceholders():
        for pos in FindOverlapping(presentation, ph.GetPresentation()):
          # message contains the same text as a placeholder presentation
          other_ph = phs.get(pos)
          if ((not other_ph
               and not IsSubstringInPlaceholder(pos, len(ph.GetPresentation()), phs))
              or
              (other_ph and len(other_ph.GetPresentation()) < len(ph.GetPresentation()))):
            return  "message contains placeholder name '%s':\n%s" % (
              ph.GetPresentation(), presentation)
      return None
  
  
    def __CopyTo(self, other):
      """
      Returns a copy of this BaseMessage.
      """
      assert isinstance(other,  self.__class__) or isinstance(self, other.__class__)
      other.__source_encoding = self.__source_encoding
      other.__content         = self.__content[:]
      other.__description     = self.__description
      other.__id              = self.__id
      other.__num_instances   = self.__num_instances
      other.__obsolete        = self.__obsolete
      other.__name            = self.__name
      other.__placeholders    = self.__placeholders[:]
      other.__sequence_number = self.__sequence_number
      other.__sources         = self.__sources[:]
  
      return other
  
    def HasText(self):
      """Returns true iff this message has anything other than placeholders."""
      for item in self.__content:
        if not isinstance(item, Placeholder):
          return True
      return False
  
  # --------------------------------------------------------
  # The Message class represents original (English) messages
  
  class Message(BaseMessage):
    # See BaseMessage constructor
    def __init__(self, source_encoding, text=None, id=None,
                 description=None, meaning="", placeholders=None,
                 source=None, sequence_number=0, clone_from=None,
                 time_created=0, name=None, is_hidden = 0):
  
      if clone_from is not None:
        BaseMessage.__init__(self, None, clone_from=clone_from)
        self.__meaning = clone_from.__meaning
        self.__time_created = clone_from.__time_created
        self.__is_hidden = clone_from.__is_hidden
        return
  
      BaseMessage.__init__(self, source_encoding, text, id, description,
                           placeholders, source, sequence_number,
                           name=name)
      self.__meaning = meaning
      self.__time_created = time_created
      self.SetIsHidden(is_hidden)
  
    # String representation
    def __str__(self):
      s = 'source: %s, id: %s, content: "%s", meaning: "%s", ' \
          'description: "%s"' % \
          (self.GetSourcesAsText(), self.GetId(), self.GetPresentableContent(),
           self.__meaning, self.GetDescription())
      if self.GetName() is not None:
        s += ', name: "%s"' % self.GetName()
      placeholders = self.GetPlaceholders()
      for i in range(len(placeholders)):
        s += ", placeholder[%d]: %s" % (i, placeholders[i])
      return s
  
    # Strips leading and trailing whitespace, and returns a tuple
    # containing the leading and trailing space that was removed.
    def Strip(self):
      leading = trailing = ''
      content = self.GetContent()
      if len(content) > 0:
        s0 = content[0]
        if not isinstance(s0, Placeholder):
          s = s0.lstrip()
          leading = s0[:-len(s)]
          content[0] = s
  
        s0 = content[-1]
        if not isinstance(s0, Placeholder):
          s = s0.rstrip()
          trailing = s0[len(s):]
          content[-1] = s
      return leading, trailing
  
    # Generate an id by hashing message content
    def GenerateId(self):
      self.SetId(GenerateMessageId(self.GetPresentableContent(),
                                   self.__meaning))
      return self.GetId()
  
    def GetMeaning(self):
      return self.__meaning
  
    def GetTimeCreated(self):
      return self.__time_created
  
    # Equality operator
    def EqualTo(self, other, strict = 1):
      # Check id, meaning, content
      if self.GetId() != other.GetId():
        return 0
      if self.__meaning != other.__meaning:
        return 0
      if self.GetPresentableContent() != other.GetPresentableContent():
        return 0
      # Check descriptions if comparison is strict
      if (strict and
          self.GetDescription() is not None and
          other.GetDescription() is not None and
          self.GetDescription() != other.GetDescription()):
        return 0
      # Check placeholders
      ph1 = self.GetPlaceholders()
      ph2 = other.GetPlaceholders()
      if len(ph1) != len(ph2):
        return 0
      for i in range(len(ph1)):
        if not ph1[i].EqualTo(ph2[i], strict):
          return 0
  
      return 1
  
    def Copy(self):
      """
      Returns a copy of this Message.
      """
      assert isinstance(self, Message)
      return Message(None, clone_from=self)
  
    def SetIsHidden(self, is_hidden):
      """Sets whether this message should be hidden.
  
      Args:
        is_hidden : 0 or 1 - if the message should be hidden, 0 otherwise
      """
      if is_hidden not in [0, 1]:
        raise MessageTranslationError("is_hidden must be 0 or 1, got %s")
      self.__is_hidden = is_hidden
  
    def IsHidden(self):
      """Returns 1 if this message is hidden, and 0 otherwise."""
      return self.__is_hidden
  
  # ----------------------------------------------------
  # The Translation class represents translated messages
  
  class Translation(BaseMessage):
    # See BaseMessage constructor
    def __init__(self, source_encoding, text=None, id=None,
                 description=None, placeholders=None, source=None,
                 sequence_number=0, clone_from=None, ignore_ph_errors=0,
                 name=None):
      if clone_from is not None:
        BaseMessage.__init__(self, None, clone_from=clone_from)
        return
  
      BaseMessage.__init__(self, source_encoding, text, id, description,
                           placeholders, source, sequence_number,
                           ignore_ph_errors=ignore_ph_errors, name=name)
  
    # String representation
    def __str__(self):
      s = 'source: %s, id: %s, content: "%s", description: "%s"' % \
          (self.GetSourcesAsText(), self.GetId(), self.GetPresentableContent(),
           self.GetDescription());
      placeholders = self.GetPlaceholders()
      for i in range(len(placeholders)):
        s += ", placeholder[%d]: %s" % (i, placeholders[i])
      return s
  
    # Equality operator
    def EqualTo(self, other, strict=1):
      # Check id and content
      if self.GetId() != other.GetId():
        return 0
      if self.GetPresentableContent() != other.GetPresentableContent():
        return 0
      # Check placeholders
      ph1 = self.GetPlaceholders()
      ph2 = other.GetPlaceholders()
      if len(ph1) != len(ph2):
        return 0
      for i in range(len(ph1)):
        if not ph1[i].EqualTo(ph2[i], strict):
          return 0
  
      return 1
  
    def Copy(self):
      """
      Returns a copy of this Translation.
      """
      return Translation(None, clone_from=self)
  