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