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 return gpo.po_message_is_fuzzy(self._gpo_message)
450
451 - def _domarkfuzzy(self, present=True):
452 gpo.po_message_set_fuzzy(self._gpo_message, present)
453
454 - def makeobsolete(self):
455 # FIXME: libgettexpo currently does not reset other data, we probably want to do that 456 # but a better solution would be for libgettextpo to output correct data on serialisation 457 gpo.po_message_set_obsolete(self._gpo_message, True) 458 self.infer_state()
459
460 - def resurrect(self):
461 gpo.po_message_set_obsolete(self._gpo_message, False) 462 self.infer_state()
463
464 - def hasplural(self):
465 return gpo.po_message_msgid_plural(self._gpo_message) is not None
466
467 - def _extract_msgidcomments(self, text=None):
468 """Extract KDE style msgid comments from the unit. 469 470 @rtype: String 471 @return: Returns the extracted msgidcomments found in this unit's msgid. 472 """ 473 if not text: 474 text = (gpo.po_message_msgid(self._gpo_message) or "").decode(self._encoding) 475 if text: 476 return pocommon.extract_msgid_comment(text) 477 return u""
478
479 - def setmsgidcomment(self, msgidcomment):
480 if msgidcomment: 481 self.source = u"_: %s\n%s" % (msgidcomment, self.source)
482 msgidcomment = property(_extract_msgidcomments, setmsgidcomment) 483
484 - def __str__(self):
485 pf = pofile(noheader=True) 486 pf.addunit(self) 487 return str(pf)
488
489 - def getlocations(self):
490 locations = [] 491 i = 0 492 location = gpo.po_message_filepos(self._gpo_message, i) 493 while location: 494 locname = gpo.po_filepos_file(location) 495 locline = gpo.po_filepos_start_line(location) 496 if locline == -1: 497 locstring = locname 498 else: 499 locstring = locname + ":" + str(locline) 500 locations.append(urllib.unquote_plus(locstring)) 501 i += 1 502 location = gpo.po_message_filepos(self._gpo_message, i) 503 return locations
504
505 - def addlocation(self, location):
506 if location.find(" ") != -1: 507 location = urllib.quote_plus(location) 508 parts = location.split(":") 509 file = parts[0] 510 if len(parts) == 2: 511 line = int(parts[1] or "0") 512 else: 513 line = -1 514 gpo.po_message_add_filepos(self._gpo_message, file, line)
515
516 - def getcontext(self):
517 msgctxt = gpo.po_message_msgctxt(self._gpo_message) 518 if msgctxt: 519 return msgctxt.decode(self._encoding) 520 else: 521 msgidcomment = self._extract_msgidcomments() 522 return msgidcomment
523
524 - def setcontext(self, context):
525 context = data.forceunicode(context) 526 gpo.po_message_set_msgctxt(self._gpo_message, context)
527
528 - def buildfromunit(cls, unit, encoding=None):
529 """Build a native unit from a foreign unit, preserving as much 530 information as possible.""" 531 if type(unit) == cls and hasattr(unit, "copy") and callable(unit.copy): 532 return unit.copy() 533 elif isinstance(unit, pocommon.pounit): 534 newunit = cls(unit.source, encoding) 535 newunit.target = unit.target 536 #context 537 newunit.msgidcomment = unit._extract_msgidcomments() 538 context = unit.getcontext() 539 if not newunit.msgidcomment and context: 540 gpo.po_message_set_msgctxt(newunit._gpo_message, context) 541 542 locations = unit.getlocations() 543 if locations: 544 newunit.addlocations(locations) 545 notes = unit.getnotes("developer") 546 if notes: 547 newunit.addnote(notes, "developer") 548 notes = unit.getnotes("translator") 549 if notes: 550 newunit.addnote(notes, "translator") 551 if unit.isobsolete(): 552 newunit.makeobsolete() 553 newunit.markfuzzy(unit.isfuzzy()) 554 for tc in ['python-format', 'c-format', 'php-format']: 555 if unit.hastypecomment(tc): 556 newunit.settypecomment(tc) 557 # We assume/guess/hope that there will only be one 558 break 559 return newunit 560 else: 561 return base.TranslationUnit.buildfromunit(unit)
562 buildfromunit = classmethod(buildfromunit) 563 564
565 -class pofile(pocommon.pofile):
566 UnitClass = pounit 567
568 - def __init__(self, inputfile=None, encoding=None, unitclass=pounit, noheader=False):
569 self._gpo_memory_file = None 570 self._gpo_message_iterator = None 571 self.units = [] 572 self.sourcelanguage = None 573 self.targetlanguage = None 574 self._encoding = 'utf-8' 575 if inputfile is None: 576 self._gpo_memory_file = gpo.po_file_create() 577 self._gpo_message_iterator = gpo.po_message_iterator(self._gpo_memory_file, None) 578 if not noheader: 579 self.init_headers() 580 else: 581 super(pofile, self).__init__(inputfile=inputfile, encoding=encoding)
582
583 - def addunit(self, unit, new=True):
584 if new: 585 gpo.po_message_insert(self._gpo_message_iterator, unit._gpo_message) 586 super(pofile, self).addunit(unit)
587
588 - def _insert_header(self, header):
589 header._store = self 590 self.units.insert(0, header) 591 gpo.po_message_iterator_free(self._gpo_message_iterator) 592 self._gpo_message_iterator = gpo.po_message_iterator(self._gpo_memory_file, None) 593 gpo.po_message_insert(self._gpo_message_iterator, header._gpo_message) 594 while gpo.po_next_message(self._gpo_message_iterator): 595 pass
596
597 - def removeduplicates(self, duplicatestyle="merge"):
598 """make sure each msgid is unique ; merge comments etc from duplicates into original""" 599 # TODO: can we handle consecutive calls to removeduplicates()? What 600 # about files already containing msgctxt? - test 601 id_dict = {} 602 uniqueunits = [] 603 # TODO: this is using a list as the pos aren't hashable, but this is slow. 604 # probably not used frequently enough to worry about it, though. 605 markedpos = [] 606 607 def addcomment(thepo): 608 thepo.msgidcomment = " ".join(thepo.getlocations()) 609 markedpos.append(thepo)
610 for thepo in self.units: 611 id = thepo.getid() 612 if thepo.isheader() and not thepo.getlocations(): 613 # header msgids shouldn't be merged... 614 uniqueunits.append(thepo) 615 elif id in id_dict: 616 if duplicatestyle == "merge": 617 if id: 618 id_dict[id].merge(thepo) 619 else: 620 addcomment(thepo) 621 uniqueunits.append(thepo) 622 elif duplicatestyle == "msgctxt": 623 origpo = id_dict[id] 624 if origpo not in markedpos: 625 gpo.po_message_set_msgctxt(origpo._gpo_message, " ".join(origpo.getlocations())) 626 markedpos.append(thepo) 627 gpo.po_message_set_msgctxt(thepo._gpo_message, " ".join(thepo.getlocations())) 628 uniqueunits.append(thepo) 629 else: 630 if not id: 631 if duplicatestyle == "merge": 632 addcomment(thepo) 633 else: 634 gpo.po_message_set_msgctxt(thepo._gpo_message, " ".join(thepo.getlocations())) 635 id_dict[id] = thepo 636 uniqueunits.append(thepo) 637 new_gpo_memory_file = gpo.po_file_create() 638 new_gpo_message_iterator = gpo.po_message_iterator(new_gpo_memory_file, None) 639 for unit in uniqueunits: 640 gpo.po_message_insert(new_gpo_message_iterator, unit._gpo_message) 641 gpo.po_message_iterator_free(self._gpo_message_iterator) 642 self._gpo_message_iterator = new_gpo_message_iterator 643 self._gpo_memory_file = new_gpo_memory_file 644 self.units = uniqueunits
645
646 - def __str__(self):
647 648 def obsolete_workaround(): 649 # Remove all items that are not output by msgmerge when a unit is obsolete. This is a work 650 # around for bug in libgettextpo 651 # FIXME Do version test in case they fix this bug 652 for unit in self.units: 653 if unit.isobsolete(): 654 gpo.po_message_set_extracted_comments(unit._gpo_message, "") 655 location = gpo.po_message_filepos(unit._gpo_message, 0) 656 while location: 657 gpo.po_message_remove_filepos(unit._gpo_message, 0) 658 location = gpo.po_message_filepos(unit._gpo_message, 0)
659 outputstring = "" 660 if self._gpo_memory_file: 661 obsolete_workaround() 662 f, fname = tempfile.mkstemp(prefix='translate', suffix='.po') 663 os.close(f) 664 self._gpo_memory_file = gpo.po_file_write_v2(self._gpo_memory_file, fname, xerror_handler) 665 f = open(fname) 666 outputstring = f.read() 667 f.close() 668 os.remove(fname) 669 return outputstring 670
671 - def isempty(self):
672 """Returns True if the object doesn't contain any translation units.""" 673 if len(self.units) == 0: 674 return True 675 # Skip the first unit if it is a header. 676 if self.units[0].isheader(): 677 units = self.units[1:] 678 else: 679 units = self.units 680 681 for unit in units: 682 if not unit.isblank() and not unit.isobsolete(): 683 return False 684 return True
685
686 - def parse(self, input):
687 if hasattr(input, 'name'): 688 self.filename = input.name 689 elif not getattr(self, 'filename', ''): 690 self.filename = '' 691 692 if hasattr(input, "read"): 693 posrc = input.read() 694 input.close() 695 input = posrc 696 697 needtmpfile = not os.path.isfile(input) 698 if needtmpfile: 699 # This is not a file - we write the string to a temporary file 700 fd, fname = tempfile.mkstemp(prefix='translate', suffix='.po') 701 os.write(fd, input) 702 input = fname 703 os.close(fd) 704 705 self._gpo_memory_file = gpo.po_file_read_v3(input, xerror_handler) 706 if self._gpo_memory_file is None: 707 print >> sys.stderr, "Error:" 708 709 if needtmpfile: 710 os.remove(input) 711 712 self.units = [] 713 # Handle xerrors here 714 self._header = gpo.po_file_domain_header(self._gpo_memory_file, None) 715 if self._header: 716 charset = gpo.po_header_field(self._header, "Content-Type") 717 if charset: 718 charset = re.search("charset=([^\\s]+)", charset).group(1) 719 self._encoding = encodingToUse(charset) 720 self._gpo_message_iterator = gpo.po_message_iterator(self._gpo_memory_file, None) 721 newmessage = gpo.po_next_message(self._gpo_message_iterator) 722 while newmessage: 723 newunit = pounit(gpo_message=newmessage, encoding=self._encoding) 724 self.addunit(newunit, new=False) 725 newmessage = gpo.po_next_message(self._gpo_message_iterator) 726 self._free_iterator()
727
728 - def __del__(self):
729 # We currently disable this while we still get segmentation faults. 730 # Note that this is definitely leaking memory because of this. 731 return 732 self._free_iterator() 733 if self._gpo_memory_file is not None: 734 gpo.po_file_free(self._gpo_memory_file) 735 self._gpo_memory_file = None
736
737 - def _free_iterator(self):
738 # We currently disable this while we still get segmentation faults. 739 # Note that this is definitely leaking memory because of this. 740 return 741 if self._gpo_message_iterator is not None: 742 gpo.po_message_iterator_free(self._gpo_message_iterator) 743 self._gpo_message_iterator = None
744