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