Package translate :: Package storage :: Module ts2 |
[hide private]
[frames] | no frames]

# Source Code for Module translate.storage.ts2

      1  #!/usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3  #
      4  # Copyright 2008-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  """Module for handling Qt linguist (.ts) files.
     22
     23  This will eventually replace the older ts.py which only supports the older
     24  format. While converters haven't been updated to use this module, we retain
     25  both.
     26
     27  U{TS file format 4.3<http://doc.trolltech.com/4.3/linguist-ts-file-format.html>},
     28  U{http://doc.trolltech.com/4.5/linguist-ts-file-format.html>},
     29  U{Example<http://svn.ez.no/svn/ezcomponents/trunk/Translation/docs/linguist-format.txt>},
     30  U{Plurals forms<http://www.koders.com/cpp/fidE7B7E83C54B9036EB7FA0F27BC56BCCFC4B9DF34.aspx#L200>}
     31
     32  U{Specification of the valid variable entries <http://doc.trolltech.com/4.3/qstring.html#arg>},
     33  U{2 <http://doc.trolltech.com/4.3/qstring.html#arg-2>}
     34  """
     35
     36  from translate.storage import base, lisa
     37  from translate.storage.placeables import general, StringElem
     38  from translate.misc.multistring import multistring
     39  from translate.lang import data
     40  from lxml import etree
     41
     42  # TODO: handle translation types
     43
     44  NPLURALS = {
     45  'jp': 1,
     46  'en': 2,
     47  'fr': 2,
     48  'lv': 3,
     49  'ga': 3,
     50  'cs': 3,
     51  'sk': 3,
     52  'mk': 3,
     53  'lt': 3,
     54  'ru': 3,
     55  'pl': 3,
     56  'ro': 3,
     57  'sl': 4,
     58  'mt': 4,
     59  'cy': 5,
     60  'ar': 6,
     61  }
     62
     63 -class tsunit(lisa.LISAunit):
     64      """A single term in the xliff file."""
     65
     66      rootNode = "message"
     67      languageNode = "source"
     68      textNode = ""
     69      namespace = ''
     70      rich_parsers = general.parsers
     71
     72 -    def createlanguageNode(self, lang, text, purpose):
     73          """Returns an xml Element setup with given parameters."""
     74
     75          assert purpose
     76          if purpose == "target":
     77              purpose = "translation"
     78          langset = etree.Element(self.namespaced(purpose))
     79          #TODO: check language
     80  #        lisa.setXMLlang(langset, lang)
     81
     82          langset.text = text
     83          return langset
     84
     85 -    def _getsourcenode(self):
     86          return self.xmlelement.find(self.namespaced(self.languageNode))
     87
     88 -    def _gettargetnode(self):
     89          return self.xmlelement.find(self.namespaced("translation"))
     90
     91 -    def getlanguageNodes(self):
     92          """We override this to get source and target nodes."""
     93          def not_none(node):
     94              return not node is None
     95          return filter(not_none, [self._getsourcenode(), self._gettargetnode()])
     96
     97 -    def getsource(self):
     98          # TODO: support <byte>. See bug 528.
     99          sourcenode = self._getsourcenode()
    100          if self.hasplural():
    101              return multistring([sourcenode.text])
    102          else:
    103              return data.forceunicode(sourcenode.text)
    104      source = property(getsource, lisa.LISAunit.setsource)
    105      rich_source = property(base.TranslationUnit._get_rich_source, base.TranslationUnit._set_rich_source)
    106
    107 -    def settarget(self, text):
    108          # This is a fairly destructive implementation. Don't assume that this
    109          # is necessarily correct in all regards, but it does deal with a lot of
    110          # cases. It is hard to deal with plurals, since
    111          #Firstly deal with reinitialising to None or setting to identical string
    112          if self.gettarget() == text:
    113              return
    114          strings = []
    115          if isinstance(text, multistring):
    116              strings = text.strings
    117          elif isinstance(text, list):
    118              strings = text
    119          else:
    120              strings = [text]
    121          targetnode = self._gettargetnode()
    122          type = targetnode.get("type")
    123          targetnode.clear()
    124          if type:
    125              targetnode.set("type", type)
    126          if self.hasplural() or len(strings) > 1:
    127              self.xmlelement.set("numerus", "yes")
    128              for string in strings:
    129                  numerus = etree.SubElement(targetnode, self.namespaced("numerusform"))
    130                  numerus.text = data.forceunicode(string) or u""
    131          else:
    132              targetnode.text = data.forceunicode(text) or u""
    133
    134 -    def gettarget(self):
    135          targetnode = self._gettargetnode()
    136          if targetnode is None:
    137              etree.SubElement(self.xmlelement, self.namespaced("translation"))
    138              return None
    139          if self.hasplural():
    140              numerus_nodes = targetnode.findall(self.namespaced("numerusform"))
    141              return multistring([node.text or u"" for node in numerus_nodes])
    142          else:
    143              return data.forceunicode(targetnode.text) or u""
    144      target = property(gettarget, settarget)
    145      rich_target = property(base.TranslationUnit._get_rich_target, base.TranslationUnit._set_rich_target)
    146
    147 -    def hasplural(self):
    148          return self.xmlelement.get("numerus") == "yes"
    149
    150 -    def addnote(self, text, origin=None):
    151          """Add a note specifically in a "comment" tag"""
    152          if isinstance(text, str):
    153              text = text.decode("utf-8")
    154          current_notes = self.getnotes(origin)
    155          self.removenotes()
    156          note = etree.SubElement(self.xmlelement, self.namespaced("comment"))
    157          note.text = "\n".join(filter(None, [current_notes, text.strip()]))
    158
    159 -    def getnotes(self, origin=None):
    160          #TODO: consider only responding when origin has certain values
    161          notenode = self.xmlelement.find(self.namespaced("comment"))
    162          comment = ''
    163          if not notenode is None:
    164              comment = notenode.text
    165          return comment
    166
    167 -    def removenotes(self):
    168          """Remove all the translator notes."""
    169          note = self.xmlelement.find(self.namespaced("comment"))
    170          if not note is None:
    171              self.xmlelement.remove(note)
    172
    173 -    def _gettype(self):
    174          """Returns the type of this translation."""
    175          targetnode = self._gettargetnode()
    176          if targetnode is not None:
    177              return targetnode.get("type")
    178          return None
    179
    180 -    def _settype(self, value=None):
    181          """Set the type of this translation."""
    182          if value is None and self._gettype:
    183              # lxml recommends against using .attrib, but there seems to be no
    184              # other way
    185              self._gettargetnode().attrib.pop("type")
    186          else:
    187              self._gettargetnode().set("type", value)
    188
    189 -    def isreview(self):
    190          """States whether this unit needs to be reviewed"""
    191          return self._gettype() == "unfinished"
    192
    193 -    def isfuzzy(self):
    194          return self._gettype() == "unfinished"
    195
    196 -    def markfuzzy(self, value=True):
    197          if value:
    198              self._settype("unfinished")
    199          else:
    200              self._settype(None)
    201
    202 -    def getid(self):
    203          context_name = self.getcontext()
    204          #XXX: context_name is not supposed to be able to be None (the <name>
    205          # tag is compulsary in the <context> tag)
    206          if context_name is not None:
    207              return context_name + self.source
    208          else:
    209              return self.source
    210
    211 -    def getcontext(self):
    212          return self.xmlelement.getparent().find("name").text
    213
    214 -    def addlocation(self, location):
    215          if isinstance(location, str):
    216              text = text.decode("utf-8")
    217          location = etree.SubElement(self.xmlelement, self.namespaced("location"))
    218          filename, line = location.split(':', 1)
    219          location.set("filename", filename)
    220          location.set("line", line or "")
    221
    222 -    def getlocations(self):
    223          location = self.xmlelement.find(self.namespaced("location"))
    224          if location is None:
    225              return []
    226          else:
    227              return [':'.join([location.get("filename"), location.get("line")])]
    228
    229 -    def merge(self, otherunit, overwrite=False, comments=True):
    230          super(tsunit, self).merge(otherunit, overwrite, comments)
    231          #TODO: check if this is necessary:
    232          if otherunit.isfuzzy():
    233              self.markfuzzy()
    234
    235 -    def isobsolete(self):
    236          return self._gettype() == "obsolete"
    237
    238
    239 -class tsfile(lisa.LISAfile):
    240      """Class representing a XLIFF file store."""
    241      UnitClass = tsunit
    242      Name = _("Qt Linguist Translation File")
    243      Mimetypes  = ["application/x-linguist"]
    244      Extensions = ["ts"]
    245      rootNode = "TS"
    246      # We will switch out .body to fit with the context we are working on
    247      bodyNode = "context"
    248      XMLskeleton = '''<!DOCTYPE TS>
    249  <TS>
    250  </TS>
    251  '''
    252      namespace = ''
    253
    254 -    def __init__(self, *args, **kwargs):
    255          self._contextname = None
    256          lisa.LISAfile.__init__(self, *args, **kwargs)
    257
    258 -    def initbody(self):
    259          """Initialises self.body."""
    260          self.namespace = self.document.getroot().nsmap.get(None, None)
    261          if self._contextname:
    262              self.body = self.getcontextnode(self._contextname)
    263          else:
    264              self.body = self.document.getroot()
    265
    266 -    def gettargetlanguage(self):
    267          """Get the target language for this .ts file.
    268
    269          @return: ISO code e.g. af, fr, pt_BR
    270          @rtype: String
    271          """
    272          return self.body.get('language')
    273
    274 -    def settargetlanguage(self, targetlanguage):
    275          """Set the target language for this .ts file to L{targetlanguage}.
    276
    277          @param targetlanguage: ISO code e.g. af, fr, pt_BR
    278          @type targetlanguage: String
    279          """
    280          if targetlanguage:
    281              self.body.set('language', targetlanguage)
    282
    283 -    def _createcontext(self, contextname, comment=None):
    284          """Creates a context node with an optional comment"""
    285          context = etree.SubElement(self.document.getroot(), self.namespaced(self.bodyNode))
    286          name = etree.SubElement(context, self.namespaced("name"))
    287          name.text = contextname
    288          if comment:
    289              comment_node = context.SubElement(context, "comment")
    290              comment_node.text = comment
    291          return context
    292
    293 -    def _getcontextname(self, contextnode):
    294          """Returns the name of the given context node."""
    295          return filenode.find(self.namespaced("name")).text
    296
    297 -    def _getcontextnames(self):
    298          """Returns all contextnames in this TS file."""
    299          contextnodes = self.document.findall(self.namespaced("context"))
    300          contextnames = [self.getcontextname(contextnode) for contextnode in contextnodes]
    301          return contextnames
    302
    303 -    def _getcontextnode(self, contextname):
    304          """Returns the context node with the given name."""
    305          contextnodes = self.document.findall(self.namespaced("context"))
    306          for contextnode in contextnodes:
    307              if self.getcontextname(contextnode) == contextname:
    308                  return contextnode
    309          return None
    310
    311 -    def addunit(self, unit, new=True, contextname=None, createifmissing=False):
    312          """Adds the given unit to the last used body node (current context).
    313
    314          If the contextname is specified, switch to that context (creating it
    315          if allowed by createifmissing)."""
    316          if self._contextname != contextname:
    317              if not self._switchcontext(contextname, createifmissing):
    318                  return None
    319          super(tsfile, self).addunit(unit, new)
    320  #        lisa.setXMLspace(unit.xmlelement, "preserve")
    321          return unit
    322
    323 -    def _switchcontext(self, contextname, createifmissing=False):
    324          """Switch the current context to the one named contextname, optionally
    325          creating it if it doesn't exist."""
    326          self._contextname = contextname
    327          contextnode = self._getcontextnode(contextname)
    328          if contextnode is None:
    329              if not createifmissing:
    330                  return False
    331              contextnode = self._createcontext(contextname)
    332
    333          self.body = contextnode
    334          if self.body is None:
    335              return False
    336          return True
    337
    338 -    def nplural(self):
    339          lang = self.body.get("language")
    340          if NPLURALS.has_key(lang):
    341              return NPLURALS[lang]
    342          else:
    343              return 1
    344
    345 -    def __str__(self):
    346          """Converts to a string containing the file's XML.
    347
    348          We have to override this to ensure mimic the Qt convention:
    349              - no XML decleration
    350              - plain DOCTYPE that lxml seems to ignore
    351          """
    352          # A bug in lxml means we have to output the doctype ourselves. For
    353          # more information, see:
    354          # http://codespeak.net/pipermail/lxml-dev/2008-October/004112.html
    355          # The problem was fixed in lxml 2.1.3
    356          output = etree.tostring(self.document, pretty_print=True,
    357                  xml_declaration=False, encoding='utf-8')
    358          if not "<!DOCTYPE TS>" in output[:30]:
    359              output = "<!DOCTYPE TS>" + output
    360          return output
    361