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 if equalpos != -1 and not invalue:
187 newunit.addlocation(line[:equalpos].strip().replace(" ", ""))
188 value = line[equalpos+1:].lstrip()[1:]
189 valuequote = line[equalpos+1:].lstrip()[0]
190 lastvalue = ""
191 invalue = True
192 else:
193 if invalue:
194 value = line
195 colonpos = value.rfind(";")
196 while colonpos != -1:
197 if value[colonpos-1] == valuequote:
198 newunit.value = lastvalue + value[:colonpos-1]
199 newunit.escape_type = valuequote
200 lastvalue = ""
201 invalue = False
202 if not invalue and colonpos != len(value)-1:
203 commentinlinepos = value.find("//", colonpos)
204 if commentinlinepos != -1:
205 newunit.addnote(value[commentinlinepos+2:].strip(), "developer")
206 if not invalue:
207 self.addunit(newunit)
208 value = ""
209 newunit = phpunit()
210 colonpos = value.rfind(";", 0, colonpos)
211 if invalue:
212 lastvalue = lastvalue + value + "\n"
213
215 """Convert the units back to lines."""
216 lines = []
217 for unit in self.units:
218 lines.append(str(unit))
219 return "".join(lines)
220