Package translate :: Package storage :: Module poxliff
[hide private]
[frames] | no frames]

Source Code for Module translate.storage.poxliff

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  # 
  4  # Copyright 2006-2009 Zuza Software Foundation 
  5  # 
  6  # This file is part of the Translate Toolkit. 
  7  # 
  8  # This program 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  # This program 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 this program; if not, see <http://www.gnu.org/licenses/>. 
 20   
 21  """XLIFF classes specifically suited for handling the PO representation in 
 22  XLIFF. 
 23   
 24  This way the API supports plurals as if it was a PO file, for example. 
 25  """ 
 26   
 27  from translate.storage import base, lisa, poheader, xliff 
 28  from translate.storage.placeables import general 
 29  from translate.misc.multistring import multistring 
 30  from lxml import etree 
 31  import re 
 32   
33 -def hasplurals(thing):
34 if not isinstance(thing, multistring): 35 return False 36 return len(thing.strings) > 1
37
38 -class PoXliffUnit(xliff.xliffunit):
39 """A class to specifically handle the plural units created from a po file.""" 40 41 rich_parsers = general.parsers 42
43 - def __init__(self, source=None, empty=False, encoding="UTF-8"):
44 self._rich_source = None 45 self._rich_target = None 46 self.units = [] 47 48 if empty: 49 return 50 51 if not hasplurals(source): 52 super(PoXliffUnit, self).__init__(source) 53 return 54 55 self.xmlelement = etree.Element(self.namespaced("group")) 56 self.xmlelement.set("restype", "x-gettext-plurals") 57 self.setsource(source)
58
59 - def __eq__(self, other):
60 if isinstance(other, PoXliffUnit): 61 if len(self.units) != len(other.units): 62 return False 63 if not super(PoXliffUnit, self).__eq__(other): 64 return False 65 for i in range(len(self.units)-1): 66 if not self.units[i+1] == other.units[i+1]: 67 return False 68 return True 69 if len(self.units) <= 1: 70 if isinstance(other, lisa.LISAunit): 71 return super(PoXliffUnit, self).__eq__(other) 72 else: 73 return self.source == other.source and self.target == other.target 74 return False
75 76 #XXX: We don't return language nodes correctly at the moment 77 # def getlanguageNodes(self): 78 # if not self.hasplural(): 79 # return super(PoXliffUnit, self).getlanguageNodes() 80 # else: 81 # return self.units[0].getlanguageNodes() 82
83 - def setsource(self, source, sourcelang="en"):
84 # TODO: consider changing from plural to singular, etc. 85 self._rich_source = None 86 if not hasplurals(source): 87 super(PoXliffUnit, self).setsource(source, sourcelang) 88 else: 89 target = self.target 90 for unit in self.units: 91 try: 92 self.xmlelement.remove(unit.xmlelement) 93 except xml.dom.NotFoundErr: 94 pass 95 self.units = [] 96 for s in source.strings: 97 newunit = xliff.xliffunit(s) 98 # newunit.namespace = self.namespace #XXX?necessary? 99 self.units.append(newunit) 100 self.xmlelement.append(newunit.xmlelement) 101 self.target = target
102 103 # We don't support any rich strings yet 104 multistring_to_rich = base.TranslationUnit.multistring_to_rich 105 rich_to_multistring = base.TranslationUnit.rich_to_multistring 106 107 rich_source = base.TranslationUnit.rich_source 108 rich_target = base.TranslationUnit.rich_target 109
110 - def getsource(self):
111 if not self.hasplural(): 112 return super(PoXliffUnit, self).getsource() 113 else: 114 strings = [] 115 strings.extend([unit.source for unit in self.units]) 116 return multistring(strings)
117 source = property(getsource, setsource) 118
119 - def settarget(self, text, lang='xx', append=False):
120 self._rich_target = None 121 if self.gettarget() == text: 122 return 123 if not self.hasplural(): 124 super(PoXliffUnit, self).settarget(text, lang, append) 125 return 126 if not isinstance(text, multistring): 127 text = multistring(text) 128 source = self.source 129 sourcel = len(source.strings) 130 targetl = len(text.strings) 131 if sourcel < targetl: 132 sources = source.strings + [source.strings[-1]] * (targetl - sourcel) 133 targets = text.strings 134 id = self.getid() 135 self.source = multistring(sources) 136 self.setid(id) 137 elif targetl < sourcel: 138 targets = text.strings + [""] * (sourcel - targetl) 139 else: 140 targets = text.strings 141 142 for i in range(len(self.units)): 143 self.units[i].target = targets[i]
144
145 - def gettarget(self):
146 if self.hasplural(): 147 strings = [unit.target for unit in self.units] 148 if strings: 149 return multistring(strings) 150 else: 151 return None 152 else: 153 return super(PoXliffUnit, self).gettarget()
154 155 target = property(gettarget, settarget) 156
157 - def addnote(self, text, origin=None, position="append"):
158 """Add a note specifically in a "note" tag""" 159 if isinstance(text, str): 160 text = text.decode("utf-8") 161 note = etree.SubElement(self.xmlelement, self.namespaced("note")) 162 note.text = text 163 if origin: 164 note.set("from", origin) 165 for unit in self.units[1:]: 166 unit.addnote(text, origin)
167
168 - def getnotes(self, origin=None):
169 #NOTE: We support both <context> and <note> tags in xliff files for comments 170 if origin == "translator": 171 notes = super(PoXliffUnit, self).getnotes("translator") 172 trancomments = self.gettranslatorcomments() 173 if notes == trancomments or trancomments.find(notes) >= 0: 174 notes = "" 175 elif notes.find(trancomments) >= 0: 176 trancomments = notes 177 notes = "" 178 trancomments = trancomments + notes 179 return trancomments 180 elif origin in ["programmer", "developer", "source code"]: 181 devcomments = super(PoXliffUnit, self).getnotes("developer") 182 autocomments = self.getautomaticcomments() 183 if devcomments == autocomments or autocomments.find(devcomments) >= 0: 184 devcomments = "" 185 elif devcomments.find(autocomments) >= 0: 186 autocomments = devcomments 187 devcomments = "" 188 return autocomments 189 else: 190 return super(PoXliffUnit, self).getnotes(origin)
191
192 - def markfuzzy(self, value=True):
193 super(PoXliffUnit, self).markfuzzy(value) 194 for unit in self.units[1:]: 195 unit.markfuzzy(value)
196
197 - def marktranslated(self):
198 super(PoXliffUnit, self).marktranslated() 199 for unit in self.units[1:]: 200 unit.marktranslated()
201
202 - def setid(self, id):
203 self.xmlelement.set("id", id) 204 if len(self.units) > 1: 205 for i in range(len(self.units)): 206 self.units[i].setid("%s[%d]" % (id, i))
207
208 - def getlocations(self):
209 """Returns all the references (source locations)""" 210 groups = self.getcontextgroups("po-reference") 211 references = [] 212 for group in groups: 213 sourcefile = "" 214 linenumber = "" 215 for (type, text) in group: 216 if type == "sourcefile": 217 sourcefile = text 218 elif type == "linenumber": 219 linenumber = text 220 assert sourcefile 221 if linenumber: 222 sourcefile = sourcefile + ":" + linenumber 223 references.append(sourcefile) 224 return references
225
226 - def getautomaticcomments(self):
227 """Returns the automatic comments (x-po-autocomment), which corresponds 228 to the #. style po comments.""" 229 def hasautocomment((type, text)): 230 return type == "x-po-autocomment"
231 groups = self.getcontextgroups("po-entry") 232 comments = [] 233 for group in groups: 234 commentpairs = filter(hasautocomment, group) 235 for (type, text) in commentpairs: 236 comments.append(text) 237 return "\n".join(comments)
238
239 - def gettranslatorcomments(self):
240 """Returns the translator comments (x-po-trancomment), which corresponds 241 to the # style po comments.""" 242 def hastrancomment((type, text)): 243 return type == "x-po-trancomment"
244 groups = self.getcontextgroups("po-entry") 245 comments = [] 246 for group in groups: 247 commentpairs = filter(hastrancomment, group) 248 for (type, text) in commentpairs: 249 comments.append(text) 250 return "\n".join(comments) 251
252 - def isheader(self):
253 return "gettext-domain-header" in (self.getrestype() or "")
254
255 - def istranslatable(self):
256 return super(PoXliffUnit, self).istranslatable() and not self.isheader()
257
258 - def createfromxmlElement(cls, element, namespace=None):
259 if element.tag.endswith("trans-unit"): 260 object = cls(None, empty=True) 261 object.xmlelement = element 262 object.namespace = namespace 263 return object 264 assert element.tag.endswith("group") 265 group = cls(None, empty=True) 266 group.xmlelement = element 267 group.namespace = namespace 268 units = list(element.iterdescendants(group.namespaced('trans-unit'))) 269 for unit in units: 270 subunit = xliff.xliffunit.createfromxmlElement(unit) 271 subunit.namespace = namespace 272 group.units.append(subunit) 273 return group
274 createfromxmlElement = classmethod(createfromxmlElement) 275
276 - def hasplural(self):
277 return self.xmlelement.tag == self.namespaced("group")
278 279
280 -class PoXliffFile(xliff.xlifffile, poheader.poheader):
281 """a file for the po variant of Xliff files""" 282 UnitClass = PoXliffUnit
283 - def __init__(self, *args, **kwargs):
284 if not "sourcelanguage" in kwargs: 285 kwargs["sourcelanguage"] = "en-US" 286 xliff.xlifffile.__init__(self, *args, **kwargs)
287
288 - def createfilenode(self, filename, sourcelanguage="en-US", datatype="po"):
289 # Let's ignore the sourcelanguage parameter opting for the internal 290 # one. PO files will probably be one language 291 return super(PoXliffFile, self).createfilenode(filename, sourcelanguage=self.sourcelanguage, datatype="po")
292
293 - def addheaderunit(self, target, filename):
294 unit = self.addsourceunit(target, filename, True) 295 unit.target = target 296 unit.xmlelement.set("restype", "x-gettext-domain-header") 297 unit.xmlelement.set("approved", "no") 298 lisa.setXMLspace(unit.xmlelement, "preserve") 299 return unit
300
301 - def addplural(self, source, target, filename, createifmissing=False):
302 """This method should now be unnecessary, but is left for reference""" 303 assert isinstance(source, multistring) 304 if not isinstance(target, multistring): 305 target = multistring(target) 306 sourcel = len(source.strings) 307 targetl = len(target.strings) 308 if sourcel < targetl: 309 sources = source.strings + [source.strings[-1]] * targetl - sourcel 310 targets = target.strings 311 else: 312 sources = source.strings 313 targets = target.strings 314 self._messagenum += 1 315 pluralnum = 0 316 group = self.creategroup(filename, True, restype="x-gettext-plural") 317 for (src, tgt) in zip(sources, targets): 318 unit = self.UnitClass(src) 319 unit.target = tgt 320 unit.setid("%d[%d]" % (self._messagenum, pluralnum)) 321 pluralnum += 1 322 group.append(unit.xmlelement) 323 self.units.append(unit) 324 325 if pluralnum < sourcel: 326 for string in sources[pluralnum:]: 327 unit = self.UnitClass(src) 328 unit.xmlelement.set("translate", "no") 329 unit.setid("%d[%d]" % (self._messagenum, pluralnum)) 330 pluralnum += 1 331 group.append(unit.xmlelement) 332 self.units.append(unit) 333 334 return self.units[-pluralnum]
335
336 - def parse(self, xml):
337 """Populates this object from the given xml string""" 338 #TODO: Make more robust 339 def ispluralgroup(node): 340 """determines whether the xml node refers to a getttext plural""" 341 return node.get("restype") == "x-gettext-plurals"
342 343 def isnonpluralunit(node): 344 """determindes whether the xml node contains a plural like id. 345 346 We want to filter out all the plural nodes, except the very first 347 one in each group. 348 """ 349 return re.match(r"\d+\[[123456]\]$", node.get("id") or "") is None
350 351 def pluralunits(pluralgroups): 352 for pluralgroup in pluralgroups: 353 yield self.UnitClass.createfromxmlElement(pluralgroup, namespace=self.namespace) 354 355 self.filename = getattr(xml, 'name', '') 356 if hasattr(xml, "read"): 357 xml.seek(0) 358 xmlsrc = xml.read() 359 xml = xmlsrc 360 self.document = etree.fromstring(xml).getroottree() 361 self.initbody() 362 root_node = self.document.getroot() 363 assert root_node.tag == self.namespaced(self.rootNode) 364 groups = root_node.iterdescendants(self.namespaced("group")) 365 pluralgroups = filter(ispluralgroup, groups) 366 termEntries = root_node.iterdescendants(self.namespaced(self.UnitClass.rootNode)) 367 368 singularunits = filter(isnonpluralunit, termEntries) 369 if len(singularunits) == 0: 370 return 371 pluralunit_iter = pluralunits(pluralgroups) 372 try: 373 nextplural = pluralunit_iter.next() 374 except StopIteration: 375 nextplural = None 376 377 for entry in singularunits: 378 term = self.UnitClass.createfromxmlElement(entry, namespace=self.namespace) 379 if nextplural and unicode(term.getid()) == ("%s[0]" % nextplural.getid()): 380 self.addunit(nextplural, new=False) 381 try: 382 nextplural = pluralunit_iter.next() 383 except StopIteration, i: 384 nextplural = None 385 else: 386 self.addunit(term, new=False) 387