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._comments = []
     98          self.source = source
     99
    100 -    def setsource(self, source):
    101          """Sets the source AND the target to be equal"""
    102          self.value = phpencode(source, self.escape_type)
    103
    104 -    def getsource(self):
    105          return phpdecode(self.value, self.escape_type)
    106      source = property(getsource, setsource)
    107
    108 -    def settarget(self, target):
    109          """Note: this also sets the .source attribute!"""
    110          # TODO: shouldn't this just call the .source property? no quoting done here...
    111          self.source = target
    112
    113 -    def gettarget(self):
    114          return self.source
    115      target = property(gettarget, settarget)
    116
    117 -    def __str__(self):
    118          """convert to a string. double check that unicode is handled somehow here"""
    119          source = self.getoutput()
    120          if isinstance(source, unicode):
    121              return source.encode(getattr(self, "encoding", "UTF-8"))
    122          return source
    123
    124 -    def getoutput(self):
    125          """convert the unit back into formatted lines for a php file"""
    126          return "".join(self._comments + ["%s='%s';\n" % (self.name, self.value)])
    127
    128 -    def addlocation(self, location):
    129          self.name = location
    130
    131 -    def getlocations(self):
    132          return [self.name]
    133
    134 -    def addnote(self, note, origin=None):
    135          self._comments.append(note)
    136
    137 -    def getnotes(self, origin=None):
    138          return '\n'.join(self._comments)
    139
    140 -    def removenotes(self):
    141          self._comments = []
    142
    143 -    def isblank(self):
    144          """Returns whether this is a blank element, containing only comments."""
    145          return not (self.name or self.value)
    146
    147 -class phpfile(base.TranslationStore):
    148      """This class represents a PHP file, made up of phpunits"""
    149      UnitClass = phpunit
    150 -    def __init__(self, inputfile=None, encoding='utf-8'):
    151          """construct a phpfile, optionally reading in from inputfile"""
    152          super(phpfile, self).__init__(unitclass = self.UnitClass)
    153          self.filename = getattr(inputfile, 'name', '')
    154          self._encoding = encoding
    155          if inputfile is not None:
    156              phpsrc = inputfile.read()
    157              inputfile.close()
    158              self.parse(phpsrc)
    159
    160 -    def parse(self, phpsrc):
    161          """Read the source of a PHP file in and include them as units"""
    162          newunit = phpunit()
    163          lastvalue = ""
    164          value = ""
    165          comment = []
    166          invalue = False
    167          incomment = False
    168          valuequote = "" # either ' or "
    169          for line in phpsrc.decode(self._encoding).split("\n"):
    170              commentstartpos = line.find("/*")
    171              commentendpos = line.rfind("*/")
    172              if commentstartpos != -1:
    173                  incomment = True
    174                  if commentendpos != -1:
    175                      newunit.addnote(line[commentstartpos:commentendpos].strip(), "developer")
    176                      incomment = False
    177                  else:
    178                      newunit.addnote(line[commentstartpos:].strip(), "developer")
    179              if commentendpos != -1 and incomment:
    180                  newunit.addnote(line[:commentendpos+2].strip(), "developer")
    181                  incomment = False
    182              if incomment and commentstartpos == -1:
    183                  newunit.addnote(line.strip(), "developer")
    184                  continue
    185              equalpos = line.find("=")
    186              if equalpos != -1 and not invalue:
    187                  newunit.addlocation(line[:equalpos].strip().replace(" ", ""))
    188                  value = line[equalpos+1:].lstrip()[1:]
    189                  valuequote = line[equalpos+1:].lstrip()[0]
    190                  lastvalue = ""
    191                  invalue = True
    192              else:
    193                  if invalue:
    194                      value = line
    195              colonpos = value.rfind(";")
    196              while colonpos != -1:
    197                  if value[colonpos-1] == valuequote:
    198                      newunit.value = lastvalue + value[:colonpos-1]
    199                      newunit.escape_type = valuequote
    200                      lastvalue = ""
    201                      invalue = False
    202                  if not invalue and colonpos != len(value)-1:
    203                      commentinlinepos = value.find("//", colonpos)
    204                      if commentinlinepos != -1:
    205                          newunit.addnote(value[commentinlinepos+2:].strip(), "developer")
    206                  if not invalue:
    207                      self.addunit(newunit)
    208                      value = ""
    209                      newunit = phpunit()
    210                  colonpos = value.rfind(";", 0, colonpos)
    211              if invalue:
    212                  lastvalue = lastvalue + value + "\n"
    213
    214 -    def __str__(self):
    215          """Convert the units back to lines."""
    216          lines = []
    217          for unit in self.units:
    218              lines.append(str(unit))
    219          return "".join(lines)
    220