Package translate :: Package storage :: Module properties
[hide private]
[frames] | no frames]

Source Code for Module translate.storage.properties

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  # 
  4  # Copyright 2004-2006 Zuza Software Foundation 
  5  # 
  6  # This file is part of translate. 
  7  # 
  8  # translate is free software; you can redistribute it and/or modify 
  9  # it under the terms of the GNU General Public License as published by 
 10  # the Free Software Foundation; either version 2 of the License, or 
 11  # (at your option) any later version. 
 12  # 
 13  # translate is distributed in the hope that it will be useful, 
 14  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 16  # GNU General Public License for more details. 
 17  # 
 18  # You should have received a copy of the GNU General Public License 
 19  # along with translate; if not, write to the Free Software 
 20  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 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  # the rstripeols convert dos <-> unix nicely as well 
 58  # output will be appropriate for the platform 
 59   
 60  eol = "\n" 
 61   
62 -def find_delimeter(line):
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 # Find the position of each delimeter type 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 # Find the first "=" or ":" delimeter 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 # Use space delimeter if we found nothing else 95 return (" ", delimeters[" "]) 96 if mindelimeter is not None and delimeters[" "] < delimeters[mindelimeter]: 97 # If space delimeter occurs earlier then ":" or "=" then it is the 98 # delimeter only if there are non-whitespace characters between it and 99 # the other detected delimeter. 100 if len(line[delimeters[" "]:delimeters[mindelimeter]].strip()) > 0: 101 return (" ", delimeters[" "]) 102 return (mindelimeter, minpos)
103
104 -def is_line_continuation(line):
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 # Count the slashes from the end of the line. Ensure we don't 122 # go into infinite loop. 123 while len(line) >= -pos and line[pos:][0] == "\\": 124 pos -= 1 125 count += 1 126 return (count % 2) == 1 # Odd is a line continuation, even is not
127
128 -def key_strip(key):
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 # If line now end in \ we put back the whitespace that was escaped 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
144 -class propunit(base.TranslationUnit):
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
158 - def setsource(self, source):
159 source = data.forceunicode(source) 160 if self.personality == "mozilla" or self.personality == "skype": 161 self.value = quote.mozillapropertiesencode(source or u"") 162 else: 163 self.value = quote.javapropertiesencode(source or u"")
164
165 - def getsource(self):
166 value = quote.propertiesdecode(self.value) 167 value = re.sub(u"\\\\ ", u" ", value) 168 return value
169 170 source = property(getsource, setsource) 171
172 - def settarget(self, target):
173 target = data.forceunicode(target) 174 if self.personality == "mozilla" or self.personality == "skype": 175 self.translation = quote.mozillapropertiesencode(target or u"") 176 else: 177 self.translation = quote.javapropertiesencode(target or u"")
178
179 - def gettarget(self):
180 translation = quote.propertiesdecode(self.translation) 181 translation = re.sub(u"\\\\ ", u" ", translation) 182 return translation
183 184 target = property(gettarget, settarget) 185
186 - def __str__(self):
187 """convert to a string. double check that unicode is handled somehow here""" 188 source = self.getoutput() 189 if isinstance(source, unicode): 190 return source.encode(default_encoding[self.personality]) 191 return source
192
193 - def getoutput(self):
194 """convert the element back into formatted lines for a .properties file""" 195 notes = self.getnotes() 196 if notes: 197 notes += u"\n" 198 if self.isblank(): 199 return notes 200 else: 201 if "\\u" in self.value and self.personality == "mozilla": 202 self.value = quote.mozillapropertiesencode(self.source) 203 if "\\u" in self.translation and self.personality == "mozilla": 204 self.translation = quote.mozillapropertiesencode(self.target) 205 value = self.translation or self.value 206 return u"%s%s%s%s\n" % (notes, self.name, self.delimeter, value)
207
208 - def getlocations(self):
209 return [self.name]
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
218 - def getnotes(self, origin=None):
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
224 - def removenotes(self):
225 self.comments = []
226
227 - def isblank(self):
228 """returns whether this is a blank element, containing only comments...""" 229 return not (self.name or self.value)
230
231 - def istranslatable(self):
232 return bool(self.name)
233
234 - def getid(self):
235 return self.name
236
237 -class propfile(base.TranslationStore):
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 # handle multiline value if we're in one 256 line = quote.rstripeol(line) 257 if inmultilinevalue: 258 newunit.value += line.lstrip() 259 # see if there's more 260 inmultilinevalue = is_line_continuation(newunit.value) 261 # if we're still waiting for more... 262 if inmultilinevalue: 263 # strip the backslash 264 newunit.value = newunit.value[:-1] 265 if not inmultilinevalue: 266 # we're finished, add it to the list... 267 self.addunit(newunit) 268 newunit = propunit("", personality) 269 # otherwise, this could be a comment 270 elif line.strip()[:1] in (u'#', u'!'): 271 # add a comment 272 newunit.comments.append(line) 273 elif not line.strip(): 274 # this is a blank line... 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 # otherwise, this is a definition 283 else: 284 newunit.delimeter = delimeter_char 285 newunit.name = key_strip(line[:delimeter_pos]) 286 newunit.value = line[delimeter_pos+1:].lstrip() 287 # backslash at end means carry string on to next line 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 # see if there is a leftover one... 295 if inmultilinevalue or len(newunit.comments) > 0: 296 self.addunit(newunit)
297
298 - def __str__(self):
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