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