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