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