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