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