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 delimiter in a property line.
64
65 Property files can be delimeted by "=", ":" or whitespace (space for now).
66 We find the position of each delimiter, then find the one that appears
67 first.
68
69 @param line: A properties line
70 @type line: str
71 @return: delimiter character and offset within L{line}
72 @rtype: Tuple (delimiter char, Offset Integer)
73 """
74 delimiters = {"=": -1, ":": -1, " ": -1}
75
76 for delimiter, pos in delimiters.iteritems():
77 prewhitespace = len(line) - len(line.lstrip())
78 pos = line.find(delimiter, prewhitespace)
79 while pos != -1:
80 if delimiters[delimiter] == -1 and line[pos-1] != "\\":
81 delimiters[delimiter] = pos
82 break
83 pos = line.find(delimiter, pos+1)
84
85 mindelimiter = None
86 minpos = -1
87 for delimiter, pos in delimiters.iteritems():
88 if pos == -1 or delimiter == " ":
89 continue
90 if minpos == -1 or pos < minpos:
91 minpos = pos
92 mindelimiter = delimiter
93 if mindelimiter is None and delimiters[" "] != -1:
94
95 return (" ", delimiters[" "])
96 if mindelimiter is not None and delimiters[" "] < delimiters[mindelimiter]:
97
98
99
100 if len(line[delimiters[" "]:delimiters[mindelimiter]].strip()) > 0:
101 return (" ", delimiters[" "])
102 return (mindelimiter, minpos)
103
105 """Spelling error that is kept around for in case someone relies on it.
106
107 Deprecated."""
108 raise DeprecationWarning
109 return find_delimiter(line)
110
112 """Determine whether L{line} has a line continuation marker.
113
114 .properties files can be terminated with a backslash (\\) indicating
115 that the 'value' continues on the next line. Continuation is only
116 valid if there are an odd number of backslashses (an even number
117 would result in a set of N/2 slashes not an escape)
118
119 @param line: A properties line
120 @type line: str
121 @return: Does L{line} end with a line continuation
122 @rtype: Boolean
123 """
124 pos = -1
125 count = 0
126 if len(line) == 0:
127 return False
128
129
130 while len(line) >= -pos and line[pos:][0] == "\\":
131 pos -= 1
132 count += 1
133 return (count % 2) == 1
134
136 """Cleanup whitespace found around a key
137
138 @param key: A properties key
139 @type key: str
140 @return: Key without any uneeded whitespace
141 @rtype: str
142 """
143 newkey = key.rstrip()
144
145 if newkey[-1:] == "\\":
146 newkey += key[len(newkey):len(newkey)+1]
147 return newkey.lstrip()
148
149 default_encoding = {"java": "latin1", "mozilla": "utf-8", "skype": "utf-16"}
150
152 """an element of a properties file i.e. a name and value, and any comments
153 associated"""
154 - def __init__(self, source="", personality="java"):
164
172
177
178 source = property(getsource, setsource)
179
187
189 translation = quote.propertiesdecode(self.translation)
190 translation = re.sub(u"\\\\ ", u" ", translation)
191 return translation
192
193 target = property(gettarget, settarget)
194
201
216
219
220 - def addnote(self, text, origin=None, position="append"):
221 if origin in ['programmer', 'developer', 'source code', None]:
222 text = data.forceunicode(text)
223 self.comments.append(text)
224 else:
225 return super(propunit, self).addnote(text, origin=origin, position=position)
226
228 if origin in ['programmer', 'developer', 'source code', None]:
229 return u'\n'.join(self.comments)
230 else:
231 return super(propunit, self).getnotes(origin)
232
235
237 """returns whether this is a blank element, containing only comments..."""
238 return not (self.name or self.value)
239
241 return bool(self.name)
242
245
247 """this class represents a .properties file, made up of propunits"""
248 UnitClass = propunit
249 - def __init__(self, inputfile=None, personality="java"):
250 """construct a propfile, optionally reading in from inputfile"""
251 super(propfile, self).__init__(unitclass = self.UnitClass)
252 self.filename = getattr(inputfile, 'name', '')
253 if inputfile is not None:
254 propsrc = inputfile.read()
255 inputfile.close()
256 self.parse(propsrc, personality)
257
258 - def parse(self, propsrc, personality="java"):
259 """read the source of a properties file in and include them as units"""
260 newunit = propunit("", personality)
261 inmultilinevalue = False
262 propsrc = unicode(propsrc, default_encoding[personality])
263 for line in propsrc.split(u"\n"):
264
265 line = quote.rstripeol(line)
266 if inmultilinevalue:
267 newunit.value += line.lstrip()
268
269 inmultilinevalue = is_line_continuation(newunit.value)
270
271 if inmultilinevalue:
272
273 newunit.value = newunit.value[:-1]
274 if not inmultilinevalue:
275
276 self.addunit(newunit)
277 newunit = propunit("", personality)
278
279 elif line.strip()[:1] in (u'#', u'!'):
280
281 newunit.comments.append(line)
282 elif not line.strip():
283
284 if str(newunit).strip():
285 self.addunit(newunit)
286 newunit = propunit("", personality)
287 else:
288 delimiter_char, delimiter_pos = find_delimiter(line)
289 if delimiter_pos == -1:
290 continue
291
292 else:
293 newunit.delimiter = delimiter_char
294 newunit.name = key_strip(line[:delimiter_pos])
295 newunit.value = line[delimiter_pos+1:].lstrip()
296
297 if is_line_continuation(newunit.value):
298 inmultilinevalue = True
299 newunit.value = newunit.value[:-1]
300 else:
301 self.addunit(newunit)
302 newunit = propunit("", personality)
303
304 if inmultilinevalue or len(newunit.comments) > 0:
305 self.addunit(newunit)
306
308 """convert the units back to lines"""
309 lines = []
310 for unit in self.units:
311 lines.append(str(unit))
312 return "".join(lines)
313