Package translate :: Package storage :: Module poheader

[hide private]
[frames] | no frames]

# Source Code for Module translate.storage.poheader

      1  #!/usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3  #
      4  # Copyright 2002-2009 Zuza Software Foundation
      5  #
      6  # This file is part of the Translate Toolkit.
      7  #
      8  # This program is free software; you can redistribute it and/or modify
      9  # it under the terms of the GNU General Public License as published by
     10  # the Free Software Foundation; either version 2 of the License, or
     11  # (at your option) any later version.
     12  #
     13  # This program is distributed in the hope that it will be useful,
     14  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  # GNU General Public License for more details.
     17  #
     18  # You should have received a copy of the GNU General Public License
     19  # along with this program; if not, see <http://www.gnu.org/licenses/>.
     20
     21  """class that handles all header functions for a header in a po file"""
     22
     23  from translate.misc import dictutils
     24  from translate import __version__
     25  import re
     26  import time
     27
     28  author_re = re.compile(r".*<\S+@\S+>.*\d{4,4}")
     29
     30  default_header = {
     31      "Project-Id-Version": "PACKAGE VERSION",
     32      "PO-Revision-Date": "YEAR-MO-DA HO:MI+ZONE",
     33      "Last-Translator": "FULL NAME <EMAIL@ADDRESS>",
     34      "Language-Team": "LANGUAGE <LL@li.org>",
     35      "Plural-Forms": "nplurals=INTEGER; plural=EXPRESSION;",
     36      }
     37
     38 -def parseheaderstring(input):
     39      """Parses an input string with the definition of a PO header and returns
     40      the interpreted values as a dictionary."""
     41      headervalues = dictutils.ordereddict()
     42      for line in input.split("\n"):
     43          if not line or ":" not in line:
     44              continue
     45          key, value = line.split(":", 1)
     46          #We don't want unicode keys
     47          key = str(key.strip())
     48          headervalues[key] = value.strip()
     49      return headervalues
     50
     51 -def tzstring():
     52      """Returns the timezone as a string in the format [+-]0000, eg +0200.
     53
     54      @rtype: str"""
     55      if time.daylight:
     56          tzoffset = time.altzone
     57      else:
     58          tzoffset = time.timezone
     59
     60      hours, minutes = time.gmtime(abs(tzoffset))[3:5]
     61      if tzoffset > 0:
     62          hours *= -1
     63      tz = str("%+d" % hours).zfill(3) + str(minutes).zfill(2)
     64      return tz
     65
     66 -def update(existing, add=False, **kwargs):
     67      """Update an existing header dictionary with the values in kwargs, adding new values
     68      only if add is true.
     69
     70      @return: Updated dictionary of header entries
     71      @rtype: dict
     72      """
     73      headerargs = dictutils.ordereddict()
     74      fixedargs = dictutils.cidict()
     75      for key, value in kwargs.items():
     76          key = key.replace("_", "-")
     77          if key.islower():
     78              key = key.title()
     79          fixedargs[key] = value
     80      removed = []
     81      for key in poheader.header_order:
     82          if existing.has_key(key):
     83              if key in fixedargs:
     84                  headerargs[key] = fixedargs.pop(key)
     85              else:
     86                  headerargs[key] = existing[key]
     87              removed.append(key)
     88          elif add and fixedargs.has_key(key):
     89              headerargs[key] = fixedargs.pop(key)
     90      for key, value in existing.iteritems():
     91          if not key in removed:
     92              headerargs[key] = value
     93      if add:
     94          for key in fixedargs:
     95              headerargs[key] = fixedargs[key]
     96      return headerargs
     97
     98
     99 -class poheader(object):
    100      """This class implements functionality for manipulation of po file headers.
    101      This class is a mix-in class and useless on its own. It must be used from all
    102      classes which represent a po file"""
    103
    104      x_generator = "Translate Toolkit %s" % __version__.sver
    105
    106      header_order = [
    107          "Project-Id-Version",
    108          "Report-Msgid-Bugs-To",
    109          "POT-Creation-Date",
    110          "PO-Revision-Date",
    111          "Last-Translator",
    112          "Language-Team",
    113          "Language",
    114          "MIME-Version",
    115          "Content-Type",
    116          "Content-Transfer-Encoding",
    117          "Plural-Forms",
    118          "X-Generator",
    119          ]
    120 -    def init_headers(self, charset='utf-8', encoding='8bit', **kwargs):
    121          """sets default values for po headers"""
    122          #FIXME: we need to allow at least setting target language, pluralforms and generator
    123          headerdict = self.makeheaderdict(charset=charset, encoding=encoding, **kwargs)
    124          self.updateheader(add=True, **headerdict)
    125          return self.header()
    126
    127 -    def makeheaderdict(self,
    128              charset="CHARSET",
    129              encoding="ENCODING",
    130              project_id_version=None,
    131              pot_creation_date=None,
    132              po_revision_date=None,
    133              last_translator=None,
    134              language_team=None,
    135              mime_version=None,
    136              plural_forms=None,
    137              report_msgid_bugs_to=None,
    138              **kwargs):
    139          """Create a header dictionary with useful defaults.
    140
    141          pot_creation_date can be None (current date) or a value (datetime or string)
    142          po_revision_date can be None (form), False (=pot_creation_date), True (=now),
    143          or a value (datetime or string)
    144
    145          @return: Dictionary with the header items
    146          @rtype: dict
    147          """
    148          if project_id_version is None:
    149              project_id_version = "PACKAGE VERSION"
    150          if pot_creation_date is None or pot_creation_date == True:
    151              pot_creation_date = time.strftime("%Y-%m-%d %H:%M") + tzstring()
    152          if isinstance(pot_creation_date, time.struct_time):
    153              pot_creation_date = time.strftime("%Y-%m-%d %H:%M", pot_creation_date) + tzstring()
    154          if po_revision_date is None:
    155              po_revision_date = "YEAR-MO-DA HO:MI+ZONE"
    156          elif po_revision_date == False:
    157              po_revision_date = pot_creation_date
    158          elif po_revision_date == True:
    159              po_revision_date = time.strftime("%Y-%m-%d %H:%M") + tzstring()
    160          if isinstance(po_revision_date, time.struct_time):
    161              po_revision_date = time.strftime("%Y-%m-%d %H:%M", po_revision_date) + tzstring()
    162          if last_translator is None:
    163              last_translator = "FULL NAME <EMAIL@ADDRESS>"
    164          if language_team is None:
    165              language_team = "LANGUAGE <LL@li.org>"
    166          if mime_version is None:
    167              mime_version = "1.0"
    168          if report_msgid_bugs_to is None:
    169              report_msgid_bugs_to = ""
    170
    171          defaultargs = dictutils.ordereddict()
    172          defaultargs["Project-Id-Version"] = project_id_version
    173          defaultargs["Report-Msgid-Bugs-To"] = report_msgid_bugs_to
    174          defaultargs["POT-Creation-Date"] = pot_creation_date
    175          defaultargs["PO-Revision-Date"] = po_revision_date
    176          defaultargs["Last-Translator"] = last_translator
    177          defaultargs["Language-Team"] = language_team
    178          defaultargs["MIME-Version"] = mime_version
    179          defaultargs["Content-Type"] = "text/plain; charset=%s" % charset
    180          defaultargs["Content-Transfer-Encoding"] = encoding
    181          if plural_forms:
    182              defaultargs["Plural-Forms"] = plural_forms
    183          defaultargs["X-Generator"] = self.x_generator
    184
    185          return update(defaultargs, add=True, **kwargs)
    186
    187 -    def header(self):
    188          """Returns the header element, or None. Only the first element is allowed
    189          to be a header. Note that this could still return an empty header element,
    190          if present."""
    191          if len(self.units) == 0:
    192              return None
    193          candidate = self.units[0]
    194          if candidate.isheader():
    195              return candidate
    196          else:
    197              return None
    198
    199 -    def parseheader(self):
    200          """Parses the PO header and returns the interpreted values as a
    201          dictionary."""
    202          header = self.header()
    203          if not header:
    204              return {}
    205          return parseheaderstring(header.target)
    206
    207 -    def updateheader(self, add=False, **kwargs):
    208          """Updates the fields in the PO style header.
    209
    210          This will create a header if add == True."""
    211          header = self.header()
    212          if not header:
    213              if add:
    214                  header = self.makeheader(**kwargs)
    215                  self._insert_header(header)
    216          else:
    217              headeritems = update(self.parseheader(), add, **kwargs)
    218              keys = headeritems.keys()
    219              if not "Content-Type" in keys or "charset=CHARSET" in headeritems["Content-Type"]:
    220                  headeritems["Content-Type"] = "text/plain; charset=UTF-8"
    221              if not "Content-Transfer-Encoding" in keys or "ENCODING" in headeritems["Content-Transfer-Encoding"]:
    222                  headeritems["Content-Transfer-Encoding"] = "8bit"
    223              headerString = ""
    224              for key, value in headeritems.items():
    225                  if value is not None:
    226                      headerString += "%s: %s\n" % (key, value)
    227              header.target = headerString
    228              header.markfuzzy(False)    # TODO: check why we do this?
    229          return header
    230
    231 -    def _insert_header(self, header):
    232          # we should be using .addunit() or some equivalent in case the
    233          # unit needs to refer back to the store, etc. This might be
    234          # subtly broken for POXLIFF, since we don't dupliate the code
    235          # from lisa::addunit().
    236          header._store = self
    237          self.units.insert(0, header)
    238
    239 -    def getheaderplural(self):
    240          """Returns the nplural and plural values from the header."""
    241          header = self.parseheader()
    242          pluralformvalue = header.get('Plural-Forms', None)
    243          if pluralformvalue is None:
    244              return None, None
    245          nplural = re.findall("nplurals=(.+?);", pluralformvalue)
    246          plural = re.findall("plural=(.+?);?$", pluralformvalue)
    247          if not nplural or nplural[0] == "INTEGER":
    248              nplural = None
    249          else:
    250              nplural = nplural[0]
    251          if not plural or plural[0] == "EXPRESSION":
    252              plural = None
    253          else:
    254              plural = plural[0]
    255          return nplural, plural
    256
    257 -    def updateheaderplural(self, nplurals, plural):
    258          """Update the Plural-Form PO header."""
    259          if isinstance(nplurals, basestring):
    260              nplurals = int(nplurals)
    261          self.updateheader(add=True, Plural_Forms="nplurals=%d; plural=%s;" % (nplurals, plural) )
    262
    263 -    def gettargetlanguage(self):
    264          """Return the target language based on information in the header.
    265
    266          The target language is determined in the following sequence:
    267            1. Use the 'Language' entry in the header.
    268            2. Poedit's custom headers.
    269            3. Analysing the 'Language-Team' entry.
    270          """
    271          header = self.parseheader()
    272          lang = header.get('Language', None)
    273          if lang is not None:
    274              return lang
    275          if 'X-Poedit-Language' in header:
    276              from translate.lang import poedit
    277              language = header.get('X-Poedit-Language')
    278              country = header.get('X-Poedit-Country')
    279              return poedit.isocode(language, country)
    280          if 'Language-Team' in header:
    281              from translate.lang.team import guess_language
    282              return guess_language(header.get('Language-Team'))
    283          return None
    284
    285 -    def settargetlanguage(self, lang):
    286          """Set the target language in the header.
    287
    288          This removes any custom Poedit headers if they exist.
    289
    290          @param lang: the new target language code
    291          @type lang: str
    292          """
    293          if isinstance(lang, basestring) and len(lang) > 1:
    294              self.updateheader(add=True, Language=lang, X_Poedit_Language=None, X_Poedit_Country=None)
    295
    296 -    def mergeheaders(self, otherstore):
    297          """Merges another header with this header.
    298
    299          This header is assumed to be the template.
    300
    301          @type otherstore: L{base.TranslationStore}
    302          """
    303
    304          newvalues = otherstore.parseheader()
    305          retain_list = ("Project-Id-Version", "PO-Revision-Date", "Last-Translator",
    306                         "Language-Team", "Plural-Forms")
    307          retain = dict((key, newvalues[key]) for key in retain_list if newvalues.get(key, None) and newvalues[key] != default_header.get(key, None))
    308          self.updateheader(**retain)
    309
    310 -    def updatecontributor(self, name, email=None):
    311          """Add contribution comments if necessary."""
    312          header = self.header()
    313          if not header:
    314              return
    315          prelines = []
    316          contriblines = []
    317          postlines = []
    318          contribexists = False
    319          incontrib = False
    320          outcontrib = False
    321          for line in header.getnotes("translator").split('\n'):
    322              line = line.strip()
    323              if line == u"FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.":
    324                  incontrib = True
    325                  continue
    326              if author_re.match(line):
    327                  incontrib = True
    328                  contriblines.append(line)
    329                  continue
    330              if line == "" and incontrib:
    331                  incontrib = False
    332                  outcontrib = True
    333              if incontrib:
    334                  contriblines.append(line)
    335              elif not outcontrib:
    336                  prelines.append(line)
    337              else:
    338                  postlines.append(line)
    339
    340          year = time.strftime("%Y")
    341          contribexists = False
    342          for i in range(len(contriblines)):
    343              line = contriblines[i]
    344              if name in line and (email is None or email in line):
    345                  contribexists = True
    346                  if year in line:
    347                      break
    348                  else:
    349                      #The contributor is there, but not for this year
    350                      if line[-1] == '.':
    351                          line = line[:-1]
    352                      contriblines[i] = "%s, %s." % (line, year)
    353
    354          if not contribexists:
    355              # Add a new contributor
    356              if email:
    357                  contriblines.append("%s <%s>, %s." % (name, email, year))
    358              else:
    359                  contriblines.append("%s, %s." % (name, year))
    360
    361          header.removenotes()
    362          header.addnote("\n".join(prelines))
    363          header.addnote("\n".join(contriblines))
    364          header.addnote("\n".join(postlines))
    365
    366 -    def makeheader(self, **kwargs):
    367          """Create a header for the given filename.
    368
    369          Check .makeheaderdict() for information on parameters."""
    370          headerpo = self.UnitClass("", encoding=self._encoding)
    371          headerpo.markfuzzy()
    372          headeritems = self.makeheaderdict(**kwargs)
    373          headervalue = ""
    374          for (key, value) in headeritems.items():
    375              headervalue += "%s: %s\n" % (key, value)
    376          headerpo.target = headervalue
    377          return headerpo
    378