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"} 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.delimeter = u"=" 154 self.comments = [] 155 self.source = source
156
157 - def setsource(self, source):
158 """Sets the source AND the target to be equal""" 159 source = data.forceunicode(source) 160 if self.personality == "mozilla": 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 """Note: this also sets the .source attribute!""" 174 self.source = target
175
176 - def gettarget(self):
177 return self.source
178 target = property(gettarget, settarget) 179
180 - def __str__(self):
181 """convert to a string. double check that unicode is handled somehow here""" 182 source = self.getoutput() 183 if isinstance(source, unicode): 184 return source.encode(default_encoding[self.personality]) 185 return source
186
187 - def getoutput(self):
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
199 - def getlocations(self):
200 return [self.name]
201
202 - def addnote(self, note, origin=None):
203 note = data.forceunicode(note) 204 self.comments.append(note)
205
206 - def getnotes(self, origin=None):
207 return u'\n'.join(self.comments)
208
209 - def removenotes(self):
210 self.comments = []
211
212 - def isblank(self):
213 """returns whether this is a blank element, containing only comments...""" 214 return not (self.name or self.value)
215
216 -class propfile(base.TranslationStore):
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 # handle multiline value if we're in one 238 line = quote.rstripeol(line) 239 if inmultilinevalue: 240 newunit.value += line.lstrip() 241 # see if there's more 242 inmultilinevalue = is_line_continuation(newunit.value) 243 # if we're still waiting for more... 244 if inmultilinevalue: 245 # strip the backslash 246 newunit.value = newunit.value[:-1] 247 if not inmultilinevalue: 248 # we're finished, add it to the list... 249 self.addunit(newunit) 250 newunit = propunit("", personality) 251 # otherwise, this could be a comment 252 elif line.strip()[:1] in (u'#', u'!'): 253 # add a comment 254 newunit.comments.append(line) 255 elif not line.strip(): 256 # this is a blank line... 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 # otherwise, this is a definition 265 else: 266 newunit.delimeter = delimeter_char 267 newunit.name = key_strip(line[:delimeter_pos]) 268 newunit.value = line[delimeter_pos+1:].lstrip() 269 # backslash at end means carry string on to next line 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 # see if there is a leftover one... 277 if inmultilinevalue or len(newunit.comments) > 0: 278 self.addunit(newunit)
279
280 - def __str__(self):
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