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"}
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.delimeter = u"="
154 self.comments = []
155 self.source = source
156
164
169
170 source = property(getsource, setsource)
171
173 """Note: this also sets the .source attribute!"""
174 self.source = target
175
178 target = property(gettarget, settarget)
179
186
188 """convert the element back into formatted lines for a .properties file"""
189 notes = self.getnotes()
190 if notes:
191 notes += u"\n"
192 if self.isblank():
193 return notes
194 else:
195 if "\\u" in self.value and self.personality == "mozilla":
196 self.value = quote.mozillapropertiesencode(self.source)
197 return u"%s%s%s%s\n" % (notes, self.name, self.delimeter, self.value)
198
201
202 - def addnote(self, note, origin=None):
203 note = data.forceunicode(note)
204 self.comments.append(note)
205
207 return u'\n'.join(self.comments)
208
211
213 """returns whether this is a blank element, containing only comments..."""
214 return not (self.name or self.value)
215
217 """this class represents a .properties file, made up of propunits"""
218 UnitClass = propunit
219 - def __init__(self, inputfile=None, personality="java"):
220 """construct a propfile, optionally reading in from inputfile"""
221 super(propfile, self).__init__(unitclass = self.UnitClass)
222 self.filename = getattr(inputfile, 'name', '')
223 if inputfile is not None:
224 propsrc = inputfile.read()
225 inputfile.close()
226 self.parse(propsrc, personality)
227
228 - def parse(self, propsrc, personality="java"):
229 """read the source of a properties file in and include them as units"""
230 newunit = propunit("", personality)
231 inmultilinevalue = False
232 if personality == "mozilla":
233 propsrc = unicode(propsrc, 'utf-8')
234 else:
235 propsrc = unicode(propsrc, 'latin1')
236 for line in propsrc.split(u"\n"):
237
238 line = quote.rstripeol(line)
239 if inmultilinevalue:
240 newunit.value += line.lstrip()
241
242 inmultilinevalue = is_line_continuation(newunit.value)
243
244 if inmultilinevalue:
245
246 newunit.value = newunit.value[:-1]
247 if not inmultilinevalue:
248
249 self.addunit(newunit)
250 newunit = propunit("", personality)
251
252 elif line.strip()[:1] in (u'#', u'!'):
253
254 newunit.comments.append(line)
255 elif not line.strip():
256
257 if str(newunit).strip():
258 self.addunit(newunit)
259 newunit = propunit("", personality)
260 else:
261 delimeter_char, delimeter_pos = find_delimeter(line)
262 if delimeter_pos == -1:
263 continue
264
265 else:
266 newunit.delimeter = delimeter_char
267 newunit.name = key_strip(line[:delimeter_pos])
268 newunit.value = line[delimeter_pos+1:].lstrip()
269
270 if is_line_continuation(newunit.value):
271 inmultilinevalue = True
272 newunit.value = newunit.value[:-1]
273 else:
274 self.addunit(newunit)
275 newunit = propunit("", personality)
276
277 if inmultilinevalue or len(newunit.comments) > 0:
278 self.addunit(newunit)
279
281 """convert the units back to lines"""
282 lines = []
283 for unit in self.units:
284 lines.append(str(unit))
285 return "".join(lines)
286