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 import pypo
     36  from translate.storage.pocommon import encodingToUse
     37  from translate.lang import data
     38  from ctypes import c_int, c_uint, c_char_p, c_long, CFUNCTYPE, POINTER
     39  from ctypes import Structure, cdll
     40  import ctypes.util
     41  import os
     42  import re
     43  import sys
     44  import tempfile
     45
     46  lsep = " "
     47  """Seperator for #: entries"""
     48
     49  STRING = c_char_p
     50
     51
     52  # Structures
     53 -class po_message(Structure):
     54      _fields_ = []
     55
     56  # Function prototypes
     57  xerror_prototype = CFUNCTYPE(None, c_int, POINTER(po_message), STRING, c_uint, c_uint, c_int, STRING)
     58  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)
     59
     60
     61  # Structures (error handler)
     62 -class po_xerror_handler(Structure):
     63      _fields_ = [('xerror', xerror_prototype),
     64                  ('xerror2', xerror2_prototype)]
     65
     66
     67 -class po_error_handler(Structure):
     68      _fields_ = [
     69      ('error', CFUNCTYPE(None, c_int, c_int, STRING)),
     70      ('error_at_line', CFUNCTYPE(None, c_int, c_int, STRING, c_uint, STRING)),
     71      ('multiline_warning', CFUNCTYPE(None, STRING, STRING)),
     72      ('multiline_error', CFUNCTYPE(None, STRING, STRING)),
     73  ]
     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
     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  # 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_is_format.argtypes = [c_int, STRING]
    133  gpo.po_message_set_format.argtypes = [c_int, STRING, c_int]
    134  gpo.po_message_msgctxt.restype = STRING
    135  gpo.po_message_msgid.restype = STRING
    136  gpo.po_message_msgid_plural.restype = STRING
    137  gpo.po_message_msgstr.restype = STRING
    138  gpo.po_message_msgstr_plural.restype = STRING
    139
    140  # Message (set methods)
    141  gpo.po_message_set_comments.argtypes = [c_int, STRING]
    142  gpo.po_message_set_extracted_comments.argtypes = [c_int, STRING]
    143  gpo.po_message_set_fuzzy.argtypes = [c_int, c_int]
    144  gpo.po_message_set_msgctxt.argtypes = [c_int, STRING]
    145
    146  # Setup the po_xerror_handler
    147  xerror_handler = po_xerror_handler()
    148  xerror_handler.xerror = xerror_prototype(xerror_cb)
    149  xerror_handler.xerror2 = xerror2_prototype(xerror2_cb)
    150
    151
    152 -def escapeforpo(text):
    153      return pypo.escapeforpo(text)
    154
    155
    156 -def quoteforpo(text):
    157      return pypo.quoteforpo(text)
    158
    159
    160 -def unquotefrompo(postr):
    161      return pypo.unquotefrompo(postr)
    162
    163
    164 -def get_libgettextpo_version():
    165      """Returns the libgettextpo version
    166
    167         @rtype: three-value tuple
    168         @return: libgettextpo version in the following format::
    169             (major version, minor version, subminor version)
    170      """
    171      libversion = c_long.in_dll(gpo, 'libgettextpo_version')
    172      major = libversion.value >> 16
    173      minor = libversion.value >> 8
    174      subminor = libversion.value - (major << 16) - (minor << 8)
    175      return major, minor, subminor
    176
    177
    178 -class pounit(pocommon.pounit):
    179
    180 -    def __init__(self, source=None, encoding='utf-8', gpo_message=None):
    181          self._rich_source = None
    182          self._rich_target = None
    183          self._encoding = encoding
    184          if not gpo_message:
    185              self._gpo_message = gpo.po_message_create()
    186          if source or source == "":
    187              self.source = source
    188              self.target = ""
    189          elif gpo_message:
    190              self._gpo_message = gpo_message
    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
    200          def remove_msgid_comments(text):
    201              if not text:
    202                  return text
    203              if text.startswith("_:"):
    204                  remainder = re.search(r"_: .*\n(.*)", text)
    205                  if remainder:
    206                      return remainder.group(1)
    207                  else:
    208                      return u""
    209              else:
    210                  return text
    211          singular = remove_msgid_comments(gpo.po_message_msgid(self._gpo_message).decode(self._encoding))
    212          if singular:
    213              if self.hasplural():
    214                  multi = multistring(singular, self._encoding)
    215                  pluralform = gpo.po_message_msgid_plural(self._gpo_message).decode(self._encoding)
    216                  multi.strings.append(pluralform)
    217                  return multi
    218              else:
    219                  return singular
    220          else:
    221              return u""
    222
    223 -    def setsource(self, source):
    224          if isinstance(source, multistring):
    225              source = source.strings
    226          if isinstance(source, unicode):
    227              source = source.encode(self._encoding)
    228          if isinstance(source, list):
    229              gpo.po_message_set_msgid(self._gpo_message, source[0].encode(self._encoding))
    230              if len(source) > 1:
    231                  gpo.po_message_set_msgid_plural(self._gpo_message, source[1].encode(self._encoding))
    232          else:
    233              gpo.po_message_set_msgid(self._gpo_message, source)
    234              gpo.po_message_set_msgid_plural(self._gpo_message, None)
    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.decode(self._encoding))
    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 = (gpo.po_message_msgstr(self._gpo_message) or "").decode(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) or "").decode(self._encoding)
    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 = u"%s\04%s" % (context.decode(self._encoding), id)
    314          return id
    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 and 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).encode(self._encoding)
    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          if not isinstance(otherpo, pounit):
    386              super(pounit, self).merge(otherpo, overwrite, comments)
    387              return
    388          if comments:
    389              self.addnote(otherpo.getnotes("translator"), origin="translator", position="merge")
    390              # FIXME mergelists(self.typecomments, otherpo.typecomments)
    391              if not authoritative:
    392                  # We don't bring across otherpo.automaticcomments as we consider ourself
    393                  # to be the the authority.  Same applies to otherpo.msgidcomments
    394                  self.addnote(otherpo.getnotes("developer"), origin="developer", position="merge")
    395                  self.msgidcomment = otherpo._extract_msgidcomments() or None
    396                  self.addlocations(otherpo.getlocations())
    397          if not self.istranslated() or overwrite:
    398              # Remove kde-style comments from the translation (if any).
    399              if self._extract_msgidcomments(otherpo.target):
    400                  otherpo.target = otherpo.target.replace('_: ' + otherpo._extract_msgidcomments() + '\n', '')
    401              self.target = otherpo.target
    402              if self.source != otherpo.source or self.getcontext() != otherpo.getcontext():
    403                  self.markfuzzy()
    404              else:
    405                  self.markfuzzy(otherpo.isfuzzy())
    406          elif not otherpo.istranslated():
    407              if self.source != otherpo.source:
    408                  self.markfuzzy()
    409          else:
    410              if self.target != otherpo.target:
    411                  self.markfuzzy()
    412
    413 -    def isheader(self):
    414          #return self.source == u"" and self.target != u""
    415          # we really want to make sure that there is no msgidcomment or msgctxt
    416          return self.getid() == "" and len(self.target) > 0
    417
    418 -    def isblank(self):
    419          return len(self.source) == len(self.target) == len(self.getcontext()) == 0
    420
    421 -    def hastypecomment(self, typecomment):
    422          return gpo.po_message_is_format(self._gpo_message, typecomment)
    423
    424 -    def settypecomment(self, typecomment, present=True):
    425          gpo.po_message_set_format(self._gpo_message, typecomment, present)
    426
    427 -    def hasmarkedcomment(self, commentmarker):
    428          commentmarker = "(%s)" % commentmarker
    429          for comment in self.getnotes("translator").split("\n"):
    430              if comment.startswith(commentmarker):
    431                  return True
    432          return False
    433
    434 -    def isfuzzy(self):
    435          return gpo.po_message_is_fuzzy(self._gpo_message)
    436
    437 -    def markfuzzy(self, present=True):
    438          gpo.po_message_set_fuzzy(self._gpo_message, present)
    439
    440 -    def isobsolete(self):
    441          return gpo.po_message_is_obsolete(self._gpo_message)
    442
    443 -    def makeobsolete(self):
    444          # FIXME: libgettexpo currently does not reset other data, we probably want to do that
    445          # but a better solution would be for libgettextpo to output correct data on serialisation
    446          gpo.po_message_set_obsolete(self._gpo_message, True)
    447
    448 -    def resurrect(self):
    449          gpo.po_message_set_obsolete(self._gpo_message, False)
    450
    451 -    def hasplural(self):
    452          return gpo.po_message_msgid_plural(self._gpo_message) is not None
    453
    454 -    def _extract_msgidcomments(self, text=None):
    455          """Extract KDE style msgid comments from the unit.
    456
    457          @rtype: String
    458          @return: Returns the extracted msgidcomments found in this unit's msgid.
    459          """
    460          if not text:
    461              text = gpo.po_message_msgid(self._gpo_message).decode(self._encoding)
    462          if text:
    463              return pocommon.extract_msgid_comment(text)
    464          return u""
    465
    466 -    def setmsgidcomment(self, msgidcomment):
    467          if msgidcomment:
    468              self.source = u"_: %s\n%s" % (msgidcomment, self.source)
    469      msgidcomment = property(_extract_msgidcomments, setmsgidcomment)
    470
    471 -    def __str__(self):
    472          pf = pofile()
    473          pf.addunit(self)
    474          return str(pf)
    475
    476 -    def getlocations(self):
    477          locations = []
    478          i = 0
    479          location = gpo.po_message_filepos(self._gpo_message, i)
    480          while location:
    481              locname = gpo.po_filepos_file(location)
    482              locline = gpo.po_filepos_start_line(location)
    483              if locline == -1:
    484                  locstring = locname
    485              else:
    486                  locstring = locname + ":" + str(locline)
    487              locations.append(locstring)
    488              i += 1
    489              location = gpo.po_message_filepos(self._gpo_message, i)
    490          return locations
    491
    492 -    def addlocation(self, location):
    493          for loc in location.split():
    494              parts = loc.split(":")
    495              file = parts[0]
    496              if len(parts) == 2:
    497                  line = int(parts[1] or "0")
    498              else:
    499                  line = -1
    500              gpo.po_message_add_filepos(self._gpo_message, file, line)
    501
    502 -    def getcontext(self):
    503          msgctxt = gpo.po_message_msgctxt(self._gpo_message)
    504          if msgctxt:
    505              return msgctxt.decode(self._encoding)
    506          else:
    507              msgidcomment = self._extract_msgidcomments()
    508              return msgidcomment
    509
    510 -    def buildfromunit(cls, unit):
    511          """Build a native unit from a foreign unit, preserving as much
    512          information as possible."""
    513          if type(unit) == cls and hasattr(unit, "copy") and callable(unit.copy):
    514              return unit.copy()
    515          elif isinstance(unit, pocommon.pounit):
    516              newunit = cls(unit.source)
    517              newunit.target = unit.target
    518              #context
    519              newunit.msgidcomment = unit._extract_msgidcomments()
    520              context = unit.getcontext()
    521              if not newunit.msgidcomment and context:
    522                  gpo.po_message_set_msgctxt(newunit._gpo_message, context)
    523
    524              locations = unit.getlocations()
    525              if locations:
    526                  newunit.addlocations(locations)
    527              notes = unit.getnotes("developer")
    528              if notes:
    529                  newunit.addnote(notes, "developer")
    530              notes = unit.getnotes("translator")
    531              if notes:
    532                  newunit.addnote(notes, "translator")
    533              if unit.isobsolete():
    534                  newunit.makeobsolete()
    535              newunit.markfuzzy(unit.isfuzzy())
    536              for tc in ['python-format', 'c-format', 'php-format']:
    537                  if unit.hastypecomment(tc):
    538                      newunit.settypecomment(tc)
    539                      # We assume/guess/hope that there will only be one
    540                      break
    541              return newunit
    542          else:
    543              return base.TranslationUnit.buildfromunit(unit)
    544      buildfromunit = classmethod(buildfromunit)
    545
    546
    547 -class pofile(pocommon.pofile):
    548      UnitClass = pounit
    549
    550 -    def __init__(self, inputfile=None, encoding=None, unitclass=pounit):
    551          self._gpo_memory_file = None
    552          self._gpo_message_iterator = None
    553          super(pofile, self).__init__(inputfile=inputfile, encoding=encoding)
    554          if inputfile is None:
    555              self._gpo_memory_file = gpo.po_file_create()
    556              self._gpo_message_iterator = gpo.po_message_iterator(self._gpo_memory_file, None)
    557
    558 -    def addunit(self, unit, new=True):
    559          if new:
    560              gpo.po_message_insert(self._gpo_message_iterator, unit._gpo_message)
    561          super(pofile, self).addunit(unit)
    562
    563 -    def removeduplicates(self, duplicatestyle="merge"):
    564          """make sure each msgid is unique ; merge comments etc from duplicates into original"""
    565          # TODO: can we handle consecutive calls to removeduplicates()? What
    566          # about files already containing msgctxt? - test
    567          id_dict = {}
    568          uniqueunits = []
    569          # TODO: this is using a list as the pos aren't hashable, but this is slow.
    570          # probably not used frequently enough to worry about it, though.
    571          markedpos = []
    572          def addcomment(thepo):
    573              thepo.msgidcomment = " ".join(thepo.getlocations())
    574              markedpos.append(thepo)
    575          for thepo in self.units:
    576              id = thepo.getid()
    577              if thepo.isheader() and not thepo.getlocations():
    578                  # header msgids shouldn't be merged...
    579                  uniqueunits.append(thepo)
    580              elif id in id_dict:
    581                  if duplicatestyle == "merge":
    582                      if id:
    583                          id_dict[id].merge(thepo)
    584                      else:
    585                          addcomment(thepo)
    586                          uniqueunits.append(thepo)
    587                  elif duplicatestyle == "msgctxt":
    588                      origpo = id_dict[id]
    589                      if origpo not in markedpos:
    590                          gpo.po_message_set_msgctxt(origpo._gpo_message, " ".join(origpo.getlocations()))
    591                          markedpos.append(thepo)
    592                      gpo.po_message_set_msgctxt(thepo._gpo_message, " ".join(thepo.getlocations()))
    593                      uniqueunits.append(thepo)
    594              else:
    595                  if not id:
    596                      if duplicatestyle == "merge":
    597                          addcomment(thepo)
    598                      else:
    599                          gpo.po_message_set_msgctxt(thepo._gpo_message, " ".join(thepo.getlocations()))
    600                  id_dict[id] = thepo
    601                  uniqueunits.append(thepo)
    602          new_gpo_memory_file = gpo.po_file_create()
    603          new_gpo_message_iterator = gpo.po_message_iterator(new_gpo_memory_file, None)
    604          for unit in uniqueunits:
    605              gpo.po_message_insert(new_gpo_message_iterator, unit._gpo_message)
    606          gpo.po_message_iterator_free(self._gpo_message_iterator)
    607          self._gpo_message_iterator = new_gpo_message_iterator
    608          self._gpo_memory_file = new_gpo_memory_file
    609          self.units = uniqueunits
    610
    611 -    def __str__(self):
    612          def obsolete_workaround():
    613              # Remove all items that are not output by msgmerge when a unit is obsolete.  This is a work
    614              # around for bug in libgettextpo
    615              # FIXME Do version test in case they fix this bug
    616              for unit in self.units:
    617                  if unit.isobsolete():
    618                      gpo.po_message_set_extracted_comments(unit._gpo_message, "")
    619                      location = gpo.po_message_filepos(unit._gpo_message, 0)
    620                      while location:
    621                          gpo.po_message_remove_filepos(unit._gpo_message, 0)
    622                          location = gpo.po_message_filepos(unit._gpo_message, 0)
    623          outputstring = ""
    624          if self._gpo_memory_file:
    625              obsolete_workaround()
    626              f, fname = tempfile.mkstemp(prefix='translate', suffix='.po')
    627              os.close(f)
    628              self._gpo_memory_file = gpo.po_file_write_v2(self._gpo_memory_file, fname, xerror_handler)
    629              f = open(fname)
    630              outputstring = f.read()
    631              f.close()
    632              os.remove(fname)
    633          return outputstring
    634
    635 -    def isempty(self):
    636          """Returns True if the object doesn't contain any translation units."""
    637          if len(self.units) == 0:
    638              return True
    639          # Skip the first unit if it is a header.
    640          if self.units[0].isheader():
    641              units = self.units[1:]
    642          else:
    643              units = self.units
    644
    645          for unit in units:
    646              if not unit.isblank() and not unit.isobsolete():
    647                  return False
    648          return True
    649
    650 -    def parse(self, input):
    651          if hasattr(input, 'name'):
    652              self.filename = input.name
    653          elif not getattr(self, 'filename', ''):
    654              self.filename = ''
    655
    656          if hasattr(input, "read"):
    657              posrc = input.read()
    658              input.close()
    659              input = posrc
    660
    661          needtmpfile = not os.path.isfile(input)
    662          if needtmpfile:
    663              # This is not a file - we write the string to a temporary file
    664              fd, fname = tempfile.mkstemp(prefix='translate', suffix='.po')
    665              os.write(fd, input)
    666              input = fname
    667              os.close(fd)
    668
    669          self._gpo_memory_file = gpo.po_file_read_v3(input, xerror_handler)
    670          if self._gpo_memory_file is None:
    671              print >> sys.stderr, "Error:"
    672
    673          if needtmpfile:
    674              os.remove(input)
    675
    676          # Handle xerrors here
    677          self._header = gpo.po_file_domain_header(self._gpo_memory_file, None)
    678          if self._header:
    679              charset = gpo.po_header_field(self._header, "Content-Type")
    680              if charset:
    681                  charset = re.search("charset=([^\\s]+)", charset).group(1)
    682              self._encoding = encodingToUse(charset)
    683          self._gpo_message_iterator = gpo.po_message_iterator(self._gpo_memory_file, None)
    684          newmessage = gpo.po_next_message(self._gpo_message_iterator)
    685          while newmessage:
    686              newunit = pounit(gpo_message=newmessage, encoding=self._encoding)
    687              self.addunit(newunit, new=False)
    688              newmessage = gpo.po_next_message(self._gpo_message_iterator)
    689          self._free_iterator()
    690
    691 -    def __del__(self):
    692          # We currently disable this while we still get segmentation faults.
    693          # Note that this is definitely leaking memory because of this.
    694          return
    695          self._free_iterator()
    696          if self._gpo_memory_file is not None:
    697              gpo.po_file_free(self._gpo_memory_file)
    698              self._gpo_memory_file = None
    699
    700 -    def _free_iterator(self):
    701          # We currently disable this while we still get segmentation faults.
    702          # Note that this is definitely leaking memory because of this.
    703          return
    704          if self._gpo_message_iterator is not None:
    705              gpo.po_message_iterator_free(self._gpo_message_iterator)
    706              self._gpo_message_iterator = None
    707