1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
61
62
63
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
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
91
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
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
119 """Sets the source AND the target to be equal"""
120 self._rich_source = None
121 self.value = phpencode(source, self.escape_type)
122
125 source = property(getsource, setsource)
126
128 self._rich_target = None
129 self.translation = phpencode(target, self.escape_type)
130
132 return phpdecode(self.translation, self.escape_type)
133 target = property(gettarget, settarget)
134
142
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
149
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
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
171
173 """Returns whether this is a blank element, containing only comments.
174 """
175 return not (self.name or self.value)
176
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 = ""
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
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
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