Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(20)

Side by Side Diff: translate/storage/poxliff.py

Issue 65: xliff2po & po2xliff should handle context SVN Base: https://translate.svn.sourceforge.net/svnroot/translate/src/trunk/
Patch Set: A more complete patch, supporting CPO and pypo Created 1 year, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 # 3 #
4 # Copyright 2006-2007 Zuza Software Foundation 4 # Copyright 2006-2007 Zuza Software Foundation
5 # 5 #
6 # This file is part of translate. 6 # This file is part of translate.
7 # 7 #
8 # translate is free software; you can redistribute it and/or modify 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 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 10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version. 11 # (at your option) any later version.
12 # 12 #
13 # translate is distributed in the hope that it will be useful, 13 # translate is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details. 16 # GNU General Public License for more details.
17 # 17 #
18 # You should have received a copy of the GNU General Public License 18 # You should have received a copy of the GNU General Public License
19 # along with translate; if not, write to the Free Software 19 # along with translate; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 # 21 #
22 22
23 """An xliff file specifically suited for handling the po representation of 23 """An xliff file specifically suited for handling the po representation of
24 xliff. """ 24 xliff. """
25 25
26 from translate.storage import xliff 26 from translate.storage import xliff
27 from translate.storage import lisa 27 from translate.storage import lisa
28 from translate.storage import poheader 28 from translate.storage import poheader
29 from translate.misc.multistring import multistring 29 from translate.misc.multistring import multistring
30 from lxml import etree 30 from lxml import etree
31 import re 31 import re
32 32
33 def hasplurals(thing): 33 def hasplurals(thing):
34 if not isinstance(thing, multistring): 34 if not isinstance(thing, multistring):
35 return False 35 return False
36 return len(thing.strings) > 1 36 return len(thing.strings) > 1
37 37
38 class PoXliffUnit(xliff.xliffunit): 38 class PoXliffUnit(xliff.xliffunit):
39 """A class to specifically handle the plural units created from a po file."" " 39 """A class to specifically handle the plural units created from a po file."" "
40 def __init__(self, source, empty=False): 40 def __init__(self, source, empty=False):
41 self.units = [] 41 self.units = []
42 42
43 if empty: 43 if empty:
44 return 44 return
45 45
46 if not hasplurals(source): 46 if not hasplurals(source):
47 super(PoXliffUnit, self).__init__(source) 47 super(PoXliffUnit, self).__init__(source)
48 return 48 return
49 49
50 self.xmlelement = etree.Element(self.namespaced("group")) 50 self.xmlelement = etree.Element(self.namespaced("group"))
(...skipping 152 matching lines...) Show 10 above Show 10 below
203 203
204 def getautomaticcomments(self): 204 def getautomaticcomments(self):
205 """Returns the automatic comments (x-po-autocomment), which corresponds 205 """Returns the automatic comments (x-po-autocomment), which corresponds
206 to the #. style po comments.""" 206 to the #. style po comments."""
207 def hasautocomment((type, text)): 207 def hasautocomment((type, text)):
208 return type == "x-po-autocomment" 208 return type == "x-po-autocomment"
209 groups = self.getcontextgroups("po-entry") 209 groups = self.getcontextgroups("po-entry")
210 comments = [] 210 comments = []
211 for group in groups: 211 for group in groups:
212 commentpairs = filter(hasautocomment, group) 212 commentpairs = filter(hasautocomment, group)
213 for (type, text) in commentpairs: 213 for (type, text) in commentpairs:
214 comments.append(text) 214 comments.append(text)
215 return "\n".join(comments) 215 return "\n".join(comments)
216 216
217 def gettranslatorcomments(self): 217 def gettranslatorcomments(self):
218 """Returns the translator comments (x-po-trancomment), which corresponds 218 """Returns the translator comments (x-po-trancomment), which corresponds
219 to the # style po comments.""" 219 to the # style po comments."""
220 def hastrancomment((type, text)): 220 def hastrancomment((type, text)):
221 return type == "x-po-trancomment" 221 return type == "x-po-trancomment"
222 groups = self.getcontextgroups("po-entry") 222 groups = self.getcontextgroups("po-entry")
223 comments = [] 223 comments = []
224 for group in groups: 224 for group in groups:
225 commentpairs = filter(hastrancomment, group) 225 commentpairs = filter(hastrancomment, group)
226 for (type, text) in commentpairs: 226 for (type, text) in commentpairs:
227 comments.append(text) 227 comments.append(text)
228 return "\n".join(comments) 228 return "\n".join(comments)
229 229
230 def isheader(self): 230 def isheader(self):
231 return "gettext-domain-header" in (self.getrestype() or "") 231 return "gettext-domain-header" in (self.getrestype() or "")
232 232
233 def createfromxmlElement(cls, element, namespace=None): 233 def createfromxmlElement(cls, element, namespace=None):
234 if element.tag.endswith("trans-unit"): 234 if element.tag.endswith("trans-unit"):
235 object = cls(None, empty=True) 235 object = cls(None, empty=True)
236 object.xmlelement = element 236 object.xmlelement = element
237 object.namespace = namespace 237 object.namespace = namespace
238 return object 238 return object
239 assert element.tag.endswith("group") 239 assert element.tag.endswith("group")
240 group = cls(None, empty=True) 240 group = cls(None, empty=True)
241 group.xmlelement = element 241 group.xmlelement = element
242 group.namespace = namespace 242 group.namespace = namespace
243 units = element.findall('.//%s' % group.namespaced('trans-unit')) 243 units = element.findall('.//%s' % group.namespaced('trans-unit'))
244 for unit in units: 244 for unit in units:
245 subunit = xliff.xliffunit.createfromxmlElement(unit) 245 subunit = xliff.xliffunit.createfromxmlElement(unit)
246 subunit.namespace = namespace 246 subunit.namespace = namespace
247 group.units.append(subunit) 247 group.units.append(subunit)
248 return group 248 return group
249 createfromxmlElement = classmethod(createfromxmlElement) 249 createfromxmlElement = classmethod(createfromxmlElement)
250 250
251 def hasplural(self): 251 def hasplural(self):
252 return self.xmlelement.tag == self.namespaced("group") 252 return self.xmlelement.tag == self.namespaced("group")
253
254 def getcontext_message(self):
255 """Returns the message context for this unit, if any.
256 This message context is intended to identify the message that
257 represents the PO msgctxt.
258 See http://bugs.locamotion.org/show_bug.cgi?id=258 for details.
259 """
260 groups = self.getcontextgroups("po-msgctxt")
261 context = ""
262 if groups:
263 # groups = [[(context-type, context_message)]]
264 context = groups[0][0][1]
265
266 return context
267
268 def setcontext_message(self, context):
269 """Sets the message context for this unit.
270 The message context will be set as the message which represents the
271 PO msgctxt in XLIFF.
272 See http://bugs.locamotion.org/show_bug.cgi?id=258 for details.
273 """
274 group_name = "po-msgctxt"
275 if self.getcontext_message():
276 self.delcontextgroup(group_name)
277
278 self.createcontextgroup(group_name, [("x-po-msgctxt", context)],
279 purpose="match information")
253 280
254 281
255 class PoXliffFile(xliff.xlifffile, poheader.poheader): 282 class PoXliffFile(xliff.xlifffile, poheader.poheader):
256 """a file for the po variant of Xliff files""" 283 """a file for the po variant of Xliff files"""
257 UnitClass = PoXliffUnit 284 UnitClass = PoXliffUnit
258 def __init__(self, *args, **kwargs): 285 def __init__(self, *args, **kwargs):
259 if not "sourcelanguage" in kwargs: 286 if not "sourcelanguage" in kwargs:
260 kwargs["sourcelanguage"] = "en-US" 287 kwargs["sourcelanguage"] = "en-US"
261 xliff.xlifffile.__init__(self, *args, **kwargs) 288 xliff.xlifffile.__init__(self, *args, **kwargs)
262 289
263 def createfilenode(self, filename, sourcelanguage="en-US", datatype="po"): 290 def createfilenode(self, filename, sourcelanguage="en-US", datatype="po"):
264 # Let's ignore the sourcelanguage parameter opting for the internal 291 # Let's ignore the sourcelanguage parameter opting for the internal
265 # one. PO files will probably be one language 292 # one. PO files will probably be one language
266 return super(PoXliffFile, self).createfilenode(filename, sourcelanguage= self.sourcelanguage, datatype="po") 293 return super(PoXliffFile, self).createfilenode(filename, sourcelanguage= self.sourcelanguage, datatype="po")
267 294
268 def addheaderunit(self, target, filename): 295 def addheaderunit(self, target, filename):
269 unit = self.addsourceunit(target, filename, True) 296 unit = self.addsourceunit(target, filename, True)
270 unit.target = target 297 unit.target = target
271 unit.xmlelement.set("restype", "x-gettext-domain-header") 298 unit.xmlelement.set("restype", "x-gettext-domain-header")
272 unit.xmlelement.set("approved", "no") 299 unit.xmlelement.set("approved", "no")
273 lisa.setXMLspace(unit.xmlelement, "preserve") 300 lisa.setXMLspace(unit.xmlelement, "preserve")
274 return unit 301 return unit
275 302
276 def addplural(self, source, target, filename, createifmissing=False): 303 def addplural(self, source, target, filename, createifmissing=False):
277 """This method should now be unnecessary, but is left for reference""" 304 """This method should now be unnecessary, but is left for reference"""
278 assert isinstance(source, multistring) 305 assert isinstance(source, multistring)
279 if not isinstance(target, multistring): 306 if not isinstance(target, multistring):
280 target = multistring(target) 307 target = multistring(target)
281 sourcel = len(source.strings) 308 sourcel = len(source.strings)
282 targetl = len(target.strings) 309 targetl = len(target.strings)
283 if sourcel < targetl: 310 if sourcel < targetl:
284 sources = source.strings + [source.strings[-1]] * targetl - sourcel 311 sources = source.strings + [source.strings[-1]] * targetl - sourcel
285 targets = target.strings 312 targets = target.strings
286 else: 313 else:
287 sources = source.strings 314 sources = source.strings
288 targets = target.strings 315 targets = target.strings
289 self._messagenum += 1 316 self._messagenum += 1
290 pluralnum = 0 317 pluralnum = 0
291 group = self.creategroup(filename, True, restype="x-gettext-plural") 318 group = self.creategroup(filename, True, restype="x-gettext-plural")
292 for (src, tgt) in zip(sources, targets): 319 for (src, tgt) in zip(sources, targets):
293 unit = self.UnitClass(src) 320 unit = self.UnitClass(src)
294 unit.target = tgt 321 unit.target = tgt
295 unit.setid("%d[%d]" % (self._messagenum, pluralnum)) 322 unit.setid("%d[%d]" % (self._messagenum, pluralnum))
296 pluralnum += 1 323 pluralnum += 1
297 group.append(unit.xmlelement) 324 group.append(unit.xmlelement)
298 self.units.append(unit) 325 self.units.append(unit)
299 326
300 if pluralnum < sourcel: 327 if pluralnum < sourcel:
301 for string in sources[pluralnum:]: 328 for string in sources[pluralnum:]:
302 unit = self.UnitClass(src) 329 unit = self.UnitClass(src)
303 unit.xmlelement.set("translate", "no") 330 unit.xmlelement.set("translate", "no")
304 unit.setid("%d[%d]" % (self._messagenum, pluralnum)) 331 unit.setid("%d[%d]" % (self._messagenum, pluralnum))
305 pluralnum += 1 332 pluralnum += 1
306 group.append(unit.xmlelement) 333 group.append(unit.xmlelement)
307 self.units.append(unit) 334 self.units.append(unit)
308 335
309 return self.units[-pluralnum] 336 return self.units[-pluralnum]
310 337
311 def parse(self, xml): 338 def parse(self, xml):
312 """Populates this object from the given xml string""" 339 """Populates this object from the given xml string"""
313 #TODO: Make more robust 340 #TODO: Make more robust
314 def ispluralgroup(node): 341 def ispluralgroup(node):
315 """determines whether the xml node refers to a getttext plural""" 342 """determines whether the xml node refers to a getttext plural"""
316 return node.get("restype") == "x-gettext-plurals" 343 return node.get("restype") == "x-gettext-plurals"
317 344
318 def isnonpluralunit(node): 345 def isnonpluralunit(node):
319 """determindes whether the xml node contains a plural like id. 346 """determindes whether the xml node contains a plural like id.
320 347
321 We want to filter out all the plural nodes, except the very first 348 We want to filter out all the plural nodes, except the very first
322 one in each group. 349 one in each group.
323 """ 350 """
324 return re.match(r"\d+\[[123456]\]$", node.get("id") or "") is None 351 return re.match(r"\d+\[[123456]\]$", node.get("id") or "") is None
325 352
326 def pluralunits(pluralgroups): 353 def pluralunits(pluralgroups):
327 for pluralgroup in pluralgroups: 354 for pluralgroup in pluralgroups:
328 yield self.UnitClass.createfromxmlElement(pluralgroup, namespace =self.namespace) 355 yield self.UnitClass.createfromxmlElement(pluralgroup, namespace =self.namespace)
329 356
330 self.filename = getattr(xml, 'name', '') 357 self.filename = getattr(xml, 'name', '')
331 if hasattr(xml, "read"): 358 if hasattr(xml, "read"):
332 xml.seek(0) 359 xml.seek(0)
333 xmlsrc = xml.read() 360 xmlsrc = xml.read()
334 xml = xmlsrc 361 xml = xmlsrc
335 self.document = etree.fromstring(xml).getroottree() 362 self.document = etree.fromstring(xml).getroottree()
336 self.initbody() 363 self.initbody()
337 assert self.document.getroot().tag == self.namespaced(self.rootNode) 364 assert self.document.getroot().tag == self.namespaced(self.rootNode)
338 groups = self.document.findall(".//%s" % self.namespaced("group")) 365 groups = self.document.findall(".//%s" % self.namespaced("group"))
339 pluralgroups = filter(ispluralgroup, groups) 366 pluralgroups = filter(ispluralgroup, groups)
340 termEntries = self.body.findall('.//%s' % self.namespaced(self.UnitClass .rootNode)) 367 termEntries = self.body.findall('.//%s' % self.namespaced(self.UnitClass .rootNode))
341 if termEntries is None: 368 if termEntries is None:
342 return 369 return
343 370
344 singularunits = filter(isnonpluralunit, termEntries) 371 singularunits = filter(isnonpluralunit, termEntries)
345 pluralunit_iter = pluralunits(pluralgroups) 372 pluralunit_iter = pluralunits(pluralgroups)
346 try: 373 try:
347 nextplural = pluralunit_iter.next() 374 nextplural = pluralunit_iter.next()
348 except StopIteration: 375 except StopIteration:
349 nextplural = None 376 nextplural = None
350 377
351 for entry in singularunits: 378 for entry in singularunits:
352 term = self.UnitClass.createfromxmlElement(entry, namespace=self.nam espace) 379 term = self.UnitClass.createfromxmlElement(entry, namespace=self.nam espace)
353 if nextplural and unicode(term.source) in nextplural.source.strings: 380 if nextplural and unicode(term.source) in nextplural.source.strings:
354 self.units.append(nextplural) 381 self.units.append(nextplural)
355 try: 382 try:
356 nextplural = pluralunit_iter.next() 383 nextplural = pluralunit_iter.next()
357 except StopIteration, i: 384 except StopIteration, i:
358 nextplural = None 385 nextplural = None
359 else: 386 else:
360 self.units.append(term) 387 self.units.append(term)
361 388
OLDNEW

Powered by Google App Engine
This is Rietveld r159