| OLD | NEW |
| 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 |
| OLD | NEW |