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 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
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
58
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
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
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"""
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
103 """Sets the source AND the target to be equal"""
104 self._rich_source = None
105 self.value = phpencode(source, self.escape_type)
106
109 source = property(getsource, setsource)
110
112 self._rich_target = None
113 self.translation = phpencode(target, self.escape_type)
114
116 return phpdecode(self.translation, self.escape_type)
117 target = property(gettarget, settarget)
118
125
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
132
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
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
153
155 """Returns whether this is a blank element, containing only comments."""
156 return not (self.name or self.value)
157
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 = ""
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
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
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