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
    113 -    def makeheaderdict(self,
    114              charset="CHARSET",
    115              encoding="ENCODING",
    116              project_id_version=None,
    117              pot_creation_date=None,
    118              po_revision_date=None,
    119              last_translator=None,
    120              language_team=None,
    121              mime_version=None,
    122              plural_forms=None,
    123              report_msgid_bugs_to=None,
    124              **kwargs):
    125          """Create a header dictionary with useful defaults.
    126
    127          pot_creation_date can be None (current date) or a value (datetime or string)
    128          po_revision_date can be None (form), False (=pot_creation_date), True (=now),
    129          or a value (datetime or string)
    130
    131          @return: Dictionary with the header items
    132          @rtype: dict
    133          """
    134          if project_id_version is None:
    135              project_id_version = "PACKAGE VERSION"
    136          if pot_creation_date is None or pot_creation_date == True:
    137              pot_creation_date = time.strftime("%Y-%m-%d %H:%M") + tzstring()
    138          if isinstance(pot_creation_date, time.struct_time):
    139              pot_creation_date = time.strftime("%Y-%m-%d %H:%M", pot_creation_date) + tzstring()
    140          if po_revision_date is None:
    141              po_revision_date = "YEAR-MO-DA HO:MI+ZONE"
    142          elif po_revision_date == False:
    143              po_revision_date = pot_creation_date
    144          elif po_revision_date == True:
    145              po_revision_date = time.strftime("%Y-%m-%d %H:%M") + tzstring()
    146          if isinstance(po_revision_date, time.struct_time):
    147              po_revision_date = time.strftime("%Y-%m-%d %H:%M", po_revision_date) + tzstring()
    148          if last_translator is None:
    149              last_translator = "FULL NAME <EMAIL@ADDRESS>"
    150          if language_team is None:
    151              language_team = "LANGUAGE <LL@li.org>"
    152          if mime_version is None:
    153              mime_version = "1.0"
    154          if report_msgid_bugs_to is None:
    155              report_msgid_bugs_to = ""
    156
    157          defaultargs = dictutils.ordereddict()
    158          defaultargs["Project-Id-Version"] = project_id_version
    159          defaultargs["Report-Msgid-Bugs-To"] = report_msgid_bugs_to
    160          defaultargs["POT-Creation-Date"] = pot_creation_date
    161          defaultargs["PO-Revision-Date"] = po_revision_date
    162          defaultargs["Last-Translator"] = last_translator
    163          defaultargs["Language-Team"] = language_team
    164          defaultargs["MIME-Version"] = mime_version
    165          defaultargs["Content-Type"] = "text/plain; charset=%s" % charset
    166          defaultargs["Content-Transfer-Encoding"] = encoding
    167          if plural_forms:
    168              defaultargs["Plural-Forms"] = plural_forms
    169          defaultargs["X-Generator"] = self.x_generator
    170
    171          return update(defaultargs, add=True, **kwargs)
    172
    173 -    def header(self):
    174          """Returns the header element, or None. Only the first element is allowed
    175          to be a header. Note that this could still return an empty header element,
    176          if present."""
    177          if len(self.units) == 0:
    178              return None
    179          candidate = self.units[0]
    180          if candidate.isheader():
    181              return candidate
    182          else:
    183              return None
    184
    185 -    def parseheader(self):
    186          """Parses the PO header and returns the interpreted values as a
    187          dictionary."""
    188          header = self.header()
    189          if not header:
    190              return {}
    191          return parseheaderstring(header.target)
    192
    193 -    def updateheader(self, add=False, **kwargs):
    194          """Updates the fields in the PO style header.
    195
    196          This will create a header if add == True."""
    197          header = self.header()
    198          if not header:
    199              if add:
    200                  header = self.makeheader(**kwargs)
    201                  # we should be using .addunit() or some equivalent in case the
    202                  # unit needs to refer back to the store, etc. This might be
    203                  # subtly broken for POXLIFF, since we don't dupliate the code
    204                  # from lisa::addunit().
    205                  header._store = self
    206                  self.units.insert(0, header)
    207          else:
    208              headeritems = update(self.parseheader(), add, **kwargs)
    209              keys = headeritems.keys()
    210              if not "Content-Type" in keys or "charset=CHARSET" in headeritems["Content-Type"]:
    211                  headeritems["Content-Type"] = "text/plain; charset=UTF-8"
    212              if not "Content-Transfer-Encoding" in keys or "ENCODING" in headeritems["Content-Transfer-Encoding"]:
    213                  headeritems["Content-Transfer-Encoding"] = "8bit"
    214              headerString = ""
    215              for key, value in headeritems.items():
    216                  if value is not None:
    217                      headerString += "%s: %s\n" % (key, value)
    218              header.target = headerString
    219              header.markfuzzy(False)    # TODO: check why we do this?
    220          return header
    221
    222 -    def getheaderplural(self):
    223          """Returns the nplural and plural values from the header."""
    224          header = self.parseheader()
    225          pluralformvalue = header.get('Plural-Forms', None)
    226          if pluralformvalue is None:
    227              return None, None
    228          nplural = re.findall("nplurals=(.+?);", pluralformvalue)
    229          plural = re.findall("plural=(.+?);?$", pluralformvalue)
    230          if not nplural or nplural[0] == "INTEGER":
    231              nplural = None
    232          else:
    233              nplural = nplural[0]
    234          if not plural or plural[0] == "EXPRESSION":
    235              plural = None
    236          else:
    237              plural = plural[0]
    238          return nplural, plural
    239
    240 -    def updateheaderplural(self, nplurals, plural):
    241          """Update the Plural-Form PO header."""
    242          if isinstance(nplurals, basestring):
    243              nplurals = int(nplurals)
    244          self.updateheader(add=True, Plural_Forms = "nplurals=%d; plural=%s;" % (nplurals, plural) )
    245
    246 -    def gettargetlanguage(self):
    247          """Return the target language if specified in the header.
    248
    249          Some attempt at understanding Poedit's custom headers is done."""
    250          header = self.parseheader()
    251          if 'X-Poedit-Language' in header:
    252              from translate.lang import poedit
    253              language = header.get('X-Poedit-Language')
    254              country = header.get('X-Poedit-Country')
    255              return poedit.isocode(language, country)
    256          return header.get('Language')
    257
    258 -    def settargetlanguage(self, lang):
    259          """Set the target language in the header.
    260
    261          This removes any custom Poedit headers if they exist.
    262
    263          @param lang: the new target language code
    264          @type lang: str
    265          """
    266          if isinstance(lang, basestring) and len(lang) > 1:
    267              self.updateheader(add=True, Language=lang, X_Poedit_Language=None, X_Poedit_Country=None)
    268
    269 -    def mergeheaders(self, otherstore):
    270          """Merges another header with this header.
    271
    272          This header is assumed to be the template.
    273
    274          @type otherstore: L{base.TranslationStore}
    275          """
    276
    277          newvalues = otherstore.parseheader()
    278          retain = {
    279                  "Project_Id_Version": newvalues['Project-Id-Version'],
    280                  "PO_Revision_Date"  : newvalues['PO-Revision-Date'],
    281                  "Last_Translator"   : newvalues['Last-Translator'],
    282                  "Language_Team"     : newvalues['Language-Team'],
    283          }
    284          # not necessarily there:
    285          plurals = newvalues.get('Plural-Forms', None)
    286          if plurals:
    287              retain['Plural-Forms'] = plurals
    288          self.updateheader(**retain)
    289
    290 -    def updatecontributor(self, name, email=None):
    291          """Add contribution comments if necessary."""
    292          header = self.header()
    293          if not header:
    294              return
    295          prelines = []
    296          contriblines = []
    297          postlines = []
    298          contribexists = False
    299          incontrib = False
    300          outcontrib = False
    301          for line in header.getnotes("translator").split('\n'):
    302              line = line.strip()
    303              if line == u"FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.":
    304                  incontrib = True
    305                  continue
    306              if author_re.match(line):
    307                  incontrib = True
    308                  contriblines.append(line)
    309                  continue
    310              if line == "" and incontrib:
    311                  incontrib = False
    312                  outcontrib = True
    313              if incontrib:
    314                  contriblines.append(line)
    315              elif not outcontrib:
    316                  prelines.append(line)
    317              else:
    318                  postlines.append(line)
    319
    320          year = time.strftime("%Y")
    321          contribexists = False
    322          for i in range(len(contriblines)):
    323              line = contriblines[i]
    324              if name in line and (email is None or email in line):
    325                  contribexists = True
    326                  if year in line:
    327                      break
    328                  else:
    329                      #The contributor is there, but not for this year
    330                      if line[-1] == '.':
    331                          line = line[:-1]
    332                      contriblines[i] = "%s, %s." % (line, year)
    333
    334          if not contribexists:
    335              # Add a new contributor
    336              if email:
    337                  contriblines.append("%s <%s>, %s." % (name, email, year))
    338              else:
    339                  contriblines.append("%s, %s." % (name, year))
    340
    341          header.removenotes()
    342          header.addnote("\n".join(prelines))
    343          header.addnote("\n".join(contriblines))
    344          header.addnote("\n".join(postlines))
    345
    346 -    def makeheader(self, **kwargs):
    347          """Create a header for the given filename.
    348
    349          Check .makeheaderdict() for information on parameters."""
    350          headerpo = self.UnitClass(encoding=self._encoding)
    351          headerpo.markfuzzy()
    352          headerpo.source = ""
    353          headeritems = self.makeheaderdict(**kwargs)
    354          headervalue = ""
    355          for (key, value) in headeritems.items():
    356              headervalue += "%s: %s\n" % (key, value)
    357          headerpo.target = headervalue
    358          return headerpo
    359