Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/Pootle-1.2.1/Pootle/pootlefile.py
diff options
context:
space:
mode:
authorSayamindu Dasgupta <sayamindu@gmail.com>2009-02-23 15:42:17 (GMT)
committer Sayamindu Dasgupta <sayamindu@gmail.com>2009-02-23 15:42:17 (GMT)
commit8320bddbaed2d83f4bb90a1b83150be8afaa56e3 (patch)
treee27632704a95cd66d11fa713e85bfb59e9f8e04a /Pootle-1.2.1/Pootle/pootlefile.py
Initial pull of pristine upstream sources
Diffstat (limited to 'Pootle-1.2.1/Pootle/pootlefile.py')
-rw-r--r--Pootle-1.2.1/Pootle/pootlefile.py748
1 files changed, 748 insertions, 0 deletions
diff --git a/Pootle-1.2.1/Pootle/pootlefile.py b/Pootle-1.2.1/Pootle/pootlefile.py
new file mode 100644
index 0000000..14571a4
--- /dev/null
+++ b/Pootle-1.2.1/Pootle/pootlefile.py
@@ -0,0 +1,748 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2004-2006 Zuza Software Foundation
+#
+# This file is part of translate.
+#
+# translate is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# translate is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with translate; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""manages a translation file and its associated files"""
+
+from translate.storage import base
+from translate.storage import po
+from translate.storage.poheader import tzstring
+from translate.storage import xliff
+from translate.storage import factory
+from translate.filters import checks
+from translate.misc.multistring import multistring
+from Pootle import __version__, request_cache
+from Pootle import statistics
+from jToolkit import timecache
+from jToolkit import glock
+import time
+import os
+import bisect
+import weakref
+import util
+
+_UNIT_CHECKER = checks.UnitChecker()
+
+suggestion_source_index = weakref.WeakKeyDictionary()
+
+def build_map(store, property):
+ unit_map = {}
+ for unit in store.units:
+ key = property(unit)
+ if key not in unit_map:
+ unit_map[key] = []
+ unit_map[key].append(unit)
+ return unit_map
+
+def build_index(store, index, property):
+ if store not in index:
+ index[store] = build_map(store, property)
+ return index[store]
+
+def get_source_index(store):
+ return build_index(store, suggestion_source_index, lambda unit: unit.source)
+
+class LockedFile:
+ """locked interaction with a filesystem file"""
+ #Locking is disabled for now since it impacts performance negatively and was
+ #not complete yet anyway. Reverse svn revision 5271 to regain the locking
+ #code here.
+ def __init__(self, filename):
+ self.filename = filename
+ self.lock = None
+
+ def initlock(self):
+ self.lock = glock.GlobalLock(self.filename + os.extsep + "lock")
+
+ def dellock(self):
+ del self.lock
+ self.lock = None
+
+ def readmodtime(self):
+ """returns the modification time of the file (locked operation)"""
+ return statistics.getmodtime(self.filename)
+
+ def getcontents(self):
+ """returns modtime, contents tuple (locked operation)"""
+ pomtime = statistics.getmodtime(self.filename)
+ fp = open(self.filename, 'r')
+ filecontents = fp.read()
+ fp.close()
+ return pomtime, filecontents
+
+ def writecontents(self, contents):
+ """writes contents to file, returning modification time (locked operation)"""
+ f = open(self.filename, 'w')
+ f.write(contents)
+ f.close()
+ pomtime = statistics.getmodtime(self.filename)
+ return pomtime
+
+class pootleassigns:
+ """this represents the assignments for a file"""
+ def __init__(self, basefile):
+ """constructs assignments object for the given file"""
+ # TODO: try and remove circular references between basefile and this class
+ self.basefile = basefile
+ self.assignsfilename = self.basefile.filename + os.extsep + "assigns"
+ self.getassigns()
+
+ def getassigns(self):
+ """reads the assigns if neccessary or returns them from the cache"""
+ if os.path.exists(self.assignsfilename):
+ self.assigns = self.readassigns()
+ else:
+ self.assigns = {}
+ return self.assigns
+
+ def readassigns(self):
+ """reads the assigns from the associated assigns file, returning the assigns
+ the format is a number of lines consisting of
+ username: action: itemranges
+ where itemranges is a comma-separated list of item numbers or itemranges like 3-5
+ e.g. pootlewizz: review: 2-99,101"""
+ assignsmtime = statistics.getmodtime(self.assignsfilename)
+ if assignsmtime == getattr(self, "assignsmtime", None):
+ return
+ assignsfile = open(self.assignsfilename, "r")
+ assignsstring = assignsfile.read()
+ assignsfile.close()
+ poassigns = {}
+ itemcount = len(getattr(self, "stats", {}).get("total", []))
+ for line in assignsstring.split("\n"):
+ if not line.strip():
+ continue
+ if not line.count(":") == 2:
+ print "invalid assigns line in %s: %r" % (self.assignsfilename, line)
+ continue
+ username, action, itemranges = line.split(":", 2)
+ username, action = username.strip().decode('utf-8'), action.strip().decode('utf-8')
+ if not username in poassigns:
+ poassigns[username] = {}
+ userassigns = poassigns[username]
+ if not action in userassigns:
+ userassigns[action] = []
+ items = userassigns[action]
+ for itemrange in itemranges.split(","):
+ if "-" in itemrange:
+ if not itemrange.count("-") == 1:
+ print "invalid assigns range in %s: %r (from line %r)" % (self.assignsfilename, itemrange, line)
+ continue
+ itemstart, itemstop = [int(item.strip()) for item in itemrange.split("-", 1)]
+ items.extend(range(itemstart, itemstop+1))
+ else:
+ item = int(itemrange.strip())
+ items.append(item)
+ if itemcount:
+ items = [item for item in items if 0 <= item < itemcount]
+ userassigns[action] = items
+ return poassigns
+
+ def assignto(self, item, username, action):
+ """assigns the item to the given username for the given action"""
+ userassigns = self.assigns.setdefault(username, {})
+ items = userassigns.setdefault(action, [])
+ if item not in items:
+ items.append(item)
+ self.saveassigns()
+
+ def unassign(self, item, username=None, action=None):
+ """removes assignments of the item to the given username (or all users) for the given action (or all actions)"""
+ if username is None:
+ usernames = self.assigns.keys()
+ else:
+ usernames = [username]
+ for username in usernames:
+ userassigns = self.assigns.setdefault(username, {})
+ if action is None:
+ itemlist = [userassigns.get(action, []) for action in userassigns]
+ else:
+ itemlist = [userassigns.get(action, [])]
+ for items in itemlist:
+ if item in items:
+ items.remove(item)
+ self.saveassigns()
+
+ def saveassigns(self):
+ """saves the current assigns to file"""
+ # assumes self.assigns is up to date
+ assignstrings = []
+ usernames = self.assigns.keys()
+ usernames.sort()
+ for username in usernames:
+ actions = self.assigns[username].keys()
+ actions.sort()
+ for action in actions:
+ items = self.assigns[username][action]
+ items.sort()
+ if items:
+ lastitem = None
+ rangestart = None
+ assignstring = "%s: %s: " % (username.encode('utf-8'), action.encode('utf-8'))
+ for item in items:
+ if item - 1 == lastitem:
+ if rangestart is None:
+ rangestart = lastitem
+ else:
+ if rangestart is not None:
+ assignstring += "-%d" % lastitem
+ rangestart = None
+ if lastitem is None:
+ assignstring += "%d" % item
+ else:
+ assignstring += ",%d" % item
+ lastitem = item
+ if rangestart is not None:
+ assignstring += "-%d" % lastitem
+ assignstrings.append(assignstring + "\n")
+ assignsfile = open(self.assignsfilename, "w")
+ assignsfile.writelines(assignstrings)
+ assignsfile.close()
+
+ def getunassigned(self, action=None):
+ """gets all strings that are unassigned (for the given action if given)"""
+ unassigneditems = range(0, self.basefile.statistics.getitemslen())
+ self.assigns = self.getassigns()
+ for username in self.assigns:
+ if action is not None:
+ assigneditems = self.assigns[username].get(action, [])
+ else:
+ assigneditems = []
+ for action, actionitems in self.assigns[username].iteritems():
+ assigneditems += actionitems
+ unassigneditems = [item for item in unassigneditems if item not in assigneditems]
+ return unassigneditems
+
+ def finditems(self, search):
+ """returns items that match the .assignedto and/or .assignedaction criteria in the searchobject"""
+ # search.assignedto == [None] means assigned to nobody
+ if search.assignedto == [None]:
+ assignitems = self.getunassigned(search.assignedaction)
+ else:
+ # filter based on assign criteria
+ assigns = self.getassigns()
+ if search.assignedto:
+ usernames = [search.assignedto]
+ else:
+ usernames = assigns.iterkeys()
+ assignitems = []
+ for username in usernames:
+ if search.assignedaction:
+ actionitems = assigns[username].get(search.assignedaction, [])
+ assignitems.extend(actionitems)
+ else:
+ for actionitems in assigns[username].itervalues():
+ assignitems.extend(actionitems)
+ return assignitems
+
+def member(sorted_set, element):
+ """Check whether element appears in sorted_set."""
+ pos = bisect.bisect_left(sorted_set, element)
+ if pos < len(sorted_set):
+ return sorted_set[pos] == element
+ else:
+ return False
+
+def intersect(set_a, set_b):
+ """Find the intersection of the sorted sets set_a and set_b."""
+ # If both set_a and set_b have elements
+ if len(set_b) != 0 and len(set_a) != 0:
+ # Find the position of the element in set_a that is at least
+ # as large as the minimum element in set_b.
+ start_a = bisect.bisect_left(set_a, set_b[0])
+ # For each element in set_a...
+ for element in set_a[start_a:]:
+ # ...which is also in set_b...
+ if member(set_b, element):
+ yield element
+
+def make_class(base_class):
+ class pootlefile(base_class):
+ """this represents a pootle-managed file and its associated files"""
+ x_generator = "Pootle %s" % __version__.ver
+ def __init__(self, project=None, pofilename=None):
+ if pofilename:
+ self.__class__.__bases__ = (factory.getclass(pofilename),)
+ super(pootlefile, self).__init__()
+ self.pofilename = pofilename
+ if project is None:
+ from Pootle import projects
+ self.project = projects.DummyProject(None)
+ self.checker = None
+ self.filename = self.pofilename
+ else:
+ self.project = project
+ self.checker = self.project.checker
+ self.filename = os.path.join(self.project.podir, self.pofilename)
+
+ self.lockedfile = LockedFile(self.filename)
+ # we delay parsing until it is required
+ self.pomtime = None
+ self.assigns = None
+
+ self.pendingfilename = self.filename + os.extsep + "pending"
+ self.pendingfile = None
+ self.pomtime = self.lockedfile.readmodtime()
+ self.statistics = statistics.pootlestatistics(self)
+ self.tmfilename = self.filename + os.extsep + "tm"
+ # we delay parsing until it is required
+ self.pomtime = None
+ self.tracker = timecache.timecache(20*60)
+ self._total = util.undefined # self.statistics.getstats()["total"]
+
+ @util.lazy('_total')
+ def _get_total(self):
+ return self.statistics.getstats()["total"]
+
+ total = property(_get_total)
+
+ def reset_statistics(self):
+ self.statistics = statistics.pootlestatistics(self)
+
+ def parsestring(cls, storestring):
+ newstore = cls()
+ newstore.parse(storestring)
+ return newstore
+ parsestring = classmethod(parsestring)
+
+ def parsefile(cls, storefile):
+ """Reads the given file (or opens the given filename) and parses back to an object"""
+ if isinstance(storefile, basestring):
+ storefile = open(storefile, "r")
+ if "r" in getattr(storefile, "mode", "r"):
+ storestring = storefile.read()
+ else:
+ storestring = ""
+ return cls.parsestring(storestring)
+ parsefile = classmethod(parsefile)
+
+ def getheaderplural(self):
+ """returns values for nplural and plural values. It tries to see if the
+ file has it specified (in a po header or similar)."""
+ try:
+ return super(pootlefile, self).getheaderplural()
+ except AttributeError:
+ return None, None
+
+ def updateheaderplural(self, *args, **kwargs):
+ """updates the file header. If there is an updateheader function in the
+ underlying store it will be delegated there."""
+ try:
+ super(pootlefile, self).updateheaderplural(*args, **kwargs)
+ except AttributeError:
+ pass
+
+ def updateheader(self, **kwargs):
+ """updates the file header. If there is an updateheader function in the
+ underlying store it will be delegated there."""
+ try:
+ super(pootlefile, self).updateheader(**kwargs)
+ except AttributeError:
+ pass
+
+ def readpendingfile(self):
+ """reads and parses the pending file corresponding to this file"""
+ if os.path.exists(self.pendingfilename):
+ inputfile = open(self.pendingfilename, "r")
+ self.pendingfile = factory.getobject(inputfile, ignore=".pending")
+ else:
+ self.pendingfile = po.pofile()
+
+ def savependingfile(self):
+ """saves changes to disk..."""
+ output = str(self.pendingfile)
+ outputfile = open(self.pendingfilename, "w")
+ outputfile.write(output)
+ outputfile.close()
+
+ def readtmfile(self):
+ """reads and parses the tm file corresponding to this file"""
+ if os.path.exists(self.tmfilename):
+ tmmtime = statistics.getmodtime(self.tmfilename)
+ if tmmtime == getattr(self, "tmmtime", None):
+ return
+ inputfile = open(self.tmfilename, "r")
+ self.tmmtime, self.tmfile = tmmtime, factory.getobject(inputfile, ignore=".tm")
+ else:
+ self.tmfile = po.pofile()
+
+ def getsuggestions(self, item):
+ """find all the suggestion items submitted for the given item"""
+ unit = self.getitem(item)
+ if isinstance(unit, xliff.xliffunit):
+ return unit.getalttrans()
+ else:
+ self.readpendingfile()
+ return get_source_index(self.pendingfile).get(unit.source, [])
+
+ def addsuggestion(self, item, suggtarget, username):
+ """adds a new suggestion for the given item"""
+ unit = self.getitem(item)
+ if isinstance(unit, xliff.xliffunit):
+ if isinstance(suggtarget, list) and (len(suggtarget) > 0):
+ suggtarget = suggtarget[0]
+ unit.addalttrans(suggtarget, origin=username)
+ self.statistics.reclassifyunit(item)
+ self.savepofile()
+ return
+ self.readpendingfile()
+ newpo = unit.copy()
+ if username is not None:
+ newpo.msgidcomments.append('"_: suggested by %s\\n"' % username)
+ newpo.target = suggtarget
+ newpo.markfuzzy(False)
+ self.pendingfile.addunit(newpo)
+ self.savependingfile()
+ self.statistics.reclassifyunit(item)
+
+ def deletesuggestion(self, item, suggitem, newtrans=None):
+ """removes the suggestion from the pending file"""
+ unit = self.getitem(item)
+ if hasattr(unit, "xmlelement"):
+ suggestions = self.getsuggestions(item)
+ unit.delalttrans(suggestions[suggitem])
+ self.savepofile()
+ else:
+ self.readpendingfile()
+ # Update the suggestion index
+ get_source_index(self.pendingfile)[unit.source] = [unit for unit in get_source_index(self.pendingfile)[unit.source] if unit.target != newtrans]
+ # TODO: remove the suggestion in a less brutal manner
+ pendingitems = [pendingitem for pendingitem, suggestpo in enumerate(self.pendingfile.units) if suggestpo.source == unit.source]
+ try:
+ pendingitem = pendingitems[suggitem]
+ del self.pendingfile.units[pendingitem]
+ self.savependingfile()
+ except IndexError:
+ pass # TODO: Print a warning for the user.
+ self.statistics.reclassifyunit(item)
+
+ def getsuggester(self, item, suggitem):
+ """returns who suggested the given item's suggitem if recorded, else None"""
+ unit = self.getsuggestions(item)[suggitem]
+ if hasattr(unit, "xmlelement"):
+ return unit.xmlelement.get("origin")
+
+ for msgidcomment in unit.msgidcomments:
+ if msgidcomment.find("suggested by ") != -1:
+ suggestedby = po.unquotefrompo([msgidcomment]).replace("_:", "", 1).replace("suggested by ", "", 1).strip()
+ return suggestedby
+ return None
+
+ def gettmsuggestions(self, item):
+ """find all the tmsuggestion items submitted for the given item"""
+ self.readtmfile()
+ unit = self.getitem(item)
+ locations = unit.getlocations()
+ # TODO: review the matching method
+ # Can't simply use the location index, because we want multiple matches
+ suggestpos = [suggestpo for suggestpo in self.tmfile.units if suggestpo.getlocations() == locations]
+ return suggestpos
+
+ def track(self, item, message):
+ """sets the tracker message for the given item"""
+ self.tracker[item] = message
+
+ def readpofile(self):
+ """reads and parses the main file"""
+ # make sure encoding is reset so it is read from the file
+ self.encoding = None
+ self.units = []
+ self._total = util.undefined
+ pomtime, filecontents = self.lockedfile.getcontents()
+ # note: we rely on this not resetting the filename, which we set earlier, when given a string
+ self.parse(filecontents)
+ self.pomtime = pomtime
+ self.reset_statistics()
+
+ def savepofile(self):
+ """saves changes to the main file to disk..."""
+ output = str(self)
+ self.pomtime = self.lockedfile.writecontents(output)
+
+ def pofreshen(self):
+ """makes sure we have a freshly parsed pofile
+
+ @return: True if the file was freshened, False otherwise"""
+ try:
+ if self.pomtime != self.lockedfile.readmodtime():
+ self.readpofile()
+ return True
+ except OSError, e:
+ # If this exception is not triggered by a bad
+ # symlink, then we have a missing file on our hands...
+ if not os.path.islink(self.filename):
+ # ...and thus we rescan our files to get rid of the missing filename
+ self.project.scanpofiles()
+ else:
+ print "%s is a broken symlink" % (self.filename,)
+ return False
+
+ def getoutput(self):
+ """returns pofile output"""
+ self.pofreshen()
+ return super(pootlefile, self).getoutput()
+
+ def updateunit(self, item, newvalues, userprefs, languageprefs):
+ """updates a translation with a new target value"""
+ self.pofreshen()
+ unit = self.getitem(item)
+
+ if newvalues.has_key("target"):
+ unit.target = newvalues["target"]
+ if newvalues.has_key("fuzzy"):
+ unit.markfuzzy(newvalues["fuzzy"])
+ if newvalues.has_key("translator_comments"):
+ unit.removenotes()
+ if newvalues["translator_comments"]:
+ unit.addnote(newvalues["translator_comments"])
+
+ if isinstance(self, po.pofile):
+ po_revision_date = time.strftime("%Y-%m-%d %H:%M") + tzstring()
+ headerupdates = {
+ "PO_Revision_Date": po_revision_date,
+ "Language": self.project.languagecode,
+ "X_Generator": self.x_generator,
+ }
+ if userprefs:
+ if getattr(userprefs, "name", None) and getattr(userprefs, "email", None):
+ headerupdates["Last_Translator"] = "%s <%s>" % (userprefs.name, userprefs.email)
+ # We are about to insert a header. This changes the structure of the PO file and thus
+ # the total array which lists the editable units. We want to force this array to be
+ # reloaded, so we simply set it to undefined.
+ if self.header() is None:
+ self._total = util.undefined
+ request_cache.reset()
+ self.updateheader(add=True, **headerupdates)
+ if languageprefs:
+ nplurals = getattr(languageprefs, "nplurals", None)
+ pluralequation = getattr(languageprefs, "pluralequation", None)
+ if nplurals and pluralequation:
+ self.updateheaderplural(nplurals, pluralequation)
+ # If we didn't add a header, savepofile doesn't have to reset the stats,
+ # since reclassifyunit will do. This gives us a little speed boost for
+ # the common case.
+ self.savepofile()
+ self.statistics.reclassifyunit(item)
+
+ def getitem(self, item):
+ """Returns a single unit based on the item number."""
+ return self.units[self.total[item]]
+
+ def iteritems(self, search, lastitem=None):
+ """iterates through the items in this pofile starting after the given lastitem, using the given search"""
+ def get_min(lastitem):
+ if lastitem is None:
+ return 0
+ else:
+ return lastitem + 1
+
+ def narrow_to_last_item_range(translatables, lastitem):
+ return translatables[get_min(lastitem):]
+
+ def narrow_to_assigns(translatables, search):
+ if search.assignedto or search.assignedaction:
+ assignitems = self.getassigns().finditems(search)
+ return list(intersect(assignitems, translatables))
+ else:
+ return translatables
+
+ def narrow_to_matches(translatables, search):
+ if search.matchnames:
+ stats = self.statistics.getstats()
+ matches = reduce(set.__or__, (set(stats[matchname]) for matchname in search.matchnames if matchname in stats))
+ return intersect(sorted(matches), translatables)
+ else:
+ return translatables
+
+ translatables = self.total
+
+ # To get the items to iterate, we
+ # 1. filter translatables to the range of elements after lastitem,
+ # 2. filter translatables according to assignments,
+ # 3. filter translatables according to the quality checks (which
+ # are contained in search.matches)
+ new_translatables = narrow_to_matches(
+ narrow_to_assigns(
+ narrow_to_last_item_range(translatables, lastitem),
+ search),
+ search)
+
+ # translatables contains the indices into the store of the units. We need to
+ # know what indices of these indices are in translatables itself. Since
+ # translatables is sorted, we simply need to perform binary searches to
+ # get the answer.
+ return (bisect.bisect_left(translatables, item) for item in new_translatables)
+
+ def matchitems(self, newfile, uselocations=False):
+ """matches up corresponding items in this pofile with the given newfile, and returns tuples of matching poitems (None if no match found)"""
+ if not hasattr(self, "sourceindex"):
+ self.makeindex()
+ if not hasattr(newfile, "sourceindex"):
+ newfile.makeindex()
+ matches = []
+ for newpo in newfile.units:
+ if newpo.isheader():
+ continue
+ foundid = False
+ if uselocations:
+ newlocations = newpo.getlocations()
+ mergedlocations = set()
+ for location in newlocations:
+ if location in mergedlocations:
+ continue
+ if location in self.locationindex:
+ oldpo = self.locationindex[location]
+ if oldpo is not None:
+ foundid = True
+ matches.append((oldpo, newpo))
+ mergedlocations.add(location)
+ continue
+ if not foundid:
+ # We can't use the multistring, because it might contain more than two
+ # entries in a PO xliff file. Rather use the singular.
+ source = unicode(newpo.source)
+ if source in self.sourceindex:
+ oldpo = self.sourceindex[source]
+ matches.append((oldpo, newpo))
+ else:
+ matches.append((None, newpo))
+ # find items that have been removed
+ matcheditems = set(oldpo for oldpo, newpo in matches if oldpo)
+ for oldpo in self.units:
+ if not oldpo in matcheditems:
+ matches.append((oldpo, None))
+ return matches
+
+ def getassigns(self):
+ if self.assigns is None:
+ self.assigns = pootleassigns(self)
+ return self.assigns
+
+ def mergeitem(self, po_position, oldpo, newpo, username, suggest=False):
+ """merges any changes from newpo into oldpo"""
+ unchanged = oldpo.target == newpo.target
+ if not suggest and (not oldpo.target or not newpo.target or oldpo.isheader() or newpo.isheader() or unchanged):
+ oldpo.merge(newpo)
+ else:
+ if oldpo in po_position:
+ strings = getattr(newpo.target, "strings", [newpo.target])
+ self.addsuggestion(po_position[oldpo], strings, username)
+ else:
+ raise KeyError("Could not find item for merge")
+
+ def mergefile(self, newfile, username, allownewstrings=True, suggestions=False):
+ """make sure each msgid is unique ; merge comments etc from duplicates into original"""
+ self.makeindex()
+ translatables = (self.units[index] for index in self.total)
+ po_position = dict((unit, position) for position, unit in enumerate(translatables))
+ matches = self.matchitems(newfile)
+ for oldpo, newpo in matches:
+ if suggestions:
+ if oldpo and newpo:
+ self.mergeitem(po_position, oldpo, newpo, username, suggest=True)
+ continue
+
+ if oldpo is None:
+ if allownewstrings:
+ self.addunit(self.UnitClass.buildfromunit(newpo))
+ elif newpo is None:
+ # TODO: mark the old one as obsolete
+ pass
+ else:
+ self.mergeitem(po_position, oldpo, newpo, username)
+ # we invariably want to get the ids (source locations) from the newpo
+ if hasattr(newpo, "sourcecomments"):
+ oldpo.sourcecomments = newpo.sourcecomments
+
+ if not isinstance(newfile, po.pofile) or suggestions:
+ #TODO: We don't support updating the header yet.
+ self.savepofile()
+ return
+
+ #Let's update selected header entries. Only the ones listed below, and ones
+ #that are empty in self can be updated. The check in header_order is just
+ #a basic sanity check so that people don't insert garbage.
+ updatekeys = ['Content-Type',
+ 'POT-Creation-Date',
+ 'Last-Translator',
+ 'Project-Id-Version',
+ 'PO-Revision-Date',
+ 'Language-Team']
+ headerstoaccept = {}
+ ownheader = self.parseheader()
+ for (key, value) in newfile.parseheader().items():
+ if key in updatekeys or (not key in ownheader or not ownheader[key]) and key in po.pofile.header_order:
+ headerstoaccept[key] = value
+ self.updateheader(add=True, **headerstoaccept)
+
+ #Now update the comments above the header:
+ header = self.header()
+ newheader = newfile.header()
+ if header is None and not newheader is None:
+ header = self.UnitClass("", encoding=self.encoding)
+ header.target = ""
+ if header:
+ header._initallcomments(blankall=True)
+ if newheader:
+ for i in range(len(header.allcomments)):
+ header.allcomments[i].extend(newheader.allcomments[i])
+
+ self.savepofile()
+ return pootlefile
+
+_pootlefile_classes = {}
+
+# We want to extend the functionality of translation stores with some
+# Pootle-specific functionality, but we still want them to act like
+# translation stores. The clean way to do this, is to store a reference
+# to a translation store inside a "pootlefile" class and to delegate
+# if needed to the store. This was done initially through __getattr__
+# and __setattr__, although it proved to be rather slow (which made
+# a difference for large sets of translation files). This is now
+# achieved through inheritance. When we have to load a translation file,
+# we get hold of its corresponding translation store class. Then we
+# see whether there is a class which contains pootlefile functionality
+# and which derives from the translation store class. If there isn't
+# we invoke make_class to create such a class. Then we return an
+# instance of this class to the user.
+def pootlefile(project=None, pofilename=None):
+ po_class = po.pofile
+ if pofilename != None:
+ po_class = factory.getclass(pofilename)
+ if po_class not in _pootlefile_classes:
+ _pootlefile_classes[po_class] = make_class(po_class)
+ return _pootlefile_classes[po_class](project, pofilename)
+
+class Search:
+ """an object containing all the searching information"""
+ def __init__(self, dirfilter=None, matchnames=[], assignedto=None, assignedaction=None, searchtext=None, searchfields=None):
+ if searchfields is None:
+ searchfields = ["source", "target"]
+ self.dirfilter = dirfilter
+ self.matchnames = matchnames
+ self.assignedto = assignedto
+ self.assignedaction = assignedaction
+ self.searchtext = searchtext
+ self.searchfields = searchfields
+
+ def copy(self):
+ """returns a copy of this search"""
+ return Search(self.dirfilter, self.matchnames, self.assignedto, self.assignedaction, self.searchtext, self.searchfields)
+