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