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