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