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
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
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
57
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
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
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"""
92 """construct a blank phpunit"""
93 self.escape_type = None
94 super(phpunit, self).__init__(source)
95 self.name = ""
96 self.value = ""
97 self.translation = ""
98 self._comments = []
99 self.source = source
100
102 """Sets the source AND the target to be equal"""
103 self.value = phpencode(source, self.escape_type)
104
107 source = property(getsource, setsource)
108
111
113 return phpdecode(self.translation, self.escape_type)
114 target = property(gettarget, settarget)
115
122
124 """convert the unit back into formatted lines for a php file"""
125 return "".join(self._comments + ["%s='%s';\n" % (self.name, self.translation or self.value)])
126
129
132
133 - def addnote(self, text, origin=None, position="append"):
134 if origin in ['programmer', 'developer', 'source code', None]:
135 if position == "append":
136 self._comments.append(text)
137 else:
138 self._comments = [text]
139 else:
140 return super(phpunit, self).addnote(text, origin=origin, position=position)
141
143 if origin in ['programmer', 'developer', 'source code', None]:
144 return '\n'.join(self._comments)
145 else:
146 return super(phpunit, self).getnotes(origin)
147
150
152 """Returns whether this is a blank element, containing only comments."""
153 return not (self.name or self.value)
154
157
158 -class phpfile(base.TranslationStore):
159 """This class represents a PHP file, made up of phpunits"""
160 UnitClass = phpunit
161 - def __init__(self, inputfile=None, encoding='utf-8'):
162 """construct a phpfile, optionally reading in from inputfile"""
163 super(phpfile, self).__init__(unitclass = self.UnitClass)
164 self.filename = getattr(inputfile, 'name', '')
165 self._encoding = encoding
166 if inputfile is not None:
167 phpsrc = inputfile.read()
168 inputfile.close()
169 self.parse(phpsrc)
170
171 - def parse(self, phpsrc):
172 """Read the source of a PHP file in and include them as units"""
173 newunit = phpunit()
174 lastvalue = ""
175 value = ""
176 comment = []
177 invalue = False
178 incomment = False
179 valuequote = ""
180 for line in phpsrc.decode(self._encoding).split("\n"):
181 commentstartpos = line.find("/*")
182 commentendpos = line.rfind("*/")
183 if commentstartpos != -1:
184 incomment = True
185 if commentendpos != -1:
186 newunit.addnote(line[commentstartpos:commentendpos].strip(), "developer")
187 incomment = False
188 else:
189 newunit.addnote(line[commentstartpos:].strip(), "developer")
190 if commentendpos != -1 and incomment:
191 newunit.addnote(line[:commentendpos+2].strip(), "developer")
192 incomment = False
193 if incomment and commentstartpos == -1:
194 newunit.addnote(line.strip(), "developer")
195 continue
196 equalpos = line.find("=")
197 hashpos = line.find("#")
198 if 0 <= hashpos < equalpos:
199
200 newunit.addnote(line.strip(), "developer")
201 continue
202 if equalpos != -1 and not invalue:
203 newunit.addlocation(line[:equalpos].strip().replace(" ", ""))
204 value = line[equalpos+1:].lstrip()[1:]
205 valuequote = line[equalpos+1:].lstrip()[0]
206 lastvalue = ""
207 invalue = True
208 else:
209 if invalue:
210 value = line
211 colonpos = value.rfind(";")
212 while colonpos != -1:
213 if value[colonpos-1] == valuequote:
214 newunit.value = lastvalue + value[:colonpos-1]
215 newunit.escape_type = valuequote
216 lastvalue = ""
217 invalue = False
218 if not invalue and colonpos != len(value)-1:
219 commentinlinepos = value.find("//", colonpos)
220 if commentinlinepos != -1:
221 newunit.addnote(value[commentinlinepos+2:].strip(), "developer")
222 if not invalue:
223 self.addunit(newunit)
224 value = ""
225 newunit = phpunit()
226 colonpos = value.rfind(";", 0, colonpos)
227 if invalue:
228 lastvalue = lastvalue + value + "\n"
229
231 """Convert the units back to lines."""
232 lines = []
233 for unit in self.units:
234 lines.append(str(unit))
235 return "".join(lines)
236