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 base, 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_size_t, 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  import urllib
     46
     47  lsep = " "
     48  """Seperator for #: entries"""
     49
     50  STRING = c_char_p
     51
     52
     53  # Structures
     54 -class po_message(Structure):
     55      _fields_ = []
     56
     57  # Function prototypes
     58  xerror_prototype = CFUNCTYPE(None, c_int, POINTER(po_message), STRING, c_uint, c_uint, c_int, STRING)
     59  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)
     60
     61
     62  # Structures (error handler)
     63 -class po_xerror_handler(Structure):
     64      _fields_ = [('xerror', xerror_prototype),
     65                  ('xerror2', xerror2_prototype)]
     66
     67
     68 -class po_error_handler(Structure):
     69      _fields_ = [
     70      ('error', CFUNCTYPE(None, c_int, c_int, STRING)),
     71      ('error_at_line', CFUNCTYPE(None, c_int, c_int, STRING, c_uint, STRING)),
     72      ('multiline_warning', CFUNCTYPE(None, STRING, STRING)),
     73      ('multiline_error', CFUNCTYPE(None, STRING, STRING)),
     74  ]
     75
     76
     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
     84 -def xerror2_cb(severity, message1, filename1, lineno1, column1, multiline_p1, message_text1, message2, filename2, lineno2, column2, multiline_p2, message_text2):
     85      print >> sys.stderr, "xerror2_cb", severity, message1, filename1, lineno1, column1, multiline_p1, message_text1, message2, filename2, lineno2, column2, multiline_p2, message_text2
     86      if severity >= 1:
     87          raise ValueError(message_text1)
     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_size_t]
    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
    153 -def escapeforpo(text):
    154      return pypo.escapeforpo(text)
    155
    156
    157 -def quoteforpo(text):
    158      return pypo.quoteforpo(text)
    159
    160
    161 -def unquotefrompo(postr):
    162      return pypo.unquotefrompo(postr)
    163
    164
    165 -def get_libgettextpo_version():
    166      """Returns the libgettextpo version
    167
    168         @rtype: three-value tuple
    169         @return: libgettextpo version in the following format::
    170             (major version, minor version, subminor version)
    171      """
    172      libversion = c_long.in_dll(gpo, 'libgettextpo_version')
    173      major = libversion.value >> 16
    174      minor = libversion.value >> 8
    175      subminor = libversion.value - (major << 16) - (minor << 8)
    176      return major, minor, subminor
    177
    178
    179 -class pounit(pocommon.pounit):
    180
    181 -    def __init__(self, source=None, encoding='utf-8', gpo_message=None):
    182          self._rich_source = None
    183          self._rich_target = None
    184          self._encoding = encoding
    185          if not gpo_message:
    186              self._gpo_message = gpo.po_message_create()
    187          if source or source == "":
    188              self.source = source
    189              self.target = ""
    190          elif gpo_message:
    191              self._gpo_message = gpo_message
    192          self.infer_state()
    193
    194 -    def infer_state(self):
    195          #FIXME: do obsolete
    196          if gpo.po_message_is_obsolete(self._gpo_message):
    197              self.set_state_n(self.STATE[self.S_OBSOLETE][0])
    198          elif gpo.po_message_is_fuzzy(self._gpo_message):
    199              self.set_state_n(self.STATE[self.S_FUZZY][0])
    200          elif self.gettarget():
    201              self.set_state_n(self.STATE[self.S_TRANSLATED][0])
    202          else:
    203              self.set_state_n(self.STATE[self.S_UNTRANSLATED][0])
    204
    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):
    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)
    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          def addcomment(thepo):
    610              thepo.msgidcomment = " ".join(thepo.getlocations())
    611              markedpos.append(thepo)
    612          for thepo in self.units:
    613              id = thepo.getid()
    614              if thepo.isheader() and not thepo.getlocations():
    615                  # header msgids shouldn't be merged...
    616                  uniqueunits.append(thepo)
    617              elif id in id_dict:
    618                  if duplicatestyle == "merge":
    619                      if id:
    620                          id_dict[id].merge(thepo)
    621                      else:
    622                          addcomment(thepo)
    623                          uniqueunits.append(thepo)
    624                  elif duplicatestyle == "msgctxt":
    625                      origpo = id_dict[id]
    626                      if origpo not in markedpos:
    627                          gpo.po_message_set_msgctxt(origpo._gpo_message, " ".join(origpo.getlocations()))
    628                          markedpos.append(thepo)
    629                      gpo.po_message_set_msgctxt(thepo._gpo_message, " ".join(thepo.getlocations()))
    630                      uniqueunits.append(thepo)
    631              else:
    632                  if not id:
    633                      if duplicatestyle == "merge":
    634                          addcomment(thepo)
    635                      else:
    636                          gpo.po_message_set_msgctxt(thepo._gpo_message, " ".join(thepo.getlocations()))
    637                  id_dict[id] = thepo
    638                  uniqueunits.append(thepo)
    639          new_gpo_memory_file = gpo.po_file_create()
    640          new_gpo_message_iterator = gpo.po_message_iterator(new_gpo_memory_file, None)
    641          for unit in uniqueunits:
    642              gpo.po_message_insert(new_gpo_message_iterator, unit._gpo_message)
    643          gpo.po_message_iterator_free(self._gpo_message_iterator)
    644          self._gpo_message_iterator = new_gpo_message_iterator
    645          self._gpo_memory_file = new_gpo_memory_file
    646          self.units = uniqueunits
    647
    648 -    def __str__(self):
    649          def obsolete_workaround():
    650              # Remove all items that are not output by msgmerge when a unit is obsolete.  This is a work
    651              # around for bug in libgettextpo
    652              # FIXME Do version test in case they fix this bug
    653              for unit in self.units:
    654                  if unit.isobsolete():
    655                      gpo.po_message_set_extracted_comments(unit._gpo_message, "")
    656                      location = gpo.po_message_filepos(unit._gpo_message, 0)
    657                      while location:
    658                          gpo.po_message_remove_filepos(unit._gpo_message, 0)
    659                          location = gpo.po_message_filepos(unit._gpo_message, 0)
    660          outputstring = ""
    661          if self._gpo_memory_file:
    662              obsolete_workaround()
    663              f, fname = tempfile.mkstemp(prefix='translate', suffix='.po')
    664              os.close(f)
    665              self._gpo_memory_file = gpo.po_file_write_v2(self._gpo_memory_file, fname, xerror_handler)
    666              f = open(fname)
    667              outputstring = f.read()
    668              f.close()
    669              os.remove(fname)
    670          return outputstring
    671
    672 -    def isempty(self):
    673          """Returns True if the object doesn't contain any translation units."""
    674          if len(self.units) == 0:
    675              return True
    676          # Skip the first unit if it is a header.
    677          if self.units[0].isheader():
    678              units = self.units[1:]
    679          else:
    680              units = self.units
    681
    682          for unit in units:
    683              if not unit.isblank() and not unit.isobsolete():
    684                  return False
    685          return True
    686
    687 -    def parse(self, input):
    688          if hasattr(input, 'name'):
    689              self.filename = input.name
    690          elif not getattr(self, 'filename', ''):
    691              self.filename = ''
    692
    693          if hasattr(input, "read"):
    694              posrc = input.read()
    695              input.close()
    696              input = posrc
    697
    698          needtmpfile = not os.path.isfile(input)
    699          if needtmpfile:
    700              # This is not a file - we write the string to a temporary file
    701              fd, fname = tempfile.mkstemp(prefix='translate', suffix='.po')
    702              os.write(fd, input)
    703              input = fname
    704              os.close(fd)
    705
    706          self._gpo_memory_file = gpo.po_file_read_v3(input, xerror_handler)
    707          if self._gpo_memory_file is None:
    708              print >> sys.stderr, "Error:"
    709
    710          if needtmpfile:
    711              os.remove(input)
    712
    713          self.units = []
    714          # Handle xerrors here
    715          self._header = gpo.po_file_domain_header(self._gpo_memory_file, None)
    716          if self._header:
    717              charset = gpo.po_header_field(self._header, "Content-Type")
    718              if charset:
    719                  charset = re.search("charset=([^\\s]+)", charset).group(1)
    720              self._encoding = encodingToUse(charset)
    721          self._gpo_message_iterator = gpo.po_message_iterator(self._gpo_memory_file, None)
    722          newmessage = gpo.po_next_message(self._gpo_message_iterator)
    723          while newmessage:
    724              newunit = pounit(gpo_message=newmessage, encoding=self._encoding)
    725              self.addunit(newunit, new=False)
    726              newmessage = gpo.po_next_message(self._gpo_message_iterator)
    727          self._free_iterator()
    728
    729 -    def __del__(self):
    730          # We currently disable this while we still get segmentation faults.
    731          # Note that this is definitely leaking memory because of this.
    732          return
    733          self._free_iterator()
    734          if self._gpo_memory_file is not None:
    735              gpo.po_file_free(self._gpo_memory_file)
    736              self._gpo_memory_file = None
    737
    738 -    def _free_iterator(self):
    739          # We currently disable this while we still get segmentation faults.
    740          # Note that this is definitely leaking memory because of this.
    741          return
    742          if self._gpo_message_iterator is not None:
    743              gpo.po_message_iterator_free(self._gpo_message_iterator)
    744              self._gpo_message_iterator = None
    745