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_delimiter(line):
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 # Find the position of each delimiter type 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 # Find the first "=" or ":" delimiter 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 # Use space delimiter if we found nothing else 95 return (" ", delimiters[" "]) 96 if mindelimiter is not None and delimiters[" "] < delimiters[mindelimiter]: 97 # If space delimiter occurs earlier then ":" or "=" then it is the 98 # delimiter only if there are non-whitespace characters between it and 99 # the other detected delimiter. 100 if len(line[delimiters[" "]:delimiters[mindelimiter]].strip()) > 0: 101 return (" ", delimiters[" "]) 102 return (mindelimiter, minpos)
103
104 -def find_delimeter(line):
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
111 -def is_line_continuation(line):
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 # Count the slashes from the end of the line. Ensure we don't 129 # go into infinite loop. 130 while len(line) >= -pos and line[pos:][0] == "\\": 131 pos -= 1 132 count += 1 133 return (count % 2) == 1 # Odd is a line continuation, even is not
134
135 -def key_strip(key):
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 # If line now end in \ we put back the whitespace that was escaped 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
151 -class propunit(base.TranslationUnit):
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"):
155 """construct a blank propunit""" 156 self.personality = personality 157 super(propunit, self).__init__(source) 158 self.name = "" 159 self.value = u"" 160 self.translation = u"" 161 self.delimiter = u"=" 162 self.comments = [] 163 self.source = source
164
165 - def setsource(self, source):
166 self._rich_source = None 167 source = data.forceunicode(source) 168 if self.personality == "mozilla" or self.personality == "skype": 169 self.value = quote.mozillapropertiesencode(source or u"") 170 else: 171 self.value = quote.javapropertiesencode(source or u"")
172
173 - def getsource(self):
174 value = quote.propertiesdecode(self.value) 175 value = re.sub(u"\\\\ ", u" ", value) 176 return value
177 178 source = property(getsource, setsource) 179
180 - def settarget(self, target):
181 self._rich_target = None 182 target = data.forceunicode(target) 183 if self.personality == "mozilla" or self.personality == "skype": 184 self.translation = quote.mozillapropertiesencode(target or u"") 185 else: 186 self.translation = quote.javapropertiesencode(target or u"")
187
188 - def gettarget(self):
189 translation = quote.propertiesdecode(self.translation) 190 translation = re.sub(u"\\\\ ", u" ", translation) 191 return translation
192 193 target = property(gettarget, settarget) 194
195 - def __str__(self):
196 """convert to a string. double check that unicode is handled somehow here""" 197 source = self.getoutput() 198 if isinstance(source, unicode): 199 return source.encode(default_encoding[self.personality]) 200 return source
201
202 - def getoutput(self):
203 """convert the element back into formatted lines for a .properties file""" 204 notes = self.getnotes() 205 if notes: 206 notes += u"\n" 207 if self.isblank(): 208 return notes 209 else: 210 if "\\u" in self.value and self.personality == "mozilla": 211 self.value = quote.mozillapropertiesencode(self.source) 212 if "\\u" in self.translation and self.personality == "mozilla": 213 self.translation = quote.mozillapropertiesencode(self.target) 214 value = self.translation or self.value 215 return u"%s%s%s%s\n" % (notes, self.name, self.delimiter, value)
216
217 - def getlocations(self):
218 return [self.name]
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
227 - def getnotes(self, origin=None):
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
233 - def removenotes(self):
234 self.comments = []
235
236 - def isblank(self):
237 """returns whether this is a blank element, containing only comments...""" 238 return not (self.name or self.value)
239
240 - def istranslatable(self):
241 return bool(self.name)
242
243 - def getid(self):
244 return self.name
245
246 -class propfile(base.TranslationStore):
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 # handle multiline value if we're in one 265 line = quote.rstripeol(line) 266 if inmultilinevalue: 267 newunit.value += line.lstrip() 268 # see if there's more 269 inmultilinevalue = is_line_continuation(newunit.value) 270 # if we're still waiting for more... 271 if inmultilinevalue: 272 # strip the backslash 273 newunit.value = newunit.value[:-1] 274 if not inmultilinevalue: 275 # we're finished, add it to the list... 276 self.addunit(newunit) 277 newunit = propunit("", personality) 278 # otherwise, this could be a comment 279 elif line.strip()[:1] in (u'#', u'!'): 280 # add a comment 281 newunit.comments.append(line) 282 elif not line.strip(): 283 # this is a blank line... 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 # otherwise, this is a definition 292 else: 293 newunit.delimiter = delimiter_char 294 newunit.name = key_strip(line[:delimiter_pos]) 295 newunit.value = line[delimiter_pos+1:].lstrip() 296 # backslash at end means carry string on to next line 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 # see if there is a leftover one... 304 if inmultilinevalue or len(newunit.comments) > 0: 305 self.addunit(newunit)
306
307 - def __str__(self):
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