translate.storage.php

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

# Source Code for Module translate.storage.php

      1  #!/usr/bin/env python
      2  # -*- coding: utf-8 -*-
      3  #
      4  # Copyright 2004-2008 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  """Classes that hold units of PHP localisation files L{phpunit} or entire files
     22     L{phpfile}. These files are used in translating many PHP based applications.
     23
     24     Only PHP files written with these conventions are supported::
     25        $lang['item'] = "vale";  # Array of values
     26        $some_entity = "value";  # Named variables
     27        $lang = array(
     28           'item1' => 'value1',
     29           'item2' => 'value2',
     30        );
     31
     32     Nested arrays are not supported::
     33        $lang = array(array('key' => 'value'));
     34
     35     The working of PHP strings and specifically the escaping conventions which
     36     differ between single quote (') and double quote (") characters are implemented as outlined
     37     in the PHP documentation for the U{String type<http://www.php.net/language.types.string>}
     38  """
     39
     40  from translate.storage import base
     41  import re
     42
     43 -def phpencode(text, quotechar="'"):
     44      """convert Python string to PHP escaping
     45
     46      The encoding is implemented for
     47      U{'single quote'<http://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.single>}
     48      and U{"double quote"<http://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.double>}
     49      syntax.
     50
     51      heredoc and nowdoc are not implemented and it is not certain whether this would
     52      ever be needed for PHP localisation needs.
     53      """
     54      if not text:
     55          return text
     56      if quotechar == '"':
     57          # \n may be converted to \\n but we don't.  This allows us to preserve pretty layout that might have appeared in muliline entries
     58          # we might lose some "blah\nblah" layouts but that's probably not the most frequent use case. See bug 588
     59          escapes = (("\\", "\\\\"), ("\r", "\\r"), ("\t", "\\t"), ("\v", "\\v"), ("\f", "\\f"), ("\\\\$", "\\$"), ('"', '\\"'), ("\\\\", "\\"))
     60          for a, b in escapes:
     61              text = text.replace(a, b)
     62          return text
     63      else:
     64          return text.replace("%s" % quotechar, "\\%s" % quotechar)
     65
     66 -def phpdecode(text, quotechar="'"):
     67      """convert PHP escaped string to a Python string"""
     68      def decode_octal_hex(match):
     69          """decode Octal \NNN and Hex values"""
     70          if match.groupdict().has_key("octal"):
     71              return match.groupdict()['octal'].decode("string_escape")
     72          elif match.groupdict().has_key("hex"):
     73              return match.groupdict()['hex'].decode("string_escape")
     74          else:
     75              return match.group
     76
     77      if not text:
     78          return text
     79      if quotechar == '"':
     80          # We do not escape \$ as it is used by variables and we can't roundtrip that item.
     81          text = text.replace('\\"', '"').replace("\\\\", "\\")
     82          text = text.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t").replace("\\v", "\v").replace("\\f", "\f")
     83          text = re.sub(r"(?P<octal>\\[0-7]{1,3})", decode_octal_hex, text)
     84          text = re.sub(r"(?P<hex>\\x[0-9A-Fa-f]{1,2})", decode_octal_hex, text)
     85      else:
     86          text = text.replace("\\'", "'").replace("\\\\", "\\")
     87      return text
     88
     89 -class phpunit(base.TranslationUnit):
     90      """a unit of a PHP file i.e. a name and value, and any comments
     91      associated"""
     92 -    def __init__(self, source=""):
     93          """construct a blank phpunit"""
     94          self.escape_type = None
     95          super(phpunit, self).__init__(source)
     96          self.name = ""
     97          self.value = ""
     98          self.translation = ""
     99          self._comments = []
    100          self.source = source
    101
    102 -    def setsource(self, source):
    103          """Sets the source AND the target to be equal"""
    104          self._rich_source = None
    105          self.value = phpencode(source, self.escape_type)
    106
    107 -    def getsource(self):
    108          return phpdecode(self.value, self.escape_type)
    109      source = property(getsource, setsource)
    110
    111 -    def settarget(self, target):
    112          self._rich_target = None
    113          self.translation = phpencode(target, self.escape_type)
    114
    115 -    def gettarget(self):
    116          return phpdecode(self.translation, self.escape_type)
    117      target = property(gettarget, settarget)
    118
    119 -    def __str__(self):
    120          """convert to a string. double check that unicode is handled somehow here"""
    121          source = self.getoutput()
    122          if isinstance(source, unicode):
    123              return source.encode(getattr(self, "encoding", "UTF-8"))
    124          return source
    125
    126 -    def getoutput(self):
    127          """convert the unit back into formatted lines for a php file"""
    128          return "".join(self._comments + ["%s='%s';\n" % (self.name, self.translation or self.value)])
    129
    130 -    def addlocation(self, location):
    131          self.name = location
    132
    133 -    def getlocations(self):
    134          return [self.name]
    135
    136 -    def addnote(self, text, origin=None, position="append"):
    137          if origin in ['programmer', 'developer', 'source code', None]:
    138              if position == "append":
    139                  self._comments.append(text)
    140              else:
    141                  self._comments = [text]
    142          else:
    143              return super(phpunit, self).addnote(text, origin=origin, position=position)
    144
    145 -    def getnotes(self, origin=None):
    146          if origin in ['programmer', 'developer', 'source code', None]:
    147              return '\n'.join(self._comments)
    148          else:
    149              return super(phpunit, self).getnotes(origin)
    150
    151 -    def removenotes(self):
    152          self._comments = []
    153
    154 -    def isblank(self):
    155          """Returns whether this is a blank element, containing only comments."""
    156          return not (self.name or self.value)
    157
    158 -    def getid(self):
    159          return self.name
    160
    161 -class phpfile(base.TranslationStore):
    162      """This class represents a PHP file, made up of phpunits"""
    163      UnitClass = phpunit
    164 -    def __init__(self, inputfile=None, encoding='utf-8'):
    165          """construct a phpfile, optionally reading in from inputfile"""
    166          super(phpfile, self).__init__(unitclass = self.UnitClass)
    167          self.filename = getattr(inputfile, 'name', '')
    168          self._encoding = encoding
    169          if inputfile is not None:
    170              phpsrc = inputfile.read()
    171              inputfile.close()
    172              self.parse(phpsrc)
    173
    174 -    def parse(self, phpsrc):
    175          """Read the source of a PHP file in and include them as units"""
    176          newunit = phpunit()
    177          lastvalue = ""
    178          value = ""
    179          invalue = False
    180          incomment = False
    181          inarray = False
    182          valuequote = "" # either ' or "
    183          equaldel = "="
    184          enddel = ";"
    185          prename = ""
    186          for line in phpsrc.decode(self._encoding).split("\n"):
    187              commentstartpos = line.find("/*")
    188              commentendpos = line.rfind("*/")
    189              if commentstartpos != -1:
    190                  incomment = True
    191                  if commentendpos != -1:
    192                      newunit.addnote(line[commentstartpos:commentendpos].strip(), "developer")
    193                      incomment = False
    194                  else:
    195                      newunit.addnote(line[commentstartpos:].strip(), "developer")
    196              if commentendpos != -1 and incomment:
    197                  newunit.addnote(line[:commentendpos+2].strip(), "developer")
    198                  incomment = False
    199              if incomment and commentstartpos == -1:
    200                  newunit.addnote(line.strip(), "developer")
    201                  continue
    202              if line.find('array(') != -1:
    203                  equaldel = "=>"
    204                  enddel = ","
    205                  inarray = True
    206                  prename = line[:line.find('=')].strip() + "->"
    207                  continue
    208              if inarray and line.find(');') != -1:
    209                  equaldel = "="
    210                  enddel = ";"
    211                  inarray = False
    212                  continue
    213              equalpos = line.find(equaldel)
    214              hashpos = line.find("#")
    215              if 0 <= hashpos < equalpos:
    216                  # Assume that this is a '#' comment line
    217                  newunit.addnote(line.strip(), "developer")
    218                  continue
    219              if equalpos != -1 and not invalue:
    220                  newunit.addlocation(prename + line[:equalpos].strip().replace(" ", ""))
    221                  value = line[equalpos+len(equaldel):].lstrip()[1:]
    222                  valuequote = line[equalpos+len(equaldel):].lstrip()[0]
    223                  lastvalue = ""
    224                  invalue = True
    225              else:
    226                  if invalue:
    227                      value = line
    228              colonpos = value.rfind(enddel)
    229              while colonpos != -1:
    230                  if value[colonpos-1] == valuequote:
    231                      newunit.value = lastvalue + value[:colonpos-1]
    232                      newunit.escape_type = valuequote
    233                      lastvalue = ""
    234                      invalue = False
    235                  if not invalue and colonpos != len(value)-1:
    236                      commentinlinepos = value.find("//", colonpos)
    237                      if commentinlinepos != -1:
    238                          newunit.addnote(value[commentinlinepos+2:].strip(), "developer")
    239                  if not invalue:
    240                      self.addunit(newunit)
    241                      value = ""
    242                      newunit = phpunit()
    243                  colonpos = value.rfind(enddel, 0, colonpos)
    244              if invalue:
    245                  lastvalue = lastvalue + value + "\n"
    246
    247 -    def __str__(self):
    248          """Convert the units back to lines."""
    249          lines = []
    250          for unit in self.units:
    251              lines.append(str(unit))
    252          return "".join(lines)
    253