1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18   
 19   
 20   
 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   
 36      """Forces derived classes to override method.""" 
 37   
 38      if type(method.im_self) == type(baseclass): 
 39           
 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   
 47      """Base class for translation units. 
 48       
 49      Our concept of a I{translation unit} is influenced heavily by XLIFF: 
 50      U{http://www.oasis-open.org/committees/xliff/documents/xliff-specification.htm} 
 51   
 52      As such most of the method- and variable names borrows from XLIFF terminology. 
 53   
 54      A translation unit consists of the following: 
 55        - A I{source} string. This is the original translatable text. 
 56        - A I{target} string. This is the translation of the I{source}. 
 57        - Zero or more I{notes} on the unit. Notes would typically be some 
 58          comments from a translator on the unit, or some comments originating from 
 59          the source code. 
 60        - Zero or more I{locations}. Locations indicate where in the original 
 61          source code this unit came from. 
 62        - Zero or more I{errors}. Some tools (eg. L{pofilter <filters.pofilter>}) can run checks on 
 63          translations and produce error messages. 
 64   
 65      @group Source: *source* 
 66      @group Target: *target* 
 67      @group Notes: *note* 
 68      @group Locations: *location* 
 69      @group Errors: *error* 
 70      """ 
 71   
 79   
 81          """Compares two TranslationUnits. 
 82           
 83          @type other: L{TranslationUnit} 
 84          @param other: Another L{TranslationUnit} 
 85          @rtype: Boolean 
 86          @return: Returns True if the supplied TranslationUnit equals this unit. 
 87           
 88          """ 
 89   
 90          return self.source == other.source and self.target == other.target 
  91   
 93          """Sets the target string to the given value.""" 
 94   
 95          self.target = target 
  96   
 98          """Returns the length of the target string. 
 99           
100          @note: Plural forms might be combined. 
101          @rtype: Integer 
102           
103          """ 
104   
105          length = len(self.target or "") 
106          strings = getattr(self.target, "strings", []) 
107          if strings: 
108              length += sum([len(pluralform) for pluralform in strings[1:]]) 
109          return length 
 110   
112          """A unique identifier for this unit. 
113   
114          @rtype: string 
115          @return: an identifier for this unit that is unique in the store 
116   
117          Derived classes should override this in a way that guarantees a unique 
118          identifier for each unit in the store. 
119          """ 
120          return self.source 
 121   
123          """A list of source code locations. 
124           
125          @note: Shouldn't be implemented if the format doesn't support it. 
126          @rtype: List 
127           
128          """ 
129   
130          return [] 
 131       
133          """Add one location to the list of locations. 
134           
135          @note: Shouldn't be implemented if the format doesn't support it. 
136           
137          """ 
138          pass 
 139   
141          """Add a location or a list of locations. 
142           
143          @note: Most classes shouldn't need to implement this, 
144                 but should rather implement L{addlocation()}. 
145          @warning: This method might be removed in future. 
146           
147          """ 
148   
149          if isinstance(location, list): 
150              for item in location: 
151                  self.addlocation(item) 
152          else: 
153              self.addlocation(location) 
 154   
155 -    def getcontext(self): 
 156          """Get the message context.""" 
157          return "" 
 158       
160          """Returns all notes about this unit. 
161           
162          It will probably be freeform text or something reasonable that can be 
163          synthesised by the format. 
164          It should not include location comments (see L{getlocations()}). 
165           
166          """ 
167          return getattr(self, "notes", "") 
 168   
169 -    def addnote(self, text, origin=None): 
 170          """Adds a note (comment).  
171   
172          @type text: string 
173          @param text: Usually just a sentence or two. 
174          @type origin: string 
175          @param origin: Specifies who/where the comment comes from. 
176                         Origin can be one of the following text strings: 
177                           - 'translator' 
178                           - 'developer', 'programmer', 'source code' (synonyms) 
179   
180          """ 
181          if getattr(self, "notes", None): 
182              self.notes += '\n'+text 
183          else: 
184              self.notes = text 
 185   
187          """Remove all the translator's notes.""" 
188   
189          self.notes = u'' 
 190   
191 -    def adderror(self, errorname, errortext): 
 192          """Adds an error message to this unit. 
193           
194            @type errorname: string 
195            @param errorname: A single word to id the error. 
196            @type errortext: string 
197            @param errortext: The text describing the error. 
198           
199          """ 
200   
201          pass 
 202   
204          """Get all error messages. 
205           
206          @rtype: Dictionary 
207           
208          """ 
209   
210          return {} 
 211   
213          """Marks the unit to indicate whether it needs review. 
214           
215          @keyword needsreview: Defaults to True. 
216          @keyword explanation: Adds an optional explanation as a note. 
217           
218          """ 
219   
220          pass 
 221   
223          """Indicates whether this unit is translated. 
224           
225          This should be used rather than deducing it from .target, 
226          to ensure that other classes can implement more functionality 
227          (as XLIFF does). 
228           
229          """ 
230   
231          return bool(self.target) and not self.isfuzzy() 
 232   
234          """Indicates whether this unit can be translated. 
235   
236          This should be used to distinguish real units for translation from 
237          header, obsolete, binary or other blank units. 
238          """ 
239          return True 
 240   
242          """Indicates whether this unit is fuzzy.""" 
243   
244          return False 
 245   
247          """Marks the unit as fuzzy or not.""" 
248          pass 
 249   
251          """Indicates whether this unit is a header.""" 
252   
253          return False 
 254   
256          """Indicates whether this unit needs review.""" 
257          return False 
 258   
259   
261          """Used to see if this unit has no source or target string. 
262           
263          @note: This is probably used more to find translatable units, 
264          and we might want to move in that direction rather and get rid of this. 
265           
266          """ 
267   
268          return not (self.source or self.target) 
 269   
271          """Tells whether or not this specific unit has plural strings.""" 
272   
273           
274          return False 
 275   
276 -    def merge(self, otherunit, overwrite=False, comments=True): 
 277          """Do basic format agnostic merging.""" 
278   
279          if self.target == "" or overwrite: 
280              self.target = otherunit.target 
 281   
283          """Iterator that only returns this unit.""" 
284          yield self 
 285   
287          """This unit in a list.""" 
288          return [self] 
 289   
291          """Build a native unit from a foreign unit, preserving as much   
292          information as possible.""" 
293   
294          if type(unit) == cls and hasattr(unit, "copy") and callable(unit.copy): 
295              return unit.copy() 
296          newunit = cls(unit.source) 
297          newunit.target = unit.target 
298          newunit.markfuzzy(unit.isfuzzy()) 
299          locations = unit.getlocations() 
300          if locations:  
301              newunit.addlocations(locations) 
302          notes = unit.getnotes() 
303          if notes:  
304              newunit.addnote(notes) 
305          return newunit 
 306      buildfromunit = classmethod(buildfromunit) 
 307   
309      """Base class for stores for multiple translation units of type UnitClass.""" 
310   
311      UnitClass = TranslationUnit 
312   
314          """Constructs a blank TranslationStore.""" 
315   
316          self.units = [] 
317          self.filepath = None 
318          self.translator = "" 
319          self.date = "" 
320          if unitclass: 
321              self.UnitClass = unitclass 
322          super(TranslationStore, self).__init__() 
 323   
325          """Iterator over all the units in this store.""" 
326          for unit in self.units: 
327              yield unit 
 328   
330          """Return a list of all units in this store.""" 
331          return [unit for unit in self.unit_iter()] 
 332   
334          """Appends the given unit to the object's list of units. 
335           
336          This method should always be used rather than trying to modify the 
337          list manually. 
338   
339          @type unit: L{TranslationUnit} 
340          @param unit: The unit that will be added. 
341           
342          """ 
343   
344          self.units.append(unit) 
 345   
347          """Adds and returns a new unit with the given source string. 
348           
349          @rtype: L{TranslationUnit} 
350   
351          """ 
352   
353          unit = self.UnitClass(source) 
354          self.addunit(unit) 
355          return unit 
 356   
358          """Finds the unit with the given source string. 
359           
360          @rtype: L{TranslationUnit} or None 
361   
362          """ 
363   
364          if len(getattr(self, "sourceindex", [])): 
365              if source in self.sourceindex: 
366                  return self.sourceindex[source] 
367          else: 
368              for unit in self.units: 
369                  if unit.source == source: 
370                      return unit 
371          return None 
 372   
374          """Returns the translated string for a given source string. 
375           
376          @rtype: String or None 
377   
378          """ 
379   
380          unit = self.findunit(source) 
381          if unit and unit.target: 
382              return unit.target 
383          else: 
384              return None 
 385   
387          """Indexes the items in this store. At least .sourceindex should be usefull.""" 
388   
389          self.locationindex = {} 
390          self.sourceindex = {} 
391          for unit in self.units: 
392               
393              self.sourceindex[unit.source] = unit 
394              if unit.hasplural(): 
395                  for nounform in unit.source.strings[1:]: 
396                      self.sourceindex[nounform] = unit 
397              for location in unit.getlocations(): 
398                  if location in self.locationindex: 
399                       
400                      self.locationindex[location] = None 
401                  else: 
402                      self.locationindex[location] = unit 
 403   
405          """Converts to a string representation that can be parsed back using L{parsestring()}.""" 
406   
407           
408          fileobj = getattr(self, "fileobj", None) 
409          self.fileobj = None 
410          dump = pickle.dumps(self) 
411          self.fileobj = fileobj 
412          return dump 
 413   
415          """Returns True if the object doesn't contain any translation units.""" 
416   
417          if len(self.units) == 0: 
418              return True 
419          for unit in self.units: 
420              if not (unit.isblank() or unit.isheader()): 
421                  return False 
422          return True 
 423   
425          """Tries to work out what the name of the filesystem file is and  
426          assigns it to .filename.""" 
427          fileobj = getattr(self, "fileobj", None) 
428          if fileobj: 
429              filename = getattr(fileobj, "name", getattr(fileobj, "filename", None)) 
430              if filename: 
431                  self.filename = filename 
 432   
434          """Converts the string representation back to an object.""" 
435          newstore = cls() 
436          if storestring: 
437              newstore.parse(storestring) 
438          return newstore 
 439      parsestring = classmethod(parsestring) 
440   
442          """parser to process the given source string""" 
443          self.units = pickle.loads(data).units 
 444   
446          """Writes the string representation to the given file (or filename).""" 
447          if isinstance(storefile, basestring): 
448              storefile = open(storefile, "w") 
449          self.fileobj = storefile 
450          self._assignname() 
451          storestring = str(self) 
452          storefile.write(storestring) 
453          storefile.close() 
 454   
456          """Save to the file that data was originally read from, if available.""" 
457          fileobj = getattr(self, "fileobj", None) 
458          if not fileobj: 
459              filename = getattr(self, "filename", None) 
460              if filename: 
461                  fileobj = file(filename, "w") 
462          else: 
463              fileobj.close() 
464              filename = getattr(fileobj, "name", getattr(fileobj, "filename", None)) 
465              if not filename: 
466                  raise ValueError("No file or filename to save to") 
467              fileobj = fileobj.__class__(filename, "w") 
468          self.savefile(fileobj) 
 469   
471          """Reads the given file (or opens the given filename) and parses back to an object.""" 
472   
473          if isinstance(storefile, basestring): 
474              storefile = open(storefile, "r") 
475          mode = getattr(storefile, "mode", "r") 
476           
477          if mode == 1 or "r" in mode: 
478              storestring = storefile.read() 
479              storefile.close() 
480          else: 
481              storestring = "" 
482          newstore = cls.parsestring(storestring) 
483          newstore.fileobj = storefile 
484          newstore._assignname() 
485          return newstore 
 486      parsefile = classmethod(parsefile) 
 487