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