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 from translate.storage import base
42 import re
43
44
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
60
61
62
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
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
90
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
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
118 """Sets the source AND the target to be equal"""
119 self._rich_source = None
120 self.value = phpencode(source, self.escape_type)
121
124 source = property(getsource, setsource)
125
127 self._rich_target = None
128 self.translation = phpencode(target, self.escape_type)
129
131 return phpdecode(self.translation, self.escape_type)
132 target = property(gettarget, settarget)
133
141
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
148
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
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
170
172 """Returns whether this is a blank element, containing only comments.
173 """
174 return not (self.name or self.value)
175
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 = ""
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
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
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