Package translate :: Package storage :: Module cpo

[hide private]
[frames] | no frames]

# Source Code for Module translate.storage.cpo

      1  #!/usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3  #
      4  # Copyright 2002-2007 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  """Classes that hold units of .po files (pounit) or entire files (pofile).
     23
     24  Gettext-style .po (or .pot) files are used in translations for KDE, GNOME and
     25  many other projects.
     26
     27  This uses libgettextpo from the gettext package. Any version before 0.17 will
     28  at least cause some subtle bugs or may not work at all. Developers might want
     29  to have a look at gettext-tools/libgettextpo/gettext-po.h from the gettext
     30  package for the public API of the library.
     31  """
     32
     33  from translate.misc.multistring import multistring
     34  from translate.storage import pocommon
     35  from translate.misc import quote
     36  from translate.lang import data
     37  from ctypes import *
     38  import ctypes.util
     39  try:
     40      import cStringIO as StringIO
     41  except ImportError:
     42      import StringIO
     43  import os
     44  import pypo
     45  import re
     46  import sys
     47  import tempfile
     48
     49  lsep = " "
     50  """Seperator for #: entries"""
     51
     52  STRING = c_char_p
     53
     54  # Structures
     55 -class po_message(Structure):
     56      _fields_ = []
     57
     58  # Function prototypes
     59  xerror_prototype = CFUNCTYPE(None, c_int, POINTER(po_message), STRING, c_uint, c_uint, c_int, STRING)
     60  xerror2_prototype = CFUNCTYPE(None, c_int, POINTER(po_message), STRING, c_uint, c_uint, c_int, STRING, POINTER(po_message), STRING, c_uint, c_uint, c_int, STRING)
     61
     62
     63  # Structures (error handler)
     64 -class po_xerror_handler(Structure):
     65      _fields_ = [('xerror', xerror_prototype),
     66                  ('xerror2', xerror2_prototype)]
     67
     68 -class po_error_handler(Structure):
     69      _fields_ = [
     70      ('error', CFUNCTYPE(None, c_int, c_int, STRING)),
     71      ('error_at_line', CFUNCTYPE(None, c_int, c_int, STRING, c_uint, STRING)),
     72      ('multiline_warning', CFUNCTYPE(None, STRING, STRING)),
     73      ('multiline_error', CFUNCTYPE(None, STRING, STRING)),
     74  ]
     75
     76  # Callback functions for po_xerror_handler
     77 -def xerror_cb(severity, message, filename, lineno, column, multilint_p, message_text):
     78      print >> sys.stderr, "xerror_cb", severity, message, filename, lineno, column, multilint_p, message_text
     79      if severity >= 1:
     80          raise ValueError(message_text)
     81
     82 -def xerror2_cb(severity, message1, filename1, lineno1, column1, multiline_p1, message_text1, message2, filename2, lineno2, column2, multiline_p2, message_text2):
     83      print >> sys.stderr, "xerror2_cb", severity, message1, filename1, lineno1, column1, multiline_p1, message_text1, message2, filename2, lineno2, column2, multiline_p2, message_text2
     84      if severity >= 1:
     85          raise ValueError(message_text1)
     86
     87
     88
     89  # Load libgettextpo
     90  gpo = None
     91  # 'gettextpo' is recognised on Unix, while only 'libgettextpo' is recognised on
     92  # windows. Therefore we test both.
     93  names = ['gettextpo', 'libgettextpo']
     94  for name in names:
     95      lib_location = ctypes.util.find_library(name)
     96      if lib_location:
     97          gpo = cdll.LoadLibrary(lib_location)
     98          if gpo:
     99              break
    100  else:
    101      # Now we are getting desperate, so let's guess a unix type DLL that might
    102      # be in LD_LIBRARY_PATH or loaded with LD_PRELOAD
    103      try:
    104          gpo = cdll.LoadLibrary('libgettextpo.so')
    105      except OSError, e:
    106          raise ImportError("gettext PO library not found")
    107
    108  # Setup return and paramater types
    109  # File access
    110  gpo.po_file_read_v3.argtypes = [STRING, POINTER(po_xerror_handler)]
    111  gpo.po_file_write_v2.argtypes = [c_int, STRING, POINTER(po_xerror_handler)]
    112  gpo.po_file_write_v2.retype = c_int
    113
    114  # Header
    115  gpo.po_file_domain_header.restype = STRING
    116  gpo.po_header_field.restype = STRING
    117  gpo.po_header_field.argtypes = [STRING, STRING]
    118
    119  # Locations (filepos)
    120  gpo.po_filepos_file.restype = STRING
    121  gpo.po_message_filepos.restype = c_int
    122  gpo.po_message_filepos.argtypes = [c_int, c_int]
    123  gpo.po_message_add_filepos.argtypes = [c_int, STRING, c_int]
    124
    125  # Message (get methods)
    126  gpo.po_message_comments.restype = STRING
    127  gpo.po_message_extracted_comments.restype = STRING
    128  gpo.po_message_prev_msgctxt.restype = STRING
    129  gpo.po_message_prev_msgid.restype = STRING
    130  gpo.po_message_prev_msgid_plural.restype = STRING
    131  gpo.po_message_is_format.restype = c_int
    132  gpo.po_message_msgctxt.restype = STRING
    133  gpo.po_message_msgid.restype = STRING
    134  gpo.po_message_msgid_plural.restype = STRING
    135  gpo.po_message_msgstr.restype = STRING
    136  gpo.po_message_msgstr_plural.restype = STRING
    137
    138  # Message (set methods)
    139  gpo.po_message_set_comments.argtypes = [c_int, STRING]
    140  gpo.po_message_set_extracted_comments.argtypes = [c_int, STRING]
    141  gpo.po_message_set_fuzzy.argtypes = [c_int, c_int]
    142  gpo.po_message_set_msgctxt.argtypes = [c_int, STRING]
    143
    144  # Setup the po_xerror_handler
    145  xerror_handler = po_xerror_handler()
    146  xerror_handler.xerror = xerror_prototype(xerror_cb)
    147  xerror_handler.xerror2 = xerror2_prototype(xerror2_cb)
    148
    149 -def escapeforpo(text):
    150      return pypo.escapeforpo(text)
    151
    152 -def quoteforpo(text):
    153      return pypo.quoteforpo(text)
    154
    155 -def unquotefrompo(postr, joinwithlinebreak=False):
    156      return pypo.unquotefrompo(postr, joinwithlinebreak)
    157
    158 -def encodingToUse(encoding):
    159      return pypo.encodingToUse(encoding)
    160
    161 -def get_libgettextpo_version():
    162      """Returns the libgettextpo version
    163
    164      @return: a three-value tuple containing the libgettextpo version in the
    165      following format:
    166          (major version, minor version, subminor version)
    167      """
    168      libversion = c_long.in_dll(gpo, 'libgettextpo_version')
    169      major = libversion.value >> 16
    170      minor = libversion.value >> 8
    171      subminor = libversion.value - (major << 16) - (minor << 8)
    172      return major, minor, subminor
    173
    174
    175 -class pounit(pocommon.pounit):
    176 -    def __init__(self, source=None, encoding='utf-8', gpo_message=None):
    177          self._encoding = encoding
    178          if not gpo_message:
    179              self._gpo_message = gpo.po_message_create()
    180          if source or source == "":
    181              self.source = source
    182              self.target = ""
    183          elif gpo_message:
    184              self._gpo_message = gpo_message
    185
    186 -    def setmsgidcomment(self, msgidcomment):
    187          if msgidcomment:
    188              newsource = "_: " + msgidcomment + "\n" + self.source
    189              self.source = newsource
    190      msgidcomment = property(None, setmsgidcomment)
    191
    192 -    def setmsgid_plural(self, msgid_plural):
    193          if isinstance(msgid_plural, list):
    194              msgid_plural = "".join(msgid_plural)
    195          gpo.po_message_set_msgid_plural(self._gpo_message, msgid_plural)
    196      msgid_plural = property(None, setmsgid_plural)
    197
    198 -    def getsource(self):
    199          def remove_msgid_comments(text):
    200              if not text:
    201                  return text
    202              if text.startswith("_:"):
    203                  remainder = re.search(r"_: .*\n(.*)", text)
    204                  if remainder:
    205                      return remainder.group(1)
    206                  else:
    207                      return u""
    208              else:
    209                  return text
    210          singular = remove_msgid_comments(gpo.po_message_msgid(self._gpo_message))
    211          if singular:
    212              multi = multistring(singular, self._encoding)
    213              if self.hasplural():
    214                  pluralform = gpo.po_message_msgid_plural(self._gpo_message)
    215                  if isinstance(pluralform, str):
    216                      pluralform = pluralform.decode(self._encoding)
    217                  multi.strings.append(pluralform)
    218              return multi
    219          else:
    220              return u""
    221
    222 -    def setsource(self, source):
    223          if isinstance(source, multistring):
    224              source = source.strings
    225          if isinstance(source, unicode):
    226              source = source.encode(self._encoding)
    227          if isinstance(source, list):
    228              gpo.po_message_set_msgid(self._gpo_message, str(source[0]))
    229              if len(source) > 1:
    230                  gpo.po_message_set_msgid_plural(self._gpo_message, str(source[1]))
    231          else:
    232              gpo.po_message_set_msgid(self._gpo_message, source)
    233              gpo.po_message_set_msgid_plural(self._gpo_message, None)
    234
    235      source = property(getsource, setsource)
    236
    237 -    def gettarget(self):
    238          if self.hasplural():
    239              plurals = []
    240              nplural = 0
    241              plural = gpo.po_message_msgstr_plural(self._gpo_message, nplural)
    242              while plural:
    243                  plurals.append(plural)
    244                  nplural += 1
    245                  plural = gpo.po_message_msgstr_plural(self._gpo_message, nplural)
    246              if plurals:
    247                  multi = multistring(plurals, encoding=self._encoding)
    248              else:
    249                  multi = multistring(u"")
    250          else:
    251              multi = multistring(gpo.po_message_msgstr(self._gpo_message) or u"", encoding=self._encoding)
    252          return multi
    253
    254 -    def settarget(self, target):
    255          # for plural strings: convert 'target' into a list
    256          if self.hasplural():
    257              if isinstance(target, multistring):
    258                  target = target.strings
    259              elif isinstance(target, basestring):
    260                  target = [target]
    261          # for non-plurals: check number of items in 'target'
    262          elif isinstance(target,(dict, list)):
    263              if len(target) == 1:
    264                  target = target[0]
    265              else:
    266                  raise ValueError("po msgid element has no plural but msgstr has %d elements (%s)" % (len(target), target))
    267          # empty the previous list of messages
    268          # TODO: the "pypo" implementation does not remove the previous items of
    269          #   the target, if self.target == target (essentially: comparing only
    270          #   the first item of a plural string with the single new string)
    271          #   Maybe this behaviour should be unified.
    272          if isinstance(target, (dict, list)):
    273              i = 0
    274              message = gpo.po_message_msgstr_plural(self._gpo_message, i)
    275              while message is not None:
    276                  gpo.po_message_set_msgstr_plural(self._gpo_message, i, None)
    277                  i += 1
    278                  message = gpo.po_message_msgstr_plural(self._gpo_message, i)
    279          # add the items of a list
    280          if isinstance(target, list):
    281              for i in range(len(target)):
    282                  targetstring = target[i]
    283                  if isinstance(targetstring, unicode):
    284                      targetstring = targetstring.encode(self._encoding)
    285                  gpo.po_message_set_msgstr_plural(self._gpo_message, i, targetstring)
    286          # add the values of a dict
    287          elif isinstance(target, dict):
    288              for i, targetstring in enumerate(target.itervalues()):
    289                  gpo.po_message_set_msgstr_plural(self._gpo_message, i, targetstring)
    290          # add a single string
    291          else:
    292              if isinstance(target, unicode):
    293                  target = target.encode(self._encoding)
    294              if target is None:
    295                  gpo.po_message_set_msgstr(self._gpo_message, "")
    296              else:
    297                  gpo.po_message_set_msgstr(self._gpo_message, target)
    298      target = property(gettarget, settarget)
    299
    300 -    def getid(self):
    301          """The unique identifier for this unit according to the convensions in
    302          .mo files."""
    303          id = gpo.po_message_msgid(self._gpo_message)
    304          # Gettext does not consider the plural to determine duplicates, only
    305          # the msgid. For generation of .mo files, we might want to use this
    306          # code to generate the entry for the hash table, but for now, it is
    307          # commented out for conformance to gettext.
    308  #        plural = gpo.po_message_msgid_plural(self._gpo_message)
    309  #        if not plural is None:
    310  #            id = '%s\0%s' % (id, plural)
    311          context = gpo.po_message_msgctxt(self._gpo_message)
    312          if context:
    313              id = "%s\04%s" % (context, id)
    314          return id or ""
    315
    316 -    def getnotes(self, origin=None):
    317          if origin == None:
    318              comments = gpo.po_message_comments(self._gpo_message) + \
    319                         gpo.po_message_extracted_comments(self._gpo_message)
    320          elif origin == "translator":
    321              comments = gpo.po_message_comments(self._gpo_message)
    322          elif origin in ["programmer", "developer", "source code"]:
    323              comments = gpo.po_message_extracted_comments(self._gpo_message)
    324          else:
    325              raise ValueError("Comment type not valid")
    326
    327          if comments and get_libgettextpo_version() < (0, 17, 0):
    328              comments = "\n".join([line.strip() for line in comments.split("\n")])
    329          # Let's drop the last newline
    330          return comments[:-1].decode(self._encoding)
    331
    332 -    def addnote(self, text, origin=None, position="append"):
    333          # ignore empty strings and strings without non-space characters
    334          if (not text) or (not text.strip()):
    335              return
    336          text = data.forceunicode(text)
    337          oldnotes = self.getnotes(origin)
    338          newnotes = None
    339          if oldnotes:
    340              if position == "append":
    341                  newnotes = oldnotes + "\n" + text
    342              elif position == "merge":
    343                  if oldnotes != text:
    344                      oldnoteslist = oldnotes.split("\n")
    345                      for newline in text.split("\n"):
    346                          newline = newline.rstrip()
    347                          # avoid duplicate comment lines (this might cause some problems)
    348                          if newline not in oldnotes or len(newline) < 5:
    349                              oldnoteslist.append(newline)
    350                      newnotes = "\n".join(oldnoteslist)
    351              else:
    352                  newnotes = text + '\n' + oldnotes
    353          else:
    354              newnotes = "\n".join([line.rstrip() for line in text.split("\n")])
    355
    356          if newnotes:
    357              newlines = []
    358              needs_space = get_libgettextpo_version() < (0, 17, 0)
    359              for line in newnotes.split("\n"):
    360                  if line and needs_space:
    361                      newlines.append(" " + line)
    362                  else:
    363                      newlines.append(line)
    364              newnotes = "\n".join(newlines)
    365              if origin in ["programmer", "developer", "source code"]:
    366                  gpo.po_message_set_extracted_comments(self._gpo_message, newnotes)
    367              else:
    368                  gpo.po_message_set_comments(self._gpo_message, newnotes)
    369
    370 -    def removenotes(self):
    371          gpo.po_message_set_comments(self._gpo_message, "")
    372
    373 -    def copy(self):
    374          newpo = self.__class__()
    375          newpo._gpo_message = self._gpo_message
    376          return newpo
    377
    378 -    def merge(self, otherpo, overwrite=False, comments=True, authoritative=False):
    379          """Merges the otherpo (with the same msgid) into this one.
    380
    381          Overwrite non-blank self.msgstr only if overwrite is True
    382          merge comments only if comments is True
    383
    384          """
    385
    386          if not isinstance(otherpo, pounit):
    387              super(pounit, self).merge(otherpo, overwrite, comments)
    388              return
    389          if comments:
    390              self.addnote(otherpo.getnotes("translator"), origin="translator", position="merge")
    391              # FIXME mergelists(self.typecomments, otherpo.typecomments)
    392              if not authoritative:
    393                  # We don't bring across otherpo.automaticcomments as we consider ourself
    394                  # to be the the authority.  Same applies to otherpo.msgidcomments
    395                  self.addnote(otherpo.getnotes("developer"), origin="developer", position="merge")
    396                  self.msgidcomment = otherpo._extract_msgidcomments() or None
    397                  self.addlocations(otherpo.getlocations())
    398          if not self.istranslated() or overwrite:
    399              # Remove kde-style comments from the translation (if any).
    400              if self._extract_msgidcomments(otherpo.target):
    401                  otherpo.target = otherpo.target.replace('_: ' + otherpo._extract_msgidcomments()+ '\n', '')
    402              self.target = otherpo.target
    403              if self.source != otherpo.source:
    404                  self.markfuzzy()
    405              else:
    406                  self.markfuzzy(otherpo.isfuzzy())
    407          elif not otherpo.istranslated():
    408              if self.source != otherpo.source:
    409                  self.markfuzzy()
    410          else:
    411              if self.target != otherpo.target:
    412                  self.markfuzzy()
    413
    414 -    def isheader(self):
    415          #return self.source == u"" and self.target != u""
    416          # we really want to make sure that there is no msgidcomment or msgctxt
    417          return self.getid() == "" and len(self.target) > 0
    418
    419 -    def isblank(self):
    420          return len(self.source) == 0 and len(self.target) == 0
    421
    422 -    def hastypecomment(self, typecomment):
    423          return gpo.po_message_is_format(self._gpo_message, typecomment)
    424
    425 -    def hasmarkedcomment(self, commentmarker):
    426          commentmarker = "(%s)" % commentmarker
    427          for comment in self.getnotes("translator").split("\n"):
    428              if comment.startswith(commentmarker):
    429                  return True
    430          return False
    431
    432 -    def istranslated(self):
    433          return super(pounit, self).istranslated() and not self.isobsolete()
    434
    435 -    def istranslatable(self):
    436          return not (self.isheader() or self.isblank() or self.isobsolete())
    437
    438 -    def isfuzzy(self):
    439          return gpo.po_message_is_fuzzy(self._gpo_message)
    440
    441 -    def markfuzzy(self, present=True):
    442          gpo.po_message_set_fuzzy(self._gpo_message, present)
    443
    444 -    def isreview(self):
    445          return self.hasmarkedcomment("review") or self.hasmarkedcomment("pofilter")
    446
    447 -    def isobsolete(self):
    448          return gpo.po_message_is_obsolete(self._gpo_message)
    449
    450 -    def makeobsolete(self):
    451          # FIXME: libgettexpo currently does not reset other data, we probably want to do that
    452          # but a better solution would be for libgettextpo to output correct data on serialisation
    453          gpo.po_message_set_obsolete(self._gpo_message, True)
    454
    455 -    def resurrect(self):
    456          gpo.po_message_set_obsolete(self._gpo_message, False)
    457
    458 -    def hasplural(self):
    459          return gpo.po_message_msgid_plural(self._gpo_message) is not None
    460
    461 -    def _extract_msgidcomments(self, text=None):
    462          """Extract KDE style msgid comments from the unit.
    463
    464          @rtype: String
    465          @return: Returns the extracted msgidcomments found in this unit's msgid.
    466
    467          """
    468
    469          if not text:
    470              text = gpo.po_message_msgid(self._gpo_message)
    471          if text:
    472              msgidcomment = re.search("_: (.*)\n", text)
    473              if msgidcomment:
    474                  return msgidcomment.group(1).decode(self._encoding)
    475          return u""
    476
    477 -    def __str__(self):
    478          pf = pofile()
    479          pf.addunit(self)
    480          return str(pf)
    481
    482 -    def getlocations(self):
    483          locations = []
    484          i = 0
    485          location = gpo.po_message_filepos(self._gpo_message, i)
    486          while location:
    487              locname = gpo.po_filepos_file(location)
    488              locline = gpo.po_filepos_start_line(location)
    489              if locline == -1:
    490                  locstring = locname
    491              else:
    492                  locstring = locname + ":" + str(locline)
    493              locations.append(locstring)
    494              i += 1
    495              location = gpo.po_message_filepos(self._gpo_message, i)
    496          return locations
    497
    498 -    def addlocation(self, location):
    499          for loc in location.split():
    500              parts = loc.split(":")
    501              file = parts[0]
    502              if len(parts) == 2:
    503                  line = int(parts[1])
    504              else:
    505                  line = -1
    506              gpo.po_message_add_filepos(self._gpo_message, file, line)
    507
    508 -    def getcontext(self):
    509          msgctxt = gpo.po_message_msgctxt(self._gpo_message)
    510          msgidcomment = self._extract_msgidcomments()
    511          if msgctxt:
    512              return msgctxt + msgidcomment
    513          else:
    514              return msgidcomment
    515
    516 -class pofile(pocommon.pofile):
    517      UnitClass = pounit
    518 -    def __init__(self, inputfile=None, encoding=None, unitclass=pounit):
    519          self.UnitClass = unitclass
    520          pocommon.pofile.__init__(self, unitclass=unitclass)
    521          self._gpo_memory_file = None
    522          self._gpo_message_iterator = None
    523          self._encoding = encodingToUse(encoding)
    524          if inputfile is not None:
    525              self.parse(inputfile)
    526          else:
    527              self._gpo_memory_file = gpo.po_file_create()
    528              self._gpo_message_iterator = gpo.po_message_iterator(self._gpo_memory_file, None)
    529
    530 -    def addunit(self, unit):
    531          gpo.po_message_insert(self._gpo_message_iterator, unit._gpo_message)
    532          self.units.append(unit)
    533
    534 -    def removeduplicates(self, duplicatestyle="merge"):
    535          """make sure each msgid is unique ; merge comments etc from duplicates into original"""
    536          msgiddict = {}
    537          uniqueunits = []
    538          # we sometimes need to keep track of what has been marked
    539          # TODO: this is using a list as the pos aren't hashable, but this is slow...
    540          markedpos = []
    541          def addcomment(thepo):
    542              thepo.msgidcomment = " ".join(thepo.getlocations())
    543              markedpos.append(thepo)
    544          for thepo in self.units:
    545              if thepo.isheader():
    546                  uniqueunits.append(thepo)
    547                  continue
    548              if duplicatestyle.startswith("msgid_comment"):
    549                  msgid = thepo._extract_msgidcomments() + thepo.source
    550              else:
    551                  msgid = thepo.source
    552              if duplicatestyle == "msgid_comment_all":
    553                  addcomment(thepo)
    554                  uniqueunits.append(thepo)
    555              elif msgid in msgiddict:
    556                  if duplicatestyle == "merge":
    557                      if msgid:
    558                          msgiddict[msgid].merge(thepo)
    559                      else:
    560                          addcomment(thepo)
    561                          uniqueunits.append(thepo)
    562                  elif duplicatestyle == "keep":
    563                      uniqueunits.append(thepo)
    564                  elif duplicatestyle == "msgid_comment":
    565                      origpo = msgiddict[msgid]
    566                      if origpo not in markedpos:
    567                          addcomment(origpo)
    568                      addcomment(thepo)
    569                      uniqueunits.append(thepo)
    570                  elif duplicatestyle == "msgctxt":
    571                      origpo = msgiddict[msgid]
    572                      if origpo not in markedpos:
    573                          gpo.po_message_set_msgctxt(origpo._gpo_message, " ".join(origpo.getlocations()))
    574                          markedpos.append(thepo)
    575                      gpo.po_message_set_msgctxt(thepo._gpo_message, " ".join(thepo.getlocations()))
    576                      uniqueunits.append(thepo)
    577              else:
    578                  if not msgid and duplicatestyle != "keep":
    579                      addcomment(thepo)
    580                  msgiddict[msgid] = thepo
    581                  uniqueunits.append(thepo)
    582          new_gpo_memory_file = gpo.po_file_create()
    583          new_gpo_message_iterator = gpo.po_message_iterator(new_gpo_memory_file, None)
    584          for unit in uniqueunits:
    585              gpo.po_message_insert(new_gpo_message_iterator, unit._gpo_message)
    586          gpo.po_message_iterator_free(self._gpo_message_iterator)
    587          self._gpo_message_iterator = new_gpo_message_iterator
    588          self._gpo_memory_file = new_gpo_memory_file
    589          self.units = uniqueunits
    590
    591 -    def __str__(self):
    592          def obsolete_workaround():
    593              # Remove all items that are not output by msgmerge when a unit is obsolete.  This is a work
    594              # around for bug in libgettextpo
    595              # FIXME Do version test in case they fix this bug
    596              for unit in self.units:
    597                  if unit.isobsolete():
    598                      gpo.po_message_set_extracted_comments(unit._gpo_message, "")
    599                      location = gpo.po_message_filepos(unit._gpo_message, 0)
    600                      while location:
    601                          gpo.po_message_remove_filepos(unit._gpo_message, 0)
    602                          location = gpo.po_message_filepos(unit._gpo_message, 0)
    603          outputstring = ""
    604          if self._gpo_memory_file:
    605              obsolete_workaround()
    606              f = tempfile.NamedTemporaryFile(prefix='translate', suffix='.po')
    607              self._gpo_memory_file = gpo.po_file_write_v2(self._gpo_memory_file, f.name, xerror_handler)
    608              f.seek(0)
    609              outputstring = f.read()
    610              f.close()
    611          return outputstring
    612
    613 -    def isempty(self):
    614          """Returns True if the object doesn't contain any translation units."""
    615          if len(self.units) == 0:
    616              return True
    617          # Skip the first unit if it is a header.
    618          if self.units[0].isheader():
    619              units = self.units[1:]
    620          else:
    621              units = self.units
    622
    623          for unit in units:
    624              if not unit.isblank() and not unit.isobsolete():
    625                  return False
    626          return True
    627
    628 -    def parse(self, input):
    629          if hasattr(input, 'name'):
    630              self.filename = input.name
    631          elif not getattr(self, 'filename', ''):
    632              self.filename = ''
    633
    634          if hasattr(input, "read"):
    635              posrc = input.read()
    636              input.close()
    637              input = posrc
    638
    639          needtmpfile = not os.path.isfile(input)
    640          if needtmpfile:
    641              # This is not a file - we write the string to a temporary file
    642              fd, fname = tempfile.mkstemp(prefix='translate', suffix='.po')
    643              os.write(fd, input)
    644              input = fname
    645              os.close(fd)
    646
    647          self._gpo_memory_file = gpo.po_file_read_v3(input, xerror_handler)
    648          if self._gpo_memory_file is None:
    649              print >> sys.stderr, "Error:"
    650
    651          if needtmpfile:
    652              os.remove(input)
    653
    654          # Handle xerrors here
    655          self._header = gpo.po_file_domain_header(self._gpo_memory_file, None)
    656          if self._header:
    657              charset = gpo.po_header_field(self._header, "Content-Type")
    658              if charset:
    659                  charset = re.search("charset=([^\\s]+)", charset).group(1)
    660              self._encoding = encodingToUse(charset)
    661          self._gpo_message_iterator = gpo.po_message_iterator(self._gpo_memory_file, None)
    662          newmessage = gpo.po_next_message(self._gpo_message_iterator)
    663          while newmessage:
    664              newunit = pounit(gpo_message=newmessage)
    665              self.units.append(newunit)
    666              newmessage = gpo.po_next_message(self._gpo_message_iterator)
    667          self._free_iterator()
    668
    669 -    def __del__(self):
    670          # We currently disable this while we still get segmentation faults.
    671          # Note that this is definitely leaking memory because of this.
    672          return
    673          self._free_iterator()
    674          if self._gpo_memory_file is not None:
    675              gpo.po_file_free(self._gpo_memory_file)
    676              self._gpo_memory_file = None
    677
    678 -    def _free_iterator(self):
    679          # We currently disable this while we still get segmentation faults.
    680          # Note that this is definitely leaking memory because of this.
    681          return
    682          if self._gpo_message_iterator is not None:
    683              gpo.po_message_iterator_free(self._gpo_message_iterator)
    684              self._gpo_message_iterator = None
    685