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