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