1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """classes that hold units of .properties files (propunit) or entire files
23 (propfile) these files are used in translating Mozilla and other software
24
25 The following U{.properties file
26 description<http://java.sun.com/j2se/1.4.2/docs/api/java/util/Properties.html#load(java.io.InputStream)>}
27 and U{example <http://www.exampledepot.com/egs/java.util/Props.html>} give some
28 good references to the .properties specification.
29
30 Properties file may also hold Java
31 U{MessageFormat<http://java.sun.com/j2se/1.4.2/docs/api/java/text/MessageFormat.html>}
32 messages. No special handling is provided in this storage class for MessageFormat,
33 but this may be implemented in future.
34
35 Implementation
36 ==============
37 A simple summary of what is permissible follows.
38
39 Comments::
40 # a comment
41 ! a comment
42
43 Name and Value pairs::
44 # Note that the b and c are escaped for epydoc rendering
45 a = a string
46 d.e.f = another string
47 b = a string with escape sequences \\t \\n \\r \\\\ \\" \\' \\ (space) \u0123
48 c = a string with a continuation line \\
49 continuation line
50 """
51
52 from translate.storage import base
53 from translate.misc import quote
54 from translate.lang import data
55 import re
56
57
58
59
60 eol = "\n"
61
63 """Find the type and position of the delimeter in a property line.
64
65 Property files can be delimeted by "=", ":" or whitespace (space for now).
66 We find the position of each delimeter, then find the one that appears
67 first.
68
69 @param line: A properties line
70 @type line: str
71 @return: Delimeter character and offset within L{line}
72 @rtype: Tuple (Delimeter char, Offset Integer)
73 """
74 delimeters = {"=": -1, ":": -1, " ": -1}
75
76 for delimeter, pos in delimeters.iteritems():
77 prewhitespace = len(line) - len(line.lstrip())
78 pos = line.find(delimeter, prewhitespace)
79 while pos != -1:
80 if delimeters[delimeter] == -1 and line[pos-1] != "\\":
81 delimeters[delimeter] = pos
82 break
83 pos = line.find(delimeter, pos+1)
84
85 mindelimeter = None
86 minpos = -1
87 for delimeter, pos in delimeters.iteritems():
88 if pos == -1 or delimeter == " ":
89 continue
90 if minpos == -1 or pos < minpos:
91 minpos = pos
92 mindelimeter = delimeter
93 if mindelimeter is None and delimeters[" "] != -1:
94
95 return (" ", delimeters[" "])
96 if mindelimeter is not None and delimeters[" "] < delimeters[mindelimeter]:
97
98
99
100 if len(line[delimeters[" "]:delimeters[mindelimeter]].strip()) > 0:
101 return (" ", delimeters[" "])
102 return (mindelimeter, minpos)
103
105 """Determine whether L{line} has a line continuation marker.
106
107 .properties files can be terminated with a backslash (\\) indicating
108 that the 'value' continues on the next line. Continuation is only
109 valid if there are an odd number of backslashses (an even number
110 would result in a set of N/2 slashes not an escape)
111
112 @param line: A properties line
113 @type line: str
114 @return: Does L{line} end with a line continuation
115 @rtype: Boolean
116 """
117 pos = -1
118 count = 0
119 if len(line) == 0:
120 return False
121
122
123 while len(line) >= -pos and line[pos:][0] == "\\":
124 pos -= 1
125 count += 1
126 return (count % 2) == 1
127
129 """Cleanup whitespace found around a key
130
131 @param key: A properties key
132 @type key: str
133 @return: Key without any uneeded whitespace
134 @rtype: str
135 """
136 newkey = key.rstrip()
137
138 if newkey[-1:] == "\\":
139 newkey += key[len(newkey):len(newkey)+1]
140 return newkey.lstrip()
141
142 default_encoding = {"java": "latin1", "mozilla": "utf-8", "skype": "utf-16"}
143
145 """an element of a properties file i.e. a name and value, and any comments
146 associated"""
147 - def __init__(self, source="", personality="java"):
148 """construct a blank propunit"""
149 self.personality = personality
150 super(propunit, self).__init__(source)
151 self.name = ""
152 self.value = u""
153 self.translation = u""
154 self.delimeter = u"="
155 self.comments = []
156 self.source = source
157
164
169
170 source = property(getsource, setsource)
171
178
180 translation = quote.propertiesdecode(self.translation)
181 translation = re.sub(u"\\\\ ", u" ", translation)
182 return translation
183
184 target = property(gettarget, settarget)
185
192
207
210
211 - def addnote(self, text, origin=None, position="append"):
212 if origin in ['programmer', 'developer', 'source code', None]:
213 text = data.forceunicode(text)
214 self.comments.append(text)
215 else:
216 return super(propunit, self).addnote(text, origin=origin, position=position)
217
219 if origin in ['programmer', 'developer', 'source code', None]:
220 return u'\n'.join(self.comments)
221 else:
222 return super(propunit, self).getnotes(origin)
223
226
228 """returns whether this is a blank element, containing only comments..."""
229 return not (self.name or self.value)
230
232 return bool(self.name)
233
236
238 """this class represents a .properties file, made up of propunits"""
239 UnitClass = propunit
240 - def __init__(self, inputfile=None, personality="java"):
241 """construct a propfile, optionally reading in from inputfile"""
242 super(propfile, self).__init__(unitclass = self.UnitClass)
243 self.filename = getattr(inputfile, 'name', '')
244 if inputfile is not None:
245 propsrc = inputfile.read()
246 inputfile.close()
247 self.parse(propsrc, personality)
248
249 - def parse(self, propsrc, personality="java"):
250 """read the source of a properties file in and include them as units"""
251 newunit = propunit("", personality)
252 inmultilinevalue = False
253 propsrc = unicode(propsrc, default_encoding[personality])
254 for line in propsrc.split(u"\n"):
255
256 line = quote.rstripeol(line)
257 if inmultilinevalue:
258 newunit.value += line.lstrip()
259
260 inmultilinevalue = is_line_continuation(newunit.value)
261
262 if inmultilinevalue:
263
264 newunit.value = newunit.value[:-1]
265 if not inmultilinevalue:
266
267 self.addunit(newunit)
268 newunit = propunit("", personality)
269
270 elif line.strip()[:1] in (u'#', u'!'):
271
272 newunit.comments.append(line)
273 elif not line.strip():
274
275 if str(newunit).strip():
276 self.addunit(newunit)
277 newunit = propunit("", personality)
278 else:
279 delimeter_char, delimeter_pos = find_delimeter(line)
280 if delimeter_pos == -1:
281 continue
282
283 else:
284 newunit.delimeter = delimeter_char
285 newunit.name = key_strip(line[:delimeter_pos])
286 newunit.value = line[delimeter_pos+1:].lstrip()
287
288 if is_line_continuation(newunit.value):
289 inmultilinevalue = True
290 newunit.value = newunit.value[:-1]
291 else:
292 self.addunit(newunit)
293 newunit = propunit("", personality)
294
295 if inmultilinevalue or len(newunit.comments) > 0:
296 self.addunit(newunit)
297
299 """convert the units back to lines"""
300 lines = []
301 for unit in self.units:
302 lines.append(str(unit))
303 return "".join(lines)
304