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"""
99
101 """Sets the source AND the target to be equal"""
102 self.value = phpencode(source, self.escape_type)
103
106 source = property(getsource, setsource)
107
109 """Note: this also sets the .source attribute!"""
110
111 self.source = target
112
115 target = property(gettarget, settarget)
116
123
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
130
133
134 - def addnote(self, note, origin=None):
135 self._comments.append(note)
136
138 return '\n'.join(self._comments)
139
142
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 = ""
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 hashpos = line.find("#")
187 if 0 <= hashpos < equalpos:
188
189 newunit.addnote(line.strip(), "developer")
190 continue
191 if equalpos != -1 and not invalue:
192 newunit.addlocation(line[:equalpos].strip().replace(" ", ""))
193 value = line[equalpos+1:].lstrip()[1:]
194 valuequote = line[equalpos+1:].lstrip()[0]
195 lastvalue = ""
196 invalue = True
197 else:
198 if invalue:
199 value = line
200 colonpos = value.rfind(";")
201 while colonpos != -1:
202 if value[colonpos-1] == valuequote:
203 newunit.value = lastvalue + value[:colonpos-1]
204 newunit.escape_type = valuequote
205 lastvalue = ""
206 invalue = False
207 if not invalue and colonpos != len(value)-1:
208 commentinlinepos = value.find("//", colonpos)
209 if commentinlinepos != -1:
210 newunit.addnote(value[commentinlinepos+2:].strip(), "developer")
211 if not invalue:
212 self.addunit(newunit)
213 value = ""
214 newunit = phpunit()
215 colonpos = value.rfind(";", 0, colonpos)
216 if invalue:
217 lastvalue = lastvalue + value + "\n"
218
220 """Convert the units back to lines."""
221 lines = []
222 for unit in self.units:
223 lines.append(str(unit))
224 return "".join(lines)
225