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