translate.storage.cpo

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