1# 2# Copyright 2007,2009 Zuza Software Foundation 3# 4# This file is part of the Translate Toolkit. 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, see <http://www.gnu.org/licenses/>. 18 19"""Class that manages .ini files for translation 20 21.. note::: A simple summary of what is permissible follows. 22 23# a comment 24; a comment 25 26[Section] 27a = a string 28b : a string 29""" 30 31import re 32from io import StringIO 33 34from translate.storage import base 35 36 37try: 38 from iniparse import INIConfig 39except ImportError: 40 raise ImportError("Missing iniparse library.") 41 42 43dialects = {} 44 45 46def register_dialect(dialect): 47 """Decorator that registers the dialect.""" 48 dialects[dialect.name] = dialect 49 return dialect 50 51 52class Dialect: 53 """Base class for differentiating dialect options and functions""" 54 55 name = None 56 57 58@register_dialect 59class DialectDefault(Dialect): 60 name = "default" 61 62 def unescape(self, text): 63 return text 64 65 def escape(self, text): 66 return text 67 68 69@register_dialect 70class DialectInno(DialectDefault): 71 name = "inno" 72 73 def unescape(self, text): 74 return text.replace("%n", "\n").replace("%t", "\t") 75 76 def escape(self, text): 77 return text.replace("\t", "%t").replace("\n", "%n") 78 79 80class iniunit(base.TranslationUnit): 81 """A INI file entry""" 82 83 def __init__(self, source=None, **kwargs): 84 self.location = "" 85 if source: 86 self.source = source 87 super().__init__(source) 88 89 def addlocation(self, location): 90 self.location = location 91 92 def getlocations(self): 93 return [self.location] 94 95 96class inifile(base.TranslationStore): 97 """An INI file""" 98 99 UnitClass = iniunit 100 101 def __init__(self, inputfile=None, dialect="default", **kwargs): 102 """construct an INI file, optionally reading in from inputfile.""" 103 self._dialect = dialects.get( 104 dialect, DialectDefault 105 )() # fail correctly/use getattr/ 106 super().__init__(**kwargs) 107 self.filename = "" 108 self._inifile = None 109 if inputfile is not None: 110 self.parse(inputfile) 111 112 def serialize(self, out): 113 _outinifile = self._inifile 114 for unit in self.units: 115 for location in unit.getlocations(): 116 match = re.match("\\[(?P<section>.+)\\](?P<entry>.+)", location) 117 value = self._dialect.escape(unit.target) 118 _outinifile[match.groupdict()["section"]][ 119 match.groupdict()["entry"] 120 ] = value 121 if _outinifile: 122 out.write(str(_outinifile).encode("utf-8")) 123 124 def parse(self, input): 125 """Parse the given file or file source string.""" 126 if hasattr(input, "name"): 127 self.filename = input.name 128 elif not getattr(self, "filename", ""): 129 self.filename = "" 130 if hasattr(input, "read"): 131 inisrc = input.read() 132 input.close() 133 input = inisrc 134 135 if isinstance(input, bytes): 136 input = StringIO(input.decode("utf-8")) 137 self._inifile = INIConfig(input, optionxformvalue=None) 138 else: 139 self._inifile = INIConfig(open(input), optionxformvalue=None) 140 141 for section in self._inifile: 142 for entry in self._inifile[section]: 143 source = self._dialect.unescape(self._inifile[section][entry]) 144 newunit = self.addsourceunit(source) 145 newunit.addlocation(f"[{section}]{entry}") 146