ARB
ConfigMapping.cxx
Go to the documentation of this file.
1 // ========================================================= //
2 // //
3 // File : ConfigMapping.cxx //
4 // Purpose : config <-> string mapping //
5 // //
6 // Coded by Ralf Westram (coder@reallysoft.de) in Mar 19 //
7 // http://www.arb-home.de/ //
8 // //
9 // ========================================================= //
10 
11 #include "ConfigMapping.h"
12 #include <arb_msg.h>
13 
14 
15 using namespace std;
16 
18  string::iterator f = s.begin();
19  string::iterator t = s.begin();
20 
21  for (; f != s.end(); ++f, ++t) {
22  if (*f == '\\') {
23  ++f;
24  if (f == s.end()) return GBS_global_string("Trailing \\ in '%s'", s.c_str());
25  switch (*f) {
26  case 'n':
27  *t = '\n';
28  break;
29  case 'r':
30  *t = '\r';
31  break;
32  case 't':
33  *t = '\t';
34  break;
35  default:
36  *t = *f;
37  break;
38  }
39  }
40  else {
41  *t = *f;
42  }
43  }
44 
45  s.erase(t, f);
46 
47  return NULp;
48 }
49 
50 void ConfigMapping::encode_escapes(string& s, const char *to_escape) {
51  string neu;
52  neu.reserve(s.length()*2+1);
53 
54  for (string::iterator p = s.begin(); p != s.end(); ++p) {
55  if (*p == '\\' || strchr(to_escape, *p)) {
56  neu = neu+'\\'+*p;
57  }
58  else if (*p == '\n') { neu = neu+"\\n"; }
59  else if (*p == '\r') { neu = neu+"\\r"; }
60  else if (*p == '\t') { neu = neu+"\\t"; }
61  else { neu = neu+*p; }
62  }
63  s = neu;
64 }
65 
67  // parse string in format "key1='value1';key2='value2'"..
68  // and put values into a map.
69  //
70  // assumes that keys are unique
71 
72  size_t pos = 0;
73  GB_ERROR parse_error = NULp;
74 
75  while (!parse_error) {
76  size_t equal = configString.find('=', pos);
77  if (equal == string::npos) break;
78 
79  const char lookingAt = configString[equal+1];
80  if (lookingAt != '\'') {
81  parse_error = GBS_global_string("expected quote \"'\" after \"=\", found \"%c\"", lookingAt);
82  break;
83  }
84  size_t start = equal+2;
85  size_t end = configString.find('\'', start);
86  while (end != string::npos) {
87  if (configString[end-1] != '\\') break; // unescaped quote (=end of value)
88  if (configString[end-2] == '\\') { // multiple escapes -> count
89  int escCount = 2;
90  size_t bwd = end-3;
91  while (bwd>=start && configString[bwd] == '\\') {
92  escCount++;
93  bwd--;
94  }
95  if ((escCount%2) == 0) break; // even number of escapes => unescaped quote (=end of value)
96  // otherwise the quote belongs to the value
97  }
98  end = configString.find('\'', end+1);
99  }
100  if (end == string::npos) {
101  parse_error = "could not find matching quote \"'\"";
102  break;
103  }
104 
105  string config_name = configString.substr(pos, equal-pos);
106  string value = configString.substr(start, end-start);
107 
108  parse_error = decode_escapes(value);
109  if (!parse_error) {
110  set_entry(config_name, value);
111  }
112 
113  pos = end+2; // skip ';'
114  }
115  return parse_error;
116 }
117 
118 // --------------------------------------------------------------------------------
119 
120 #ifdef UNIT_TESTS
121 #ifndef TEST_UNIT_H
122 #include <test_unit.h>
123 #include <arb_defs.h>
124 #endif
125 
126 #define TEST_ESCAPE_ENCODING(plain,expected) do{ \
127  string encoded = plain; \
128  ConfigMapping::encode_escapes(encoded, "'"); \
129  TEST_EXPECT_EQUAL(encoded, expected); \
130  string decoded = encoded; \
131  TEST_EXPECT_NO_ERROR(ConfigMapping::decode_escapes(decoded)); \
132  TEST_EXPECT_EQUAL(decoded, plain); \
133  }while(0)
134 
135 void TEST_configValueEscaping() {
136  TEST_ESCAPE_ENCODING("hello", "hello"); // plain text
137 
138  TEST_ESCAPE_ENCODING("'hello'", "\\'hello\\'"); // quoted text
139  TEST_ESCAPE_ENCODING("\"hello\"", "\"hello\""); // double-quoted text
140 
141  // special characters (LF, CR + TAB shall not be saved to config, esp. if saving to file via AWT_config_manager)
142  TEST_ESCAPE_ENCODING("LINE\nNEXT", "LINE\\nNEXT");
143  TEST_ESCAPE_ENCODING("1\t2\r3", "1\\t2\\r3");
144 
145  // simple escape-handling
146  TEST_ESCAPE_ENCODING("hel\\lo", "hel\\\\lo");
147  TEST_ESCAPE_ENCODING("\\hello", "\\\\hello");
148  TEST_ESCAPE_ENCODING("hello\\", "hello\\\\");
149 
150  TEST_ESCAPE_ENCODING("hello\\'", "hello\\\\\\'");
151  TEST_ESCAPE_ENCODING("\\'hello\\'", "\\\\\\'hello\\\\\\'");
152 
153  string invalidEncoding = "xyz\\";
154  TEST_EXPECT_ERROR_CONTAINS(ConfigMapping::decode_escapes(invalidEncoding), "Trailing \\ in 'xyz\\'");
155 }
156 
157 struct TestedConfig {
158  const char *configString;
159  int entryCount;
160  const char *entryList;
161 };
162 struct ReinterpretedConfig {
163  const char *configString;
164  const char *reinterpreted;
165 };
166 struct FailingConfig {
167  const char *configString;
168  GB_ERROR error;
169 };
170 
171 static TestedConfig testedCfg[] = {
172  { "", 0, ""}, // empty config
173  { "tag='value'", 1, "tag" },
174  { "321='1st';abc='2nd'", 2, "321/abc" },
175  { "t-ha t='2';t;hi/s='1'", 2, "t-ha t,t;hi/s" }, // tags can contain anything but '='
176  { "t'ag'='value'", 1, "t'ag'" }, // even quotes are possible in tags
177 };
178 static FailingConfig failedCfg[] = {
179  { "nix=;was='ia'", "expected quote \"'\"" }, // no value
180  { "tag=value", "expected quote \"'\"" }, // unquoted value
181  { "hasto='match", "could not find matching quote" }, // unmatched quote
182 };
183 static ReinterpretedConfig reinterpretCfg[] = {
184  { "laksjd", "" }, // is reinterpreted as "" (empty config)
185  { "this='1';that='2'", "that='2';this='1'" }, // sorted by tagname
186 };
187 
188 inline bool anyStringContains(const CharPtrArray& strings, char c) {
189  for (int i = 0; strings[i]; ++i) {
190  if (strchr(strings[i], c)) {
191  return true;
192  }
193  }
194  return false;
195 }
196 static char autodetectSeparator(const CharPtrArray& strings) {
197  const char *sep = "/;,:|#*";
198  for (int i = 0; sep[i]; ++i) {
199  if (!anyStringContains(strings, sep[i])) {
200  return sep[i];
201  }
202  }
203  TEST_REJECT(true); // add more sep
204  return 0;
205 }
206 
207 void TEST_ConfigMapping() {
208  for (size_t c = 0; c<ARRAY_ELEMS(testedCfg); ++c) {
209  const TestedConfig& CFG = testedCfg[c];
210  TEST_ANNOTATE(CFG.configString);
211 
212  ConfigMapping config;
213  TEST_EXPECT_NO_ERROR(config.parseFrom(CFG.configString));
214 
215  // convert to string + compare with loaded config string:
216  TEST_EXPECT_EQUAL(config.config_string(), CFG.configString);
217 
218  // test entries:
219  ConstStrArray entries;
220  config.get_entries(entries);
221  TEST_EXPECT_EQUAL(entries.size(), CFG.entryCount);
222 
223  TEST_EXPECT_EQUAL_STRINGCOPY__NOERROREXPORTED(GBT_join_strings(entries, autodetectSeparator(entries)), CFG.entryList);
224  }
225 
226  // test nested quoting:
227  {
228  TEST_ANNOTATE(NULp);
229  const char *stored = "tag='val';'qtag'='\\'qval\\''";
230  ConfigMapping config;
231  TEST_EXPECT_NO_ERROR(config.parseFrom(stored));
232 
233  TEST_EXPECT_EQUAL(config.get_entry("tag"), "val");
234  TEST_EXPECT_EQUAL(config.get_entry("'qtag'"), "'qval'");
235 
236  TEST_EXPECT(config.has_entry("'qtag'"));
237  TEST_REJECT(config.has_entry("qtag"));
238 
239  const char *storedAsValue = "stored='tag=\\'val\\';\\'qtag\\'=\\'\\\\\\'qval\\\\\\'\\''";
241  wrapped.set_entry("stored", stored);
242 
243  TEST_EXPECT_EQUAL(wrapped.config_string(), storedAsValue);
244 
245  ConfigMapping unwrapped;
246  TEST_EXPECT_NO_ERROR(unwrapped.parseFrom(storedAsValue));
247  TEST_EXPECT_EQUAL(unwrapped.get_entry("stored"), stored);
248 
249  // test delete entry
250  unwrapped.delete_entry("stored");
251  TEST_EXPECT_EQUAL(unwrapped.config_string(), "");
252 
253  config.delete_entry("'qtag'");
254  TEST_EXPECT_EQUAL(config.config_string(), "tag='val'");
255 
256  config.set_entry("tag", "lav"); // test overwriting a value
257  TEST_EXPECT_EQUAL(config.config_string(), "tag='lav'");
258 
259  const char *SLASHED = "slashed\\";
260  config.set_entry("key", SLASHED);
261  TEST_EXPECT_EQUAL(config.config_string(), "key='slashed\\\\';tag='lav'");
262 
263  {
264  ConfigMapping slashed;
265  string cfgStr = config.config_string();
266 
267  TEST_EXPECT_NO_ERROR(slashed.parseFrom(cfgStr));
268  TEST_EXPECT_EQUAL(slashed.get_entry("key"), SLASHED);
269  TEST_EXPECT_EQUAL(slashed.config_string(), cfgStr);
270  }
271  }
272 
273  // test reinterpretation of configs:
274  for (size_t c = 0; c<ARRAY_ELEMS(reinterpretCfg); ++c) {
275  const ReinterpretedConfig& CFG = reinterpretCfg[c];
276  TEST_ANNOTATE(CFG.configString);
277  ConfigMapping config;
278  TEST_EXPECT_NO_ERROR(config.parseFrom(CFG.configString));
279 
280  // convert to string + compare with expected reinterpretation:
281  TEST_EXPECT_EQUAL(config.config_string(), CFG.reinterpreted);
282  }
283 
284  // test error-config:
285  for (size_t c = 0; c<ARRAY_ELEMS(failedCfg); ++c) {
286  const FailingConfig& CFG = failedCfg[c];
287  TEST_ANNOTATE(CFG.configString);
288  ConfigMapping config;
289  TEST_EXPECT_ERROR_CONTAINS(config.parseFrom(CFG.configString), CFG.error);
290  }
291 }
292 
293 #endif // UNIT_TESTS
294 
295 // --------------------------------------------------------------------------------
296 
const char * GB_ERROR
Definition: arb_core.h:25
void delete_entry(const char *entry)
Definition: ConfigMapping.h:49
return string(buffer, length)
const char * GBS_global_string(const char *templat,...)
Definition: arb_msg.cxx:203
STL namespace.
#define ARRAY_ELEMS(array)
Definition: arb_defs.h:19
bool has_entry(const char *entry) const
Definition: ConfigMapping.h:35
static HelixNrInfo * start
std::string config_string() const
Definition: ConfigMapping.h:55
#define TEST_EXPECT(cond)
Definition: test_unit.h:1328
void get_entries(class ConstStrArray &to_array)
Definition: ConfigMapping.h:73
static void encode_escapes(std::string &s, const char *to_escape)
#define TEST_REJECT(cond)
Definition: test_unit.h:1330
static void error(const char *msg)
Definition: mkptypes.cxx:96
static GB_ERROR decode_escapes(std::string &s)
void set_entry(const std::string &entry, const std::string &value)
Definition: ConfigMapping.h:44
char * GBT_join_strings(const CharPtrArray &strings, char separator)
GB_ERROR parseFrom(const std::string &configString)
const char * get_entry(const char *entry) const
Definition: ConfigMapping.h:39
#define TEST_EXPECT_NO_ERROR(call)
Definition: test_unit.h:1118
#define NULp
Definition: cxxforward.h:116
#define TEST_EXPECT_ERROR_CONTAINS(call, part)
Definition: test_unit.h:1114
static void wrapped()
#define TEST_EXPECT_EQUAL(expr, want)
Definition: test_unit.h:1294
GB_write_int const char s
Definition: AW_awar.cxx:154