#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2002-2009 Zuza Software Foundation # # This file is part of the Translate Toolkit. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . """class that handles all header functions for a header in a po file""" from translate.misc import dictutils from translate import __version__ import re import time author_re = re.compile(r".*<\S+@\S+>.*\d{4,4}") def parseheaderstring(input): """Parses an input string with the definition of a PO header and returns the interpreted values as a dictionary.""" headervalues = dictutils.ordereddict() for line in input.split("\n"): if not line or ":" not in line: continue key, value = line.split(":", 1) #We don't want unicode keys key = str(key.strip()) headervalues[key] = value.strip() return headervalues def tzstring(): """Returns the timezone as a string in the format [+-]0000, eg +0200. @rtype: str""" if time.daylight: tzoffset = time.altzone else: tzoffset = time.timezone hours, minutes = time.gmtime(abs(tzoffset))[3:5] if tzoffset > 0: hours *= -1 tz = str("%+d" % hours).zfill(3) + str(minutes).zfill(2) return tz def update(existing, add=False, **kwargs): """Update an existing header dictionary with the values in kwargs, adding new values only if add is true. @return: Updated dictionary of header entries @rtype: dict """ headerargs = dictutils.ordereddict() fixedargs = dictutils.cidict() for key, value in kwargs.items(): key = key.replace("_", "-") if key.islower(): key = key.title() fixedargs[key] = value removed = [] for key in poheader.header_order: if existing.has_key(key): if key in fixedargs: headerargs[key] = fixedargs.pop(key) else: headerargs[key] = existing[key] removed.append(key) elif add and fixedargs.has_key(key): headerargs[key] = fixedargs.pop(key) for key, value in existing.iteritems(): if not key in removed: headerargs[key] = value if add: for key in fixedargs: headerargs[key] = fixedargs[key] return headerargs class poheader(object): """This class implements functionality for manipulation of po file headers. This class is a mix-in class and useless on its own. It must be used from all classes which represent a po file""" x_generator = "Translate Toolkit %s" % __version__.sver header_order = [ "Project-Id-Version", "Report-Msgid-Bugs-To", "POT-Creation-Date", "PO-Revision-Date", "Last-Translator", "Language-Team", "Language", "MIME-Version", "Content-Type", "Content-Transfer-Encoding", "Plural-Forms", "X-Generator", ] 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): """Create a header dictionary with useful defaults. pot_creation_date can be None (current date) or a value (datetime or string) po_revision_date can be None (form), False (=pot_creation_date), True (=now), or a value (datetime or string) @return: Dictionary with the header items @rtype: dict """ if project_id_version is None: project_id_version = "PACKAGE VERSION" if pot_creation_date is None or pot_creation_date == True: pot_creation_date = time.strftime("%Y-%m-%d %H:%M") + tzstring() if isinstance(pot_creation_date, time.struct_time): pot_creation_date = time.strftime("%Y-%m-%d %H:%M", pot_creation_date) + tzstring() if po_revision_date is None: po_revision_date = "YEAR-MO-DA HO:MI+ZONE" elif po_revision_date == False: po_revision_date = pot_creation_date elif po_revision_date == True: po_revision_date = time.strftime("%Y-%m-%d %H:%M") + tzstring() if isinstance(po_revision_date, time.struct_time): po_revision_date = time.strftime("%Y-%m-%d %H:%M", po_revision_date) + tzstring() if last_translator is None: last_translator = "FULL NAME " if language_team is None: language_team = "LANGUAGE " if mime_version is None: mime_version = "1.0" if report_msgid_bugs_to is None: report_msgid_bugs_to = "" defaultargs = dictutils.ordereddict() defaultargs["Project-Id-Version"] = project_id_version defaultargs["Report-Msgid-Bugs-To"] = report_msgid_bugs_to defaultargs["POT-Creation-Date"] = pot_creation_date defaultargs["PO-Revision-Date"] = po_revision_date defaultargs["Last-Translator"] = last_translator defaultargs["Language-Team"] = language_team defaultargs["MIME-Version"] = mime_version defaultargs["Content-Type"] = "text/plain; charset=%s" % charset defaultargs["Content-Transfer-Encoding"] = encoding if plural_forms: defaultargs["Plural-Forms"] = plural_forms defaultargs["X-Generator"] = self.x_generator return update(defaultargs, add=True, **kwargs) def header(self): """Returns the header element, or None. Only the first element is allowed to be a header. Note that this could still return an empty header element, if present.""" if len(self.units) == 0: return None candidate = self.units[0] if candidate.isheader(): return candidate else: return None def parseheader(self): """Parses the PO header and returns the interpreted values as a dictionary.""" header = self.header() if not header: return {} return parseheaderstring(header.target) def updateheader(self, add=False, **kwargs): """Updates the fields in the PO style header. This will create a header if add == True.""" header = self.header() if not header: if add: header = self.makeheader(**kwargs) # we should be using .addunit() or some equivalent in case the # unit needs to refer back to the store, etc. This might be # subtly broken for POXLIFF, since we don't dupliate the code # from lisa::addunit(). header._store = self self.units.insert(0, header) else: headeritems = update(self.parseheader(), add, **kwargs) keys = headeritems.keys() if not "Content-Type" in keys or "charset=CHARSET" in headeritems["Content-Type"]: headeritems["Content-Type"] = "text/plain; charset=UTF-8" if not "Content-Transfer-Encoding" in keys or "ENCODING" in headeritems["Content-Transfer-Encoding"]: headeritems["Content-Transfer-Encoding"] = "8bit" headerString = "" for key, value in headeritems.items(): if value is not None: headerString += "%s: %s\n" % (key, value) header.target = headerString header.markfuzzy(False) # TODO: check why we do this? return header def getheaderplural(self): """Returns the nplural and plural values from the header.""" header = self.parseheader() pluralformvalue = header.get('Plural-Forms', None) if pluralformvalue is None: return None, None nplural = re.findall("nplurals=(.+?);", pluralformvalue) plural = re.findall("plural=(.+?);?$", pluralformvalue) if not nplural or nplural[0] == "INTEGER": nplural = None else: nplural = nplural[0] if not plural or plural[0] == "EXPRESSION": plural = None else: plural = plural[0] return nplural, plural def updateheaderplural(self, nplurals, plural): """Update the Plural-Form PO header.""" if isinstance(nplurals, basestring): nplurals = int(nplurals) self.updateheader(add=True, Plural_Forms = "nplurals=%d; plural=%s;" % (nplurals, plural) ) def gettargetlanguage(self): """Return the target language if specified in the header. Some attempt at understanding Poedit's custom headers is done.""" header = self.parseheader() if 'X-Poedit-Language' in header: from translate.lang import poedit language = header.get('X-Poedit-Language') country = header.get('X-Poedit-Country') return poedit.isocode(language, country) return header.get('Language') def settargetlanguage(self, lang): """Set the target language in the header. This removes any custom Poedit headers if they exist. @param lang: the new target language code @type lang: str """ if isinstance(lang, basestring) and len(lang) > 1: self.updateheader(add=True, Language=lang, X_Poedit_Language=None, X_Poedit_Country=None) def mergeheaders(self, otherstore): """Merges another header with this header. This header is assumed to be the template. @type otherstore: L{base.TranslationStore} """ newvalues = otherstore.parseheader() retain = { "Project_Id_Version": newvalues['Project-Id-Version'], "PO_Revision_Date" : newvalues['PO-Revision-Date'], "Last_Translator" : newvalues['Last-Translator'], "Language_Team" : newvalues['Language-Team'], } # not necessarily there: plurals = newvalues.get('Plural-Forms', None) if plurals: retain['Plural-Forms'] = plurals self.updateheader(**retain) def updatecontributor(self, name, email=None): """Add contribution comments if necessary.""" header = self.header() if not header: return prelines = [] contriblines = [] postlines = [] contribexists = False incontrib = False outcontrib = False for line in header.getnotes("translator").split('\n'): line = line.strip() if line == u"FIRST AUTHOR , YEAR.": incontrib = True continue if author_re.match(line): incontrib = True contriblines.append(line) continue if line == "" and incontrib: incontrib = False outcontrib = True if incontrib: contriblines.append(line) elif not outcontrib: prelines.append(line) else: postlines.append(line) year = time.strftime("%Y") contribexists = False for i in range(len(contriblines)): line = contriblines[i] if name in line and (email is None or email in line): contribexists = True if year in line: break else: #The contributor is there, but not for this year if line[-1] == '.': line = line[:-1] contriblines[i] = "%s, %s." % (line, year) if not contribexists: # Add a new contributor if email: contriblines.append("%s <%s>, %s." % (name, email, year)) else: contriblines.append("%s, %s." % (name, year)) header.removenotes() header.addnote("\n".join(prelines)) header.addnote("\n".join(contriblines)) header.addnote("\n".join(postlines)) def makeheader(self, **kwargs): """Create a header for the given filename. Check .makeheaderdict() for information on parameters.""" headerpo = self.UnitClass(encoding=self._encoding) headerpo.markfuzzy() headerpo.source = "" headeritems = self.makeheaderdict(**kwargs) headervalue = "" for (key, value) in headeritems.items(): headervalue += "%s: %s\n" % (key, value) headerpo.target = headervalue return headerpo