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