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