Package translate :: Package storage :: Module base
[hide private]
[frames] | no frames]

Source Code for Module translate.storage.base

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  #  
  4  # Copyright 2006-2008 Zuza Software Foundation 
  5  #  
  6  # This file is part of translate. 
  7  # 
  8  # translate 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  # translate 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 translate; if not, write to the Free Software 
 20  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 21   
 22  """Base classes for storage interfaces. 
 23   
 24  @organization: Zuza Software Foundation 
 25  @copyright: 2006-2007 Zuza Software Foundation 
 26  @license: U{GPL <http://www.fsf.org/licensing/licenses/gpl.html>} 
 27  """ 
 28   
 29  try: 
 30      import cPickle as pickle 
 31  except: 
 32      import pickle 
 33  from exceptions import NotImplementedError 
 34   
35 -def force_override(method, baseclass):
36 """Forces derived classes to override method.""" 37 38 if type(method.im_self) == type(baseclass): 39 # then this is a classmethod and im_self is the actual class 40 actualclass = method.im_self 41 else: 42 actualclass = method.im_class 43 if actualclass != baseclass: 44 raise NotImplementedError("%s does not reimplement %s as required by %s" % (actualclass.__name__, method.__name__, baseclass.__name__))
45
46 -class ParseError(Exception):
47 pass
48
49 -class TranslationUnit(object):
50 """Base class for translation units. 51 52 Our concept of a I{translation unit} is influenced heavily by XLIFF: 53 U{http://www.oasis-open.org/committees/xliff/documents/xliff-specification.htm} 54 55 As such most of the method- and variable names borrows from XLIFF terminology. 56 57 A translation unit consists of the following: 58 - A I{source} string. This is the original translatable text. 59 - A I{target} string. This is the translation of the I{source}. 60 - Zero or more I{notes} on the unit. Notes would typically be some 61 comments from a translator on the unit, or some comments originating from 62 the source code. 63 - Zero or more I{locations}. Locations indicate where in the original 64 source code this unit came from. 65 - Zero or more I{errors}. Some tools (eg. L{pofilter <filters.pofilter>}) can run checks on 66 translations and produce error messages. 67 68 @group Source: *source* 69 @group Target: *target* 70 @group Notes: *note* 71 @group Locations: *location* 72 @group Errors: *error* 73 """ 74
75 - def __init__(self, source):
76 """Constructs a TranslationUnit containing the given source string.""" 77 78 self._store = None 79 self.source = source 80 self.target = None 81 self.notes = "" 82 super(TranslationUnit, self).__init__()
83
84 - def __eq__(self, other):
85 """Compares two TranslationUnits. 86 87 @type other: L{TranslationUnit} 88 @param other: Another L{TranslationUnit} 89 @rtype: Boolean 90 @return: Returns True if the supplied TranslationUnit equals this unit. 91 92 """ 93 94 return self.source == other.source and self.target == other.target
95
96 - def settarget(self, target):
97 """Sets the target string to the given value.""" 98 99 self.target = target
100
101 - def gettargetlen(self):
102 """Returns the length of the target string. 103 104 @note: Plural forms might be combined. 105 @rtype: Integer 106 107 """ 108 109 length = len(self.target or "") 110 strings = getattr(self.target, "strings", []) 111 if strings: 112 length += sum([len(pluralform) for pluralform in strings[1:]]) 113 return length
114
115 - def getid(self):
116 """A unique identifier for this unit. 117 118 @rtype: string 119 @return: an identifier for this unit that is unique in the store 120 121 Derived classes should override this in a way that guarantees a unique 122 identifier for each unit in the store. 123 """ 124 return self.source
125
126 - def getlocations(self):
127 """A list of source code locations. 128 129 @note: Shouldn't be implemented if the format doesn't support it. 130 @rtype: List 131 132 """ 133 134 return []
135
136 - def addlocation(self, location):
137 """Add one location to the list of locations. 138 139 @note: Shouldn't be implemented if the format doesn't support it. 140 141 """ 142 pass
143
144 - def addlocations(self, location):
145 """Add a location or a list of locations. 146 147 @note: Most classes shouldn't need to implement this, 148 but should rather implement L{addlocation()}. 149 @warning: This method might be removed in future. 150 151 """ 152 153 if isinstance(location, list): 154 for item in location: 155 self.addlocation(item) 156 else: 157 self.addlocation(location)
158
159 - def getcontext(self):
160 """Get the message context.""" 161 return ""
162
163 - def getnotes(self, origin=None):
164 """Returns all notes about this unit. 165 166 It will probably be freeform text or something reasonable that can be 167 synthesised by the format. 168 It should not include location comments (see L{getlocations()}). 169 170 """ 171 return getattr(self, "notes", "")
172
173 - def addnote(self, text, origin=None):
174 """Adds a note (comment). 175 176 @type text: string 177 @param text: Usually just a sentence or two. 178 @type origin: string 179 @param origin: Specifies who/where the comment comes from. 180 Origin can be one of the following text strings: 181 - 'translator' 182 - 'developer', 'programmer', 'source code' (synonyms) 183 184 """ 185 if getattr(self, "notes", None): 186 self.notes += '\n'+text 187 else: 188 self.notes = text
189
190 - def removenotes(self):
191 """Remove all the translator's notes.""" 192 193 self.notes = u''
194
195 - def adderror(self, errorname, errortext):
196 """Adds an error message to this unit. 197 198 @type errorname: string 199 @param errorname: A single word to id the error. 200 @type errortext: string 201 @param errortext: The text describing the error. 202 203 """ 204 205 pass
206
207 - def geterrors(self):
208 """Get all error messages. 209 210 @rtype: Dictionary 211 212 """ 213 214 return {}
215
216 - def markreviewneeded(self, needsreview=True, explanation=None):
217 """Marks the unit to indicate whether it needs review. 218 219 @keyword needsreview: Defaults to True. 220 @keyword explanation: Adds an optional explanation as a note. 221 222 """ 223 224 pass
225
226 - def istranslated(self):
227 """Indicates whether this unit is translated. 228 229 This should be used rather than deducing it from .target, 230 to ensure that other classes can implement more functionality 231 (as XLIFF does). 232 233 """ 234 235 return bool(self.target) and not self.isfuzzy()
236
237 - def istranslatable(self):
238 """Indicates whether this unit can be translated. 239 240 This should be used to distinguish real units for translation from 241 header, obsolete, binary or other blank units. 242 """ 243 return True
244
245 - def isfuzzy(self):
246 """Indicates whether this unit is fuzzy.""" 247 248 return False
249
250 - def markfuzzy(self, value=True):
251 """Marks the unit as fuzzy or not.""" 252 pass
253
254 - def isheader(self):
255 """Indicates whether this unit is a header.""" 256 257 return False
258
259 - def isreview(self):
260 """Indicates whether this unit needs review.""" 261 return False
262 263
264 - def isblank(self):
265 """Used to see if this unit has no source or target string. 266 267 @note: This is probably used more to find translatable units, 268 and we might want to move in that direction rather and get rid of this. 269 270 """ 271 272 return not (self.source or self.target)
273
274 - def hasplural(self):
275 """Tells whether or not this specific unit has plural strings.""" 276 277 #TODO: Reconsider 278 return False
279
280 - def getsourcelanguage(self):
281 return getattr(self._store, "sourcelanguage", "en")
282
283 - def gettargetlanguage(self):
284 return getattr(self._store, "targetlanguage", None)
285
286 - def merge(self, otherunit, overwrite=False, comments=True):
287 """Do basic format agnostic merging.""" 288 289 if self.target == "" or overwrite: 290 self.target = otherunit.target
291
292 - def unit_iter(self):
293 """Iterator that only returns this unit.""" 294 yield self
295
296 - def getunits(self):
297 """This unit in a list.""" 298 return [self]
299
300 - def buildfromunit(cls, unit):
301 """Build a native unit from a foreign unit, preserving as much 302 information as possible.""" 303 304 if type(unit) == cls and hasattr(unit, "copy") and callable(unit.copy): 305 return unit.copy() 306 newunit = cls(unit.source) 307 newunit.target = unit.target 308 newunit.markfuzzy(unit.isfuzzy()) 309 locations = unit.getlocations() 310 if locations: 311 newunit.addlocations(locations) 312 notes = unit.getnotes() 313 if notes: 314 newunit.addnote(notes) 315 return newunit
316 buildfromunit = classmethod(buildfromunit)
317
318 -class TranslationStore(object):
319 """Base class for stores for multiple translation units of type UnitClass.""" 320 321 UnitClass = TranslationUnit 322 Mimetypes = None 323 Extensions = None 324
325 - def __init__(self, unitclass=None):
326 """Constructs a blank TranslationStore.""" 327 328 self.units = [] 329 self.filepath = None 330 self.translator = "" 331 self.date = "" 332 self.sourcelanguage = None 333 self.targetlanguage = None 334 if unitclass: 335 self.UnitClass = unitclass 336 super(TranslationStore, self).__init__()
337
338 - def setsourcelanguage(self, sourcelanguage):
339 """Sets the source language for this store""" 340 self.sourcelanguage = sourcelanguage
341
342 - def settargetlanguage(self, targetlanguage):
343 """Sets the target language for this store""" 344 self.targetlanguage = targetlanguage
345
346 - def unit_iter(self):
347 """Iterator over all the units in this store.""" 348 for unit in self.units: 349 yield unit
350
351 - def getunits(self):
352 """Return a list of all units in this store.""" 353 return [unit for unit in self.unit_iter()]
354
355 - def addunit(self, unit):
356 """Appends the given unit to the object's list of units. 357 358 This method should always be used rather than trying to modify the 359 list manually. 360 361 @type unit: L{TranslationUnit} 362 @param unit: The unit that will be added. 363 364 """ 365 unit._store = self 366 self.units.append(unit)
367
368 - def addsourceunit(self, source):
369 """Adds and returns a new unit with the given source string. 370 371 @rtype: L{TranslationUnit} 372 373 """ 374 375 unit = self.UnitClass(source) 376 self.addunit(unit) 377 return unit
378
379 - def findunit(self, source):
380 """Finds the unit with the given source string. 381 382 @rtype: L{TranslationUnit} or None 383 384 """ 385 386 if len(getattr(self, "sourceindex", [])): 387 if source in self.sourceindex: 388 return self.sourceindex[source] 389 else: 390 for unit in self.units: 391 if unit.source == source: 392 return unit 393 return None
394
395 - def translate(self, source):
396 """Returns the translated string for a given source string. 397 398 @rtype: String or None 399 400 """ 401 402 unit = self.findunit(source) 403 if unit and unit.target: 404 return unit.target 405 else: 406 return None
407
408 - def makeindex(self):
409 """Indexes the items in this store. At least .sourceindex should be usefull.""" 410 411 self.locationindex = {} 412 self.sourceindex = {} 413 for unit in self.units: 414 # Do we need to test if unit.source exists? 415 self.sourceindex[unit.source] = unit 416 if unit.hasplural(): 417 for nounform in unit.source.strings[1:]: 418 self.sourceindex[nounform] = unit 419 for location in unit.getlocations(): 420 if location in self.locationindex: 421 # if sources aren't unique, don't use them 422 self.locationindex[location] = None 423 else: 424 self.locationindex[location] = unit
425
426 - def __str__(self):
427 """Converts to a string representation that can be parsed back using L{parsestring()}.""" 428 429 # We can't pickle fileobj if it is there, so let's hide it for a while. 430 fileobj = getattr(self, "fileobj", None) 431 self.fileobj = None 432 dump = pickle.dumps(self) 433 self.fileobj = fileobj 434 return dump
435
436 - def isempty(self):
437 """Returns True if the object doesn't contain any translation units.""" 438 439 if len(self.units) == 0: 440 return True 441 for unit in self.units: 442 if not (unit.isblank() or unit.isheader()): 443 return False 444 return True
445
446 - def _assignname(self):
447 """Tries to work out what the name of the filesystem file is and 448 assigns it to .filename.""" 449 fileobj = getattr(self, "fileobj", None) 450 if fileobj: 451 filename = getattr(fileobj, "name", getattr(fileobj, "filename", None)) 452 if filename: 453 self.filename = filename
454
455 - def parsestring(cls, storestring):
456 """Converts the string representation back to an object.""" 457 newstore = cls() 458 if storestring: 459 newstore.parse(storestring) 460 return newstore
461 parsestring = classmethod(parsestring) 462
463 - def parse(self, data):
464 """parser to process the given source string""" 465 self.units = pickle.loads(data).units
466
467 - def savefile(self, storefile):
468 """Writes the string representation to the given file (or filename).""" 469 if isinstance(storefile, basestring): 470 storefile = open(storefile, "w") 471 self.fileobj = storefile 472 self._assignname() 473 storestring = str(self) 474 storefile.write(storestring) 475 storefile.close()
476
477 - def save(self):
478 """Save to the file that data was originally read from, if available.""" 479 fileobj = getattr(self, "fileobj", None) 480 if not fileobj: 481 filename = getattr(self, "filename", None) 482 if filename: 483 fileobj = file(filename, "w") 484 else: 485 fileobj.close() 486 filename = getattr(fileobj, "name", getattr(fileobj, "filename", None)) 487 if not filename: 488 raise ValueError("No file or filename to save to") 489 fileobj = fileobj.__class__(filename, "w") 490 self.savefile(fileobj)
491
492 - def parsefile(cls, storefile):
493 """Reads the given file (or opens the given filename) and parses back to an object.""" 494 495 if isinstance(storefile, basestring): 496 storefile = open(storefile, "r") 497 mode = getattr(storefile, "mode", "r") 498 #For some reason GzipFile returns 1, so we have to test for that here 499 if mode == 1 or "r" in mode: 500 storestring = storefile.read() 501 storefile.close() 502 else: 503 storestring = "" 504 newstore = cls.parsestring(storestring) 505 newstore.fileobj = storefile 506 newstore._assignname() 507 return newstore
508 parsefile = classmethod(parsefile)
509