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-2006 Zuza Software Foundation
      5  #
      6  # This file is part of translate.
      7  #
      8  # translate 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  # translate 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 translate; if not, write to the Free Software
     20  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
     21
     22  """class that handles all header functions for a header in a po file"""
     23
     24  from translate.misc import dictutils
     25  from translate import __version__
     26  import re
     27  import time
     28
     29  author_re = re.compile(r".*<\S+@\S+>.*\d{4,4}")
     30
     31 -def parseheaderstring(input):
     32      """Parses an input string with the definition of a PO header and returns
     33      the interpreted values as a dictionary"""
     34      headervalues = dictutils.ordereddict()
     35      for line in input.split("\n"):
     36          if not line or ":" not in line:
     37              continue
     38          key, value = line.split(":", 1)
     39          #We don't want unicode keys
     40          key = str(key.strip())
     41          headervalues[key] = value.strip()
     42      return headervalues
     43
     44 -def tzstring():
     45      """Returns the timezone as a string in the format [+-]0000, eg +0200."""
     46      if time.daylight:
     47          tzoffset = time.altzone
     48      else:
     49          tzoffset = time.timezone
     50
     51      hours, minutes = time.gmtime(abs(tzoffset))[3:5]
     52      if tzoffset > 0:
     53          hours *= -1
     54      tz = str("%+d" % hours).zfill(3) + str(minutes).zfill(2)
     55      return tz
     56
     57 -def update(existing, add=False, **kwargs):
     58      """Update an existing header dictionary with the values in kwargs, adding new values
     59      only if add is true.
     60
     61      @return: Updated dictionary of header entries
     62      @rtype: dict
     63      """
     64      headerargs = dictutils.ordereddict()
     65      fixedargs = dictutils.cidict()
     66      for key, value in kwargs.items():
     67          key = key.replace("_", "-")
     68          if key.islower():
     69              key = key.title()
     70          fixedargs[key] = value
     71      removed = []
     72      for key in poheader.header_order:
     73          if existing.has_key(key):
     74              if key in fixedargs:
     75                  headerargs[key] = fixedargs.pop(key)
     76              else:
     77                  headerargs[key] = existing[key]
     78              removed.append(key)
     79          elif add and fixedargs.has_key(key):
     80              headerargs[key] = fixedargs.pop(key)
     81      for key, value in existing.iteritems():
     82          if not key in removed:
     83              headerargs[key] = value
     84      if add:
     85          for key in fixedargs:
     86              headerargs[key] = fixedargs[key]
     87      return headerargs
     88
     89
     90 -class poheader(object):
     91      """This class implements functionality for manipulation of po file headers.
     92      This class is a mix-in class and useless on its own. It must be used from all
     93      classes which represent a po file"""
     94
     95      x_generator = "Translate Toolkit %s" % __version__.ver
     96
     97      header_order = [
     98          "Project-Id-Version",
     99          "Report-Msgid-Bugs-To",
    100          "POT-Creation-Date",
    101          "PO-Revision-Date",
    102          "Last-Translator",
    103          "Language-Team",
    104          "MIME-Version",
    105          "Content-Type",
    106          "Content-Transfer-Encoding",
    107          "Plural-Forms",
    108          "X-Generator",
    109          ]
    110
    111
    112 -    def makeheaderdict(self, charset="CHARSET", encoding="ENCODING", project_id_version=None, pot_creation_date=None, po_revision_date=None, last_translator=None, language_team=None, mime_version=None, plural_forms=None, report_msgid_bugs_to=None, **kwargs):
    113          """create a header for the given filename. arguments are specially handled, kwargs added as key: value
    114          pot_creation_date can be None (current date) or a value (datetime or string)
    115          po_revision_date can be None (form), False (=pot_creation_date), True (=now), or a value (datetime or string)
    116
    117          @return: Dictionary with the header items
    118          @rtype: dict
    119          """
    120          if project_id_version is None:
    121              project_id_version = "PACKAGE VERSION"
    122          if pot_creation_date is None or pot_creation_date == True:
    123              pot_creation_date = time.strftime("%Y-%m-%d %H:%M") + tzstring()
    124          if isinstance(pot_creation_date, time.struct_time):
    125              pot_creation_date = time.strftime("%Y-%m-%d %H:%M", pot_creation_date) + tzstring()
    126          if po_revision_date is None:
    127              po_revision_date = "YEAR-MO-DA HO:MI+ZONE"
    128          elif po_revision_date == False:
    129              po_revision_date = pot_creation_date
    130          elif po_revision_date == True:
    131              po_revision_date = time.strftime("%Y-%m-%d %H:%M") + tzstring()
    132          if isinstance(po_revision_date, time.struct_time):
    133              po_revision_date = time.strftime("%Y-%m-%d %H:%M", po_revision_date) + tzstring()
    134          if last_translator is None:
    135              last_translator = "FULL NAME <EMAIL@ADDRESS>"
    136          if language_team is None:
    137              language_team = "LANGUAGE <LL@li.org>"
    138          if mime_version is None:
    139              mime_version = "1.0"
    140          if report_msgid_bugs_to is None:
    141              report_msgid_bugs_to = ""
    142
    143          defaultargs = dictutils.ordereddict()
    144          defaultargs["Project-Id-Version"] = project_id_version
    145          defaultargs["Report-Msgid-Bugs-To"] = report_msgid_bugs_to
    146          defaultargs["POT-Creation-Date"] = pot_creation_date
    147          defaultargs["PO-Revision-Date"] = po_revision_date
    148          defaultargs["Last-Translator"] = last_translator
    149          defaultargs["Language-Team"] = language_team
    150          defaultargs["MIME-Version"] = mime_version
    151          defaultargs["Content-Type"] = "text/plain; charset=%s" % charset
    152          defaultargs["Content-Transfer-Encoding"] = encoding
    153          if plural_forms:
    154              defaultargs["Plural-Forms"] = plural_forms
    155          defaultargs["X-Generator"] = self.x_generator
    156
    157          return update(defaultargs, add=True, **kwargs)
    158
    159 -    def header(self):
    160          """Returns the header element, or None. Only the first element is allowed
    161          to be a header. Note that this could still return an empty header element,
    162          if present."""
    163          if len(self.units) == 0:
    164              return None
    165          candidate = self.units[0]
    166          if candidate.isheader():
    167              return candidate
    168          else:
    169              return None
    170
    171 -    def parseheader(self):
    172          """Parses the PO header and returns
    173          the interpreted values as a dictionary"""
    174          header = self.header()
    175          if not header:
    176              return {}
    177          return parseheaderstring(header.target)
    178
    179 -    def updateheader(self, add=False, **kwargs):
    180          """Updates the fields in the PO style header.
    181          This will create a header if add == True"""
    182          header = self.header()
    183          if not header:
    184              # FIXME: does not work for xliff files yet
    185              if add and callable(getattr(self, "makeheader", None)):
    186                  header = self.makeheader(**kwargs)
    187                  self.units.insert(0, header)
    188          else:
    189              headeritems = update(self.parseheader(), add, **kwargs)
    190              keys = headeritems.keys()
    191              if not "Content-Type" in keys or "charset=CHARSET" in headeritems["Content-Type"]:
    192                  headeritems["Content-Type"] = "text/plain; charset=UTF-8"
    193              if not "Content-Transfer-Encoding" in keys or "ENCODING" in headeritems["Content-Transfer-Encoding"]:
    194                  headeritems["Content-Transfer-Encoding"] = "8bit"
    195              headerString = ""
    196              for key, value in headeritems.items():
    197                  headerString += "%s: %s\n" % (key, value)
    198              header.target = headerString
    199              header.markfuzzy(False)    # TODO: check why we do this?
    200          return header
    201
    202 -    def getheaderplural(self):
    203          """returns the nplural and plural values from the header"""
    204          header = self.parseheader()
    205          pluralformvalue = header.get('Plural-Forms', None)
    206          if pluralformvalue is None:
    207              return None, None
    208          nplural = re.findall("nplurals=(.+?);", pluralformvalue)
    209          plural = re.findall("plural=(.+?);?$", pluralformvalue)
    210          if not nplural or nplural[0] == "INTEGER":
    211              nplural = None
    212          else:
    213              nplural = nplural[0]
    214          if not plural or plural[0] == "EXPRESSION":
    215              plural = None
    216          else:
    217              plural = plural[0]
    218          return nplural, plural
    219
    220 -    def updateheaderplural(self, nplurals, plural):
    221          """update the Plural-Form PO header"""
    222          if isinstance(nplurals, basestring):
    223              nplurals = int(nplurals)
    224          self.updateheader( Plural_Forms = "nplurals=%d; plural=%s;" % (nplurals, plural) )
    225
    226 -    def mergeheaders(self, otherstore):
    227          """Merges another header with this header.
    228
    229          This header is assumed to be the template.
    230
    231          @type otherstore: L{base.TranslationStore}
    232
    233          """
    234
    235          newvalues = otherstore.parseheader()
    236          retain = {
    237                  "Project_Id_Version": newvalues['Project-Id-Version'],
    238                  "PO_Revision_Date"  : newvalues['PO-Revision-Date'],
    239                  "Last_Translator"   : newvalues['Last-Translator'],
    240                  "Language_Team"     : newvalues['Language-Team'],
    241          }
    242          # not necessarily there:
    243          plurals = newvalues.get('Plural-Forms', None)
    244          if plurals:
    245              retain['Plural-Forms'] = plurals
    246          self.updateheader(**retain)
    247
    248 -    def updatecontributor(self, name, email=None):
    249          """Add contribution comments
    250          """
    251          header = self.header()
    252          if not header:
    253              return
    254          prelines = []
    255          contriblines = []
    256          postlines = []
    257          contribexists = False
    258          incontrib = False
    259          outcontrib = False
    260          for line in header.getnotes("translator").split('\n'):
    261              line = line.strip()
    262              if line == u"FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.":
    263                  incontrib = True
    264                  continue
    265              if author_re.match(line):
    266                  incontrib = True
    267                  contriblines.append(line)
    268                  continue
    269              if line == "" and incontrib:
    270                  incontrib = False
    271                  outcontrib = True
    272              if incontrib:
    273                  contriblines.append(line)
    274              elif not outcontrib:
    275                  prelines.append(line)
    276              else:
    277                  postlines.append(line)
    278
    279          year = time.strftime("%Y")
    280          contribexists = False
    281          for line in contriblines:
    282              if name in line and (email is None or email in line):
    283                  contribexists = True
    284                  break
    285          if not contribexists:
    286              # Add a new contributor
    287              if email:
    288                  contriblines.append("%s <%s>, %s" % (name, email, year))
    289              else:
    290                  contriblines.append("%s, %s" % (name, year))
    291
    292          header.removenotes()
    293          header.addnote("\n".join(prelines))
    294          header.addnote("\n".join(contriblines))
    295          header.addnote("\n".join(postlines))
    296