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