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