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