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