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