~ubuntu-branches/ubuntu/precise/tmispell-voikko/precise

« back to all changes in this revision

Viewing changes to src/config_file.cc

  • Committer: Bazaar Package Importer
  • Author(s): Timo Jyrinki
  • Date: 2006-10-18 20:04:10 UTC
  • Revision ID: james.westby@ubuntu.com-20061018200410-zd4fwc1xwkclmb51
Tags: upstream-0.6.1
ImportĀ upstreamĀ versionĀ 0.6.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Copyright (C) Pauli Virtanen
 
2
 *
 
3
 * This program is free software; you can redistribute it and/or
 
4
 * modify it under the terms of the GNU General Public License
 
5
 * as published by the Free Software Foundation; either version 2
 
6
 * of the License, or (at your option) any later version.
 
7
 *
 
8
 * This program is distributed in the hope that it will be useful,
 
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
 * GNU General Public License for more details.
 
12
 *
 
13
 * You should have received a copy of the GNU General Public License
 
14
 * along with this program; if not, write to the Free Software
 
15
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
16
 *********************************************************************************/
 
17
 
 
18
/**
 
19
 * @file config_file.cc
 
20
 * @author Pauli Virtanen
 
21
 *
 
22
 * Parsing the configuration file.
 
23
 */
 
24
#include <iostream>
 
25
#include <fstream>
 
26
#include <sstream>
 
27
#include <string>
 
28
#include <map>
 
29
 
 
30
#include <cctype>
 
31
 
 
32
#include "i18n.hh"
 
33
#include "common.hh"
 
34
#include "regexp.hh"
 
35
#include "tmerror.hh"
 
36
 
 
37
#include "config_file.hh"
 
38
 
 
39
using std::istream;
 
40
using std::ifstream;
 
41
using std::ostringstream;
 
42
using std::map;
 
43
using std::string;
 
44
 
 
45
/*
 
46
 * The configuration file contains spell-checker entries and options.
 
47
 *
 
48
 * Spell checker entry format: (on one line)
 
49
 * "identifier" "library" "dictionary" "word_chars" "boundary_chars"
 
50
 *
 
51
 * Option format:
 
52
 *   <key> = <value>
 
53
 *
 
54
 * The comment character is '#' after which anything is ignored.
 
55
 * Strings may span multiple lines, if needed.
 
56
 */
 
57
 
 
58
/****************************************************************************/
 
59
/** @name Utility for parsing the configuration file
 
60
 ** @{
 
61
 **/
 
62
 
 
63
/**
 
64
 * For reading a file part by part.
 
65
 */
 
66
class ConfigFileIterator
 
67
{
 
68
public:
 
69
        /// Open a file for reading
 
70
        ConfigFileIterator(string const& file_name);
 
71
 
 
72
        /// Get the next quoted string, skipping no-linefeed whitespace first
 
73
        string get_next_quoted_string();
 
74
 
 
75
        /// Skip whitespace, optionally also linefeeds
 
76
        void skip_whitespace(bool skip_lf);
 
77
 
 
78
        /// Skip comments and whitespace
 
79
        void skip_comments_and_whitespace();
 
80
 
 
81
        /// Return the current line
 
82
        int line() const { return line_; }
 
83
 
 
84
        /// Match a regular expression to the current position
 
85
        bool match(RegExp& re) const {
 
86
                return re.match(buffer_, p_);
 
87
        }
 
88
 
 
89
        string::const_iterator begin(RegExp& re, int nth=0)
 
90
                { return p_ + re.begin(nth); }
 
91
 
 
92
        string::const_iterator end(RegExp& re, int nth=0)
 
93
                { return p_ + re.end(nth); }
 
94
 
 
95
        std::string sub(RegExp& re, int nth=0)
 
96
                {
 
97
                        return re.sub(buffer_, nth);
 
98
                }
 
99
        
 
100
        /// Set the current position
 
101
        void position(string::const_iterator p) { p_ = p; }
 
102
 
 
103
        /// Check whether there is still more data
 
104
        operator bool() const { return p_ != buffer_.end(); }
 
105
        
 
106
private:
 
107
        int line_; ///< Current line
 
108
        string buffer_; ///< Buffer where file is
 
109
        string::const_iterator p_; ///< Current position in buffer
 
110
};
 
111
 
 
112
void ConfigFileIterator::skip_whitespace(bool skip_lf)
 
113
{
 
114
        while (p_ != buffer_.end() && std::isspace(*p_)) {
 
115
                if (*p_ == '\n') {
 
116
                        ++line_;
 
117
                        if (!skip_lf) { ++p_; break; }
 
118
                }
 
119
                ++p_;
 
120
        }
 
121
}
 
122
 
 
123
/**
 
124
 * Read a string, that may be quoted in single or double quotes, beginning
 
125
 * from current position, after possible whitespace. May span multiple lines.
 
126
 * If quoted, then the quoting character '\' just quotes the next character
 
127
 * as-is.
 
128
 * @return The quoted string that was found.
 
129
 */
 
130
string ConfigFileIterator::get_next_quoted_string()
 
131
{
 
132
        skip_whitespace(false);
 
133
        
 
134
        string::const_iterator str_beg; // Where the extracted substring begins
 
135
        string::const_iterator str_end; // Where the extracted substring ends
 
136
 
 
137
        if (p_ != buffer_.end() && (*p_ == '\'' || *p_ == '"')) {
 
138
                char quote_char = *p_;
 
139
 
 
140
                ++p_;
 
141
 
 
142
                str_beg = p_;
 
143
                
 
144
                for (; p_ != buffer_.end(); ++p_) {
 
145
                        if (*p_ == quote_char) {
 
146
                                break;
 
147
                        } else if (*p_ == '\\') {
 
148
                                ++p_;
 
149
                                if (p_ == buffer_.end() || *p_ == '\n') {
 
150
                                        throw Error(_("\\ at the end of "
 
151
                                                      "a string"));
 
152
                                }
 
153
                        }
 
154
                }
 
155
                
 
156
                if (p_ != buffer_.end() && *p_ == quote_char) {
 
157
                        str_end = p_;
 
158
                        ++p_;
 
159
                } else {
 
160
                        throw Error(_("Unterminated quoted string"));
 
161
                }
 
162
 
 
163
        } else {
 
164
                str_beg = p_;
 
165
                while (p_ != buffer_.end() && *p_ != '\n' && !std::isspace(*p_))
 
166
                        ++p_;
 
167
                str_end = p_;
 
168
        }
 
169
 
 
170
        return string(str_beg, str_end);
 
171
}
 
172
 
 
173
/**
 
174
 * Skip content of lines after '#'. Also skip linefeeds and whitespace.
 
175
 */
 
176
void ConfigFileIterator::skip_comments_and_whitespace()
 
177
{
 
178
        while (p_ != buffer_.end()) {
 
179
                if (*p_ == '#') {
 
180
                        while (p_ != buffer_.end() && *p_ != '\n') ++p_;
 
181
                } else if (std::isspace(*p_)) {
 
182
                        skip_whitespace(true);
 
183
                } else {
 
184
                        break;
 
185
                }
 
186
        }
 
187
}
 
188
 
 
189
/**
 
190
 * Read the given file to memory for parsing.
 
191
 */
 
192
ConfigFileIterator::ConfigFileIterator(string const& file_name)
 
193
{
 
194
        ifstream in(file_name.c_str());
 
195
        ostringstream out;
 
196
 
 
197
        if (!in) {
 
198
                throw Error(_("Unable to open configuration file %s"),
 
199
                            file_name.c_str());
 
200
        }
 
201
 
 
202
        char buf[1024];
 
203
        int readen;
 
204
        while ((readen = in.rdbuf()->sgetn(buf, 1024)) > 0)
 
205
                out.write(buf, readen);
 
206
 
 
207
        buffer_ = out.str();
 
208
        line_ = 1;
 
209
        p_ = buffer_.begin();
 
210
}
 
211
 
 
212
/** @} */
 
213
 
 
214
 
 
215
/****************************************************************************/
 
216
/** @name Parsing the configuration file
 
217
 ** @{
 
218
 **/
 
219
 
 
220
/**
 
221
 * Initialize a parse error message.
 
222
 */
 
223
ParseError::ParseError(std::string const& what,
 
224
                       std::string const& file, int line, int column)
 
225
        : Error("")
 
226
{
 
227
        if (line > 0 && column > 0) {
 
228
                msg_ = ssprintf(_("Parse error in file \"%s\" on line %d, "
 
229
                                  "column %d: %s"),
 
230
                                line, column, what.c_str());
 
231
        } else if (line > 0) {
 
232
                msg_ = ssprintf(_("Parse error in file \"%s\" on line %d: %s"),
 
233
                                line, what.c_str());
 
234
        } else {
 
235
                msg_ = ssprintf(_("Parse error in file \"%s\": %s"),
 
236
                                what.c_str());
 
237
        }
 
238
}
 
239
 
 
240
/**
 
241
 * Read a spell checker entry from config file and add the entry.
 
242
 */
 
243
static void add_spell_checker_entry(ConfigFileIterator& i,
 
244
                                    ConfigFile::SpellcheckerMap& entries)
 
245
{
 
246
        string id = i.get_next_quoted_string();
 
247
        
 
248
        string library = i.get_next_quoted_string();
 
249
        string dictionary = i.get_next_quoted_string();
 
250
        string encoding = i.get_next_quoted_string();
 
251
        string lc_ctype = i.get_next_quoted_string();
 
252
        string word_chars = i.get_next_quoted_string();
 
253
        string boundary_chars = i.get_next_quoted_string();
 
254
 
 
255
        if (id.empty() || library.empty() || dictionary.empty() ||
 
256
            encoding.empty()) {
 
257
                throw Error(_("Incomplete spell checker entry"));
 
258
        }
 
259
        
 
260
        entries.insert(
 
261
                ConfigFile::SpellcheckerMap::value_type(
 
262
                        id,
 
263
                        SpellcheckerEntry(library,
 
264
                                          dictionary,
 
265
                                          encoding,
 
266
                                          lc_ctype,
 
267
                                          word_chars,
 
268
                                          boundary_chars)));
 
269
}
 
270
 
 
271
/**
 
272
 * Check if there is an option line at current position.
 
273
 * If so, store the key-value pair.
 
274
 * NOTE: This assumes that i is at a beginning of something, not at whitespace.
 
275
 */
 
276
static bool handle_option_lines(ConfigFileIterator& i,
 
277
                                ConfigFile::OptionMap* options)
 
278
{
 
279
        static RegExp optre("^([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*",
 
280
                            RegExp::EXTENDED|RegExp::ICASE);
 
281
 
 
282
        if (i.match(optre)) {
 
283
                i.position(i.end(optre));
 
284
                string read = i.get_next_quoted_string();
 
285
                (*options)[i.sub(optre, 1)] = read;
 
286
                return true;
 
287
        } else {
 
288
                return false;
 
289
        }
 
290
}
 
291
 
 
292
/**
 
293
 * Read and parse configuration file.
 
294
 */
 
295
ConfigFile::ConfigFile(string const& file_name)
 
296
{
 
297
        ConfigFileIterator i(file_name);
 
298
 
 
299
        i.skip_comments_and_whitespace();
 
300
        while (i) {
 
301
                try {
 
302
                        if (handle_option_lines(i, &options_))
 
303
                                /* nothing */;
 
304
                        else
 
305
                                add_spell_checker_entry(i, entries_);
 
306
                        
 
307
                } catch (Error const& err) {
 
308
                        throw ParseError(err.what(),
 
309
                                         file_name,
 
310
                                         i.line());
 
311
                }
 
312
                
 
313
                i.skip_comments_and_whitespace();
 
314
        }
 
315
}
 
316
 
 
317
/** @} */
 
318
 
 
319
 
 
320
/** Create a new spell checker entry */
 
321
SpellcheckerEntry::SpellcheckerEntry(std::string library,
 
322
                                     std::string dictionary,
 
323
                                     std::string encoding,
 
324
                                     std::string lc_ctype,
 
325
                                     std::string word_chars,
 
326
                                     std::string boundary_chars)
 
327
        : library_(library),
 
328
          dictionary_(dictionary),
 
329
          encoding_(encoding),
 
330
          lc_ctype_(lc_ctype)
 
331
{
 
332
        word_chars_.insert(word_chars_.begin(), 
 
333
                           word_chars.begin(),
 
334
                           word_chars.end());
 
335
        boundary_chars_.insert(boundary_chars_.begin(), 
 
336
                               boundary_chars.begin(),
 
337
                               boundary_chars.end());
 
338
}
 
339
 
 
340
/**
 
341
 * Case insensitive string lexical StrictWeakOrdering.
 
342
 */
 
343
bool NocaseCmp::operator()(std::string const& s1, std::string const& s2) const
 
344
{
 
345
        std::string::const_iterator i1, i2;
 
346
 
 
347
        for (i1 = s1.begin(), i2 = s2.begin();
 
348
             i1 != s1.end() && i2 != s2.end();
 
349
             ++i1, ++i2)
 
350
        {
 
351
                int r = std::tolower(*i1) - std::tolower(*i2);
 
352
                if (r < 0)
 
353
                        return true;
 
354
                else if (r > 0)
 
355
                        return false;
 
356
        }
 
357
        return false;
 
358
}
 
359
 
 
360
/**
 
361
 * Gets a string corresponding to the given key from the given map,
 
362
 * or empty string if there is no match.
 
363
 */
 
364
SpellcheckerEntry const& ConfigFile::get(string const& id) const
 
365
{
 
366
        static SpellcheckerEntry none("", "", "", "", "", "");
 
367
 
 
368
        SpellcheckerMap::const_iterator i = entries_.find(id);
 
369
        if (i == entries_.end()) {
 
370
                return none;
 
371
        } else {
 
372
                return i->second;
 
373
        }
 
374
}
 
375
 
 
376
bool ConfigFile::has(std::string const& id) const
 
377
{
 
378
        return (entries_.find(id) != entries_.end());
 
379
}
 
380
 
 
381
 
 
382
string const& ConfigFile::get_option(string const& option_name) const
 
383
{
 
384
        static string none;
 
385
 
 
386
        OptionMap::const_iterator i = options_.find(option_name);
 
387
        if (i == options_.end()) {
 
388
                return none;
 
389
        } else {
 
390
                return i->second;
 
391
        }
 
392
}