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._comments = [] 98 self.source = source
99
100 - def setsource(self, source):
101 """Sets the source AND the target to be equal""" 102 self.value = phpencode(source, self.escape_type)
103
104 - def getsource(self):
105 return phpdecode(self.value, self.escape_type)
106 source = property(getsource, setsource) 107
108 - def settarget(self, target):
109 """Note: this also sets the .source attribute!""" 110 # TODO: shouldn't this just call the .source property? no quoting done here... 111 self.source = target
112
113 - def gettarget(self):
114 return self.source
115 target = property(gettarget, settarget) 116
117 - def __str__(self):
118 """convert to a string. double check that unicode is handled somehow here""" 119 source = self.getoutput() 120 if isinstance(source, unicode): 121 return source.encode(getattr(self, "encoding", "UTF-8")) 122 return source
123
124 - def getoutput(self):
125 """convert the unit back into formatted lines for a php file""" 126 return "".join(self._comments + ["%s='%s';\n" % (self.name, self.value)])
127
128 - def addlocation(self, location):
129 self.name = location
130
131 - def getlocations(self):
132 return [self.name]
133
134 - def addnote(self, note, origin=None):
135 self._comments.append(note)
136
137 - def getnotes(self, origin=None):
138 return '\n'.join(self._comments)
139
140 - def removenotes(self):
141 self._comments = []
142
143 - def isblank(self):
144 """Returns whether this is a blank element, containing only comments.""" 145 return not (self.name or self.value)
146
147 -class phpfile(base.TranslationStore):
148 """This class represents a PHP file, made up of phpunits""" 149 UnitClass = phpunit
150 - def __init__(self, inputfile=None, encoding='utf-8'):
151 """construct a phpfile, optionally reading in from inputfile""" 152 super(phpfile, self).__init__(unitclass = self.UnitClass) 153 self.filename = getattr(inputfile, 'name', '') 154 self._encoding = encoding 155 if inputfile is not None: 156 phpsrc = inputfile.read() 157 inputfile.close() 158 self.parse(phpsrc)
159
160 - def parse(self, phpsrc):
161 """Read the source of a PHP file in and include them as units""" 162 newunit = phpunit() 163 lastvalue = "" 164 value = "" 165 comment = [] 166 invalue = False 167 incomment = False 168 valuequote = "" # either ' or " 169 for line in phpsrc.decode(self._encoding).split("\n"): 170 commentstartpos = line.find("/*") 171 commentendpos = line.rfind("*/") 172 if commentstartpos != -1: 173 incomment = True 174 if commentendpos != -1: 175 newunit.addnote(line[commentstartpos:commentendpos].strip(), "developer") 176 incomment = False 177 else: 178 newunit.addnote(line[commentstartpos:].strip(), "developer") 179 if commentendpos != -1 and incomment: 180 newunit.addnote(line[:commentendpos+2].strip(), "developer") 181 incomment = False 182 if incomment and commentstartpos == -1: 183 newunit.addnote(line.strip(), "developer") 184 continue 185 equalpos = line.find("=") 186 if equalpos != -1 and not invalue: 187 newunit.addlocation(line[:equalpos].strip().replace(" ", "")) 188 value = line[equalpos+1:].lstrip()[1:] 189 valuequote = line[equalpos+1:].lstrip()[0] 190 lastvalue = "" 191 invalue = True 192 else: 193 if invalue: 194 value = line 195 colonpos = value.rfind(";") 196 while colonpos != -1: 197 if value[colonpos-1] == valuequote: 198 newunit.value = lastvalue + value[:colonpos-1] 199 newunit.escape_type = valuequote 200 lastvalue = "" 201 invalue = False 202 if not invalue and colonpos != len(value)-1: 203 commentinlinepos = value.find("//", colonpos) 204 if commentinlinepos != -1: 205 newunit.addnote(value[commentinlinepos+2:].strip(), "developer") 206 if not invalue: 207 self.addunit(newunit) 208 value = "" 209 newunit = phpunit() 210 colonpos = value.rfind(";", 0, colonpos) 211 if invalue: 212 lastvalue = lastvalue + value + "\n"
213
214 - def __str__(self):
215 """Convert the units back to lines.""" 216 lines = [] 217 for unit in self.units: 218 lines.append(str(unit)) 219 return "".join(lines)
220