Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
inifile.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "inifile.hh"
3#include "unicode.hh"
4#include "utils.hh"
5#include "strings.hh"
6#include <cstring>
7
8#define IDEBUG(...) Ase::debug ("inifile", __VA_ARGS__)
9
10#define ISASCIINLSPACE(c) (c == ' ' || (c >= 9 && c <= 13)) // ' \t\n\v\f\r'
11#define ISASCIIWHITESPACE(c) (c == ' ' || c == '\t' || (c >= 11 && c <= 13)) // ' \t\v\f\r'
12
13namespace Ase {
14
31/* TODO: possible IniFile improvements
32 * - support \xUUUU unicode escapes in strings
33 * - support \s for space (desktop-entry-spec)
34 * - support value list parsing, using ';' as delimiters
35 * - support current locale matching, including locale aliases
36 * - support merging of duplicates
37 * - support %(var) interpolation like Pyton's configparser.ConfigParser
38 * - parse into vector<IniEntry> which are: { kind=(section|assignment|other); String text, comment; }
39 */
40
41static bool
42parse_whitespaces (const char **stringp, int min_spaces)
43{
44 const char *p = *stringp;
45 while (ISASCIIWHITESPACE (*p))
46 p++;
47 if (p - *stringp >= min_spaces)
48 {
49 *stringp = p;
50 return true;
51 }
52 return false;
53}
54
55static bool
56skip_whitespaces (const char **stringp)
57{
58 return parse_whitespaces (stringp, 0);
59}
60
61static inline bool
62scan_escaped (const char **stringp, size_t *linenop, const char term)
63{
64 const char *p = *stringp;
65 size_t lineno = *linenop;
66 while (*p)
67 if (*p == term)
68 {
69 p++;
70 *stringp = p;
71 *linenop = lineno;
72 return true;
73 }
74 else if (p[0] == '\\' && p[1])
75 p += 2;
76 else // *p != 0
77 {
78 if (*p == '\n')
79 lineno++;
80 p++;
81 }
82 return false;
83}
84
85static String
86string_rtrim (String &str)
87{
88 const char *s = str.data();
89 const char *e = s + str.size();
90 while (e > s && ISASCIINLSPACE (e[-1]))
91 e--;
92 const size_t l = e - s;
93 str.erase (str.begin() + l, str.end());
94 return String (str.data(), str.size()); // force copy
95}
96
97static bool
98scan_value (const char **stringp, size_t *linenop, String *valuep, const char *termchars = "")
99{ // read up to newline or comment or termchars, support line continuation and quoted strings
100 const char *p = *stringp;
101 size_t lineno = *linenop;
102 String v;
103 v.reserve (16);
104 for (;; ++p)
105 switch (p[0])
106 {
107 const char *d;
108 case '\\':
109 if (p[1] == '\n' || (p[1] == '\r' && p[2] == '\n'))
110 {
111 p += 1 + (p[1] == '\r');
112 lineno++;
113 break;
114 }
115 else if (!p[1])
116 break; // ignore
117 p++;
118 v += String ("\\") + p[0];
119 break;
120 case '"': case '\'':
121 d = p;
122 p++;
123 if (scan_escaped (&p, &lineno, d[0]))
124 {
125 v += String (d, p - d);
126 p--; // back off to terminating \" or \'
127 }
128 else
129 return false; // brr, unterminated string
130 break;
131 case '\r':
132 if (p[1] && p[1] != '\n')
133 v += ' '; // turn stray '\r' into space
134 break;
135 default:
136 if (!strchr (termchars, p[0]))
137 {
138 v += p[0];
139 break;
140 }
141 // fall through
142 case 0: case '\n': case ';': case '#':
143 *stringp = p;
144 *linenop = lineno;
145 *valuep = string_rtrim (v); // forces copy
146 return true;
147 }
148}
149
150static bool
151skip_line (const char **stringp, size_t *linenop, String *textp)
152{ // ( !'\n' )* '\n'
153 const char *p = *stringp, *const start = p;
154 size_t lineno = *linenop;
155 while (*p && *p != '\n')
156 p++;
157 if (textp)
158 *textp = String (start, p - start);
159 if (*p == '\n')
160 {
161 lineno++;
162 p++;
163 }
164 *stringp = p;
165 *linenop = lineno;
166 return true;
167}
168
169static bool
170skip_commentline (const char **stringp, size_t *linenop, String *commentp = NULL)
171{ // S* ( '#' | ';' ) ( !'\n' )* '\n'
172 const char *p = *stringp;
173 skip_whitespaces (&p);
174 if (*p != '#' && *p != ';')
175 return false;
176 p++;
177 *stringp = p;
178 return skip_line (stringp, linenop, commentp);
179}
180
181static bool
182skip_to_eol (const char **stringp, size_t *linenop)
183{ // comment? '\r'? '\n', or EOF
184 const char *p = *stringp;
185 if (*p == '#' || *p == ';')
186 return skip_commentline (stringp, linenop);
187 if (*p == '\r')
188 p++;
189 if (*p == '\n')
190 {
191 p++;
192 (*linenop) += 1;
193 *stringp = p;
194 return true;
195 }
196 if (!*p)
197 {
198 *stringp = p;
199 return true;
200 }
201 return false;
202}
203
204static bool
205parse_assignment (const char **stringp, size_t *linenop, String *keyp, String *localep, String *valuep)
206{ // S* KEYCHARS+ S* ( '[' S* LOCALECHARS* S* ']' )? S* ( '=' | ':' ) scan_value comment? EOL
207 const char *p = *stringp;
208 size_t lineno = *linenop;
209 String key, locale, value;
210 bool success = true;
211 success = success && skip_whitespaces (&p);
212 success = success && scan_value (&p, &lineno, &key, "[]=:");
213 success = success && skip_whitespaces (&p);
214 if (success && *p == '[')
215 {
216 p++;
217 success = success && skip_whitespaces (&p);
218 success = success && scan_value (&p, &lineno, &locale, "[]");
219 success = success && skip_whitespaces (&p);
220 if (*p != ']')
221 return false;
222 p++;
223 success = success && skip_whitespaces (&p);
224 }
225 if (!success)
226 return false;
227 if (*p != '=' && *p != ':')
228 return false;
229 p++;
230 success = success && skip_whitespaces (&p);
231 success = success && scan_value (&p, &lineno, &value);
232 success = success && skip_to_eol (&p, &lineno);
233 if (!success)
234 return false;
235 *stringp = p;
236 *linenop = lineno;
237 *keyp = key;
238 *localep = locale;
239 *valuep = value;
240 return true;
241}
242
243static bool
244parse_section (const char **stringp, size_t *linenop, String *sectionp)
245{ // S* '[' S* SECTIONCHARS+ S* ']' S* comment? EOL
246 const char *p = *stringp;
247 size_t lineno = *linenop;
248 String section;
249 bool success = true;
250 success = success && skip_whitespaces (&p);
251 if (*p != '[')
252 return false;
253 p++;
254 success = success && skip_whitespaces (&p);
255 success = success && scan_value (&p, &lineno, &section, "[]");
256 success = success && skip_whitespaces (&p);
257 if (*p != ']')
258 return false;
259 p++;
260 success = success && skip_whitespaces (&p);
261 success = success && skip_to_eol (&p, &lineno);
262 if (!success)
263 return false;
264 *stringp = p;
265 *linenop = lineno;
266 *sectionp = section;
267 return true;
268}
269
270void
271IniFile::load_ini (const String &inputname, const String &data)
272{
273 const char *p = data.c_str();
274 size_t nextno = 1;
275 String section = "";
276 while (*p)
277 {
278 const size_t lineno = nextno;
279 String text, key, locale, *debugp = 0 ? &text : NULL; // DEBUG parsing?
280 if (skip_commentline (&p, &nextno, debugp))
281 {
282 if (debugp)
283 printerr ("%s:%d: #%s\n", inputname.c_str(), lineno, debugp->c_str());
284 }
285 else if (parse_section (&p, &nextno, &text))
286 {
287 if (debugp)
288 printerr ("%s:%d: %s\n", inputname.c_str(), lineno, text.c_str());
289 section = text;
290 if (strchr (section.c_str(), '"'))
291 { // reconstruct section path from '[branch "devel.wip"]' syntax
292 StringS sv = string_split (section);
293 for (auto &s : sv)
294 if (s.c_str()[0] == '"')
295 s = string_from_cquote (s);
296 section = string_join (".", sv);
297 }
298 }
299 else if (parse_assignment (&p, &nextno, &key, &locale, &text))
300 {
301 if (debugp)
302 printerr ("%s:%d:\t%s[%s] = %s\n", inputname.c_str(), lineno, key.c_str(), locale.c_str(), string_to_cquote (text));
303 String k (key);
304 if (!locale.empty())
305 k += "[" + locale + "]";
306 if (strchr (section.c_str(), '=') || strchr (key.c_str(), '.'))
307 IDEBUG ("%s:%d: invalid key name: %s.%s", inputname.c_str(), lineno, section.c_str(), k.c_str());
308 else
309 sections_[section].push_back (k + "=" + text);
310 }
311 else if (skip_line (&p, &nextno, debugp))
312 {
313 if (debugp)
314 printerr ("%s:%d:~ %s\n", inputname.c_str(), lineno, debugp->c_str());
315 }
316 else
317 break; // EOF if !skip_line
318 }
319}
320
321IniFile::IniFile (const String &name, const String &inidata)
322{
323 load_ini (name, inidata);
324 if (sections_.empty())
325 IDEBUG ("empty INI file: %s", string_to_cquote (name));
326}
327
329{
330 if (blob)
331 load_ini (blob.name(), blob.string());
332 if (sections_.empty())
333 IDEBUG ("empty INI file: %s", string_to_cquote (blob ? blob.name() : "<NULL>"));
334}
335
337{
338 *this = source;
339}
340
341IniFile&
343{
344 sections_ = source.sections_;
345 return *this;
346}
347
348bool
350{
351 return !sections_.empty();
352}
353
354const StringS&
355IniFile::section (const String &name) const
356{
357 SectionMap::const_iterator cit = sections_.find (name);
358 if (cit != sections_.end())
359 return cit->second;
360 static const StringS empty_dummy;
361 return empty_dummy;
362}
363
364bool
365IniFile::has_section (const String &section) const
366{
367 SectionMap::const_iterator cit = sections_.find (section);
368 return cit != sections_.end();
369}
370
373{
374 StringS secs;
375 for (auto it : sections_)
376 secs.push_back (it.first);
377 return secs;
378}
379
381IniFile::attributes (const String &section) const
382{
383 StringS opts;
384 SectionMap::const_iterator cit = sections_.find (section);
385 if (cit != sections_.end())
386 for (auto s : cit->second)
387 opts.push_back (s.substr (0, s.find ('=')));
388 return opts;
389}
390
391bool
392IniFile::has_attribute (const String &section, const String &key) const
393{
394 SectionMap::const_iterator cit = sections_.find (section);
395 if (cit == sections_.end())
396 return false;
397 for (auto s : cit->second)
398 if (s.size() > key.size() && s[key.size()] == '=' && memcmp (s.data(), key.data(), key.size()) == 0)
399 return true;
400 return false;
401}
402
405{
406 StringS opts;
407 for (auto it : sections_)
408 for (auto s : it.second)
409 opts.push_back (it.first + "." + s);
410 return opts;
411}
412
413bool
414IniFile::has_raw_value (const String &dotpath, String *valuep) const
415{
416 const char *p = dotpath.c_str(), *d = strrchr (p, '.');
417 if (!d)
418 return false;
419 const String secname = String (p, d - p);
420 d++; // point to key
421 const StringS &sv = section (secname);
422 if (!sv.size())
423 return false;
424 const size_t l = dotpath.size() - (d - p); // key length
425 for (auto kv : sv)
426 if (kv.size() > l && kv[l] == '=' && memcmp (kv.data(), d, l) == 0)
427 {
428 if (valuep)
429 *valuep = kv.substr (l + 1);
430 return true;
431 }
432 return false;
433}
434
435String
436IniFile::raw_value (const String &dotpath) const
437{
438 String v;
439 has_raw_value (dotpath, &v);
440 return v;
441}
442
443String
445{
446 String v;
447 size_t dummy = 0;
448 for (const char *p = input.c_str(); *p; p++)
449 switch (*p)
450 {
451 const char *start;
452 case '\\':
453 switch (p[1])
454 {
455 case 0: break; // ignore trailing backslash
456 case 'n': v += '\n'; break;
457 case 'r': v += '\r'; break;
458 case 't': v += '\t'; break;
459 case 'b': v += '\b'; break;
460 case 'f': v += '\f'; break;
461 case 'v': v += '\v'; break;
462 default: v += p[1]; break;
463 }
464 p++;
465 break;
466 case '"': case '\'':
467 start = ++p;
468 if (scan_escaped (&p, &dummy, start[-1]))
469 {
470 p--; // back off to terminating \" or \'
471 v += string_from_cquote (String (start, p - start));
472 break;
473 }
474 // fall through
475 default:
476 v += p[0];
477 break;
478 }
479 return v;
480}
481
482bool
483IniFile::has_value (const String &dotpath, String *valuep) const
484{
485 const bool hasit = has_raw_value (dotpath, valuep);
486 if (valuep && hasit)
487 *valuep = cook_string (*valuep);
488 return hasit;
489}
490
491String
492IniFile::value_as_string (const String &dotpath) const
493{
494 String raw = raw_value (dotpath);
495 String v = cook_string (raw);
496 return v;
497}
498
499
500// == IniWriter ==
506IniWriter::Section*
507IniWriter::find_section (String name, bool create)
508{
509 for (size_t i = 0; i < sections_.size(); i++)
510 if (sections_[i].name == name)
511 return &sections_[i];
512 if (create)
513 {
514 const size_t i = sections_.size();
515 sections_.resize (i + 1);
516 sections_[i].name = name;
517 return &sections_[i];
518 }
519 return NULL;
520}
521
522size_t
523IniWriter::find_entry (IniWriter::Section &section, String name, bool create)
524{
525 for (size_t i = 0; i < section.entries.size(); i++)
526 if (section.entries[i].size() > name.size() &&
527 section.entries[i][name.size()] == '=' &&
528 section.entries[i].compare (0, name.size(), name) == 0)
529 return i;
530 if (create)
531 {
532 const size_t i = section.entries.size();
533 section.entries.push_back (name + "=");
534 return i;
535 }
536 return size_t (-1);
537}
538
540void
542{
543 const size_t p = key.rfind ('.');
544 if (p <= 0 || p + 1 >= key.size())
545 {
546 warning ("%s: invalid key: %s", __func__, key);
547 return; // invalid entry
548 }
549 Section *section = find_section (key.substr (0, p), true);
550 const String entry_key = key.substr (p + 1);
551 const size_t idx = find_entry (*section, entry_key, true);
552 section->entries[idx] = entry_key + "=" + value;
553}
554
556String
558{
559 String s;
560 for (size_t i = 0; i < sections_.size(); i++)
561 if (!sections_[i].entries.empty())
562 {
563 String sec = sections_[i].name;
564 const ssize_t d = sec.find ('.');
565 if (d >= 0 && d < sec.size())
566 sec = sec.substr (0, d) + " " + string_to_cquote (sec.substr (d + 1));
567 s += String ("[") + sec + "]\n";
568 for (size_t j = 0; j < sections_[i].entries.size(); j++)
569 {
570 const String raw = sections_[i].entries[j];
571 const size_t p = raw.find ('=');
572 const String k = raw.substr (0, p);
573 String v = raw.substr (p + 1);
574 static String allowed_chars = string_set_ascii_alnum() + "<>,;.:-_~*/+^!$=?";
575 if (!string_is_canonified (v, allowed_chars))
576 v = string_to_cquote (v);
577 s += string_format ("\t%s = %s\n", k, v);
578 }
579 }
580 return s;
581}
582
583} // Ase
T c_str(T... args)
Binary large object storage container.
Definition blob.hh:12
String name()
Retrieve the Blob's filename or url.
Definition blob.cc:88
String string()
Copy Blob data into a zero terminated string.
Definition blob.cc:117
Class to parse INI configuration file sections and values.
Definition inifile.hh:11
bool has_value(const String &dotpath, String *valuep=NULL) const
Check and possibly retrieve value if present.
Definition inifile.cc:483
bool has_raw_value(const String &dotpath, String *valuep=NULL) const
Check and possibly retrieve raw value if present.
Definition inifile.cc:414
StringS attributes(const String &section) const
List all attributes available in section.
Definition inifile.cc:381
bool has_attribute(const String &section, const String &key) const
Return if section contains key.
Definition inifile.cc:392
IniFile & operator=(const IniFile &source)
Assignment operator.
Definition inifile.cc:342
bool has_sections() const
Checks if IniFile is non-empty.
Definition inifile.cc:349
bool has_section(const String &section) const
Check presence of a section.
Definition inifile.cc:365
static String cook_string(const String &input_string)
Unquote contents of input_string
Definition inifile.cc:444
StringS sections() const
List all sections.
Definition inifile.cc:372
String raw_value(const String &dotpath) const
Retrieve raw (uncooked) value of section.attribute[locale].
Definition inifile.cc:436
IniFile(const String &name, const String &inidata)
Load INI file from immediate data.
Definition inifile.cc:321
StringS raw_values() const
List all section.attribute=value pairs.
Definition inifile.cc:404
String value_as_string(const String &dotpath) const
Retrieve value of section.attribute[locale].
Definition inifile.cc:492
void set(String key, String value)
Set (or add) a value with INI file semantics: section.key = value.
Definition inifile.cc:541
String output()
Generate INI file syntax for all values store in the class.
Definition inifile.cc:557
T data(T... args)
T empty(T... args)
T end(T... args)
T find(T... args)
memcmp
The Anklang C++ API namespace.
Definition api.hh:9
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
String string_from_cquote(const String &input)
Parse a possibly quoted C string into regular string.
Definition strings.cc:1043
String string_join(const String &junctor, const StringS &strvec)
Definition strings.cc:452
StringS string_split(const String &string, const String &splitter, size_t maxn)
Definition strings.cc:343
bool string_is_canonified(const String &string, const String &valid_chars)
Check if string_canonify() would modify string.
Definition strings.cc:90
std::vector< String > StringS
Convenience alias for a std::vector<std::string>.
Definition cxxaux.hh:36
const String & string_set_ascii_alnum()
Returns a string containing all of 0-9, A-Z and a-z.
Definition strings.cc:118
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
String string_to_cquote(const String &str)
Returns a string as C string including double quotes.
Definition strings.cc:1036
T push_back(T... args)
T reserve(T... args)
T resize(T... args)
T rfind(T... args)
T size(T... args)
strchr
strrchr
T substr(T... args)
typedef size_t