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