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