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