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  if (configString[equal+1] != '\'') {
80  parse_error = "expected quote \"'\"";
81  break;
82  }
83  size_t start = equal+2;
84  size_t end = configString.find('\'', start);
85  while (end != string::npos) {
86  if (configString[end-1] != '\\') break; // unescaped quote (=end of value)
87  if (configString[end-2] == '\\') { // multiple escapes -> count
88  int escCount = 2;
89  size_t bwd = end-3;
90  while (bwd>=start && configString[bwd] == '\\') {
91  escCount++;
92  bwd--;
93  }
94  if ((escCount%2) == 0) break; // even number of escapes => unescaped quote (=end of value)
95  // otherwise the quote belongs to the value
96  }
97  end = configString.find('\'', end+1);
98  }
99  if (end == string::npos) {
100  parse_error = "could not find matching quote \"'\"";
101  break;
102  }
103 
104  string config_name = configString.substr(pos, equal-pos);
105  string value = configString.substr(start, end-start);
106 
107  parse_error = decode_escapes(value);
108  if (!parse_error) {
109  set_entry(config_name, value);
110  }
111 
112  pos = end+2; // skip ';'
113  }
114  return parse_error;
115 }
116 
117 // --------------------------------------------------------------------------------
118 
119 #ifdef UNIT_TESTS
120 #ifndef TEST_UNIT_H
121 #include <test_unit.h>
122 #include <arb_defs.h>
123 #endif
124 
125 #define TEST_ESCAPE_ENCODING(plain,expected) do{ \
126  string encoded = plain; \
127  ConfigMapping::encode_escapes(encoded, "'"); \
128  TEST_EXPECT_EQUAL(encoded, expected); \
129  string decoded = encoded; \
130  TEST_EXPECT_NO_ERROR(ConfigMapping::decode_escapes(decoded)); \
131  TEST_EXPECT_EQUAL(decoded, plain); \
132  }while(0)
133 
134 void TEST_configValueEscaping() {
135  TEST_ESCAPE_ENCODING("hello", "hello"); // plain text
136 
137  TEST_ESCAPE_ENCODING("'hello'", "\\'hello\\'"); // quoted text
138  TEST_ESCAPE_ENCODING("\"hello\"", "\"hello\""); // double-quoted text
139 
140  // special characters (LF, CR + TAB shall not be saved to config, esp. if saving to file via AWT_config_manager)
141  TEST_ESCAPE_ENCODING("LINE\nNEXT", "LINE\\nNEXT");
142  TEST_ESCAPE_ENCODING("1\t2\r3", "1\\t2\\r3");
143 
144  // simple escape-handling
145  TEST_ESCAPE_ENCODING("hel\\lo", "hel\\\\lo");
146  TEST_ESCAPE_ENCODING("\\hello", "\\\\hello");
147  TEST_ESCAPE_ENCODING("hello\\", "hello\\\\");
148 
149  TEST_ESCAPE_ENCODING("hello\\'", "hello\\\\\\'");
150  TEST_ESCAPE_ENCODING("\\'hello\\'", "\\\\\\'hello\\\\\\'");
151 
152  string invalidEncoding = "xyz\\";
153  TEST_EXPECT_ERROR_CONTAINS(ConfigMapping::decode_escapes(invalidEncoding), "Trailing \\ in 'xyz\\'");
154 }
155 
156 struct TestedConfig {
157  const char *configString;
158  int entryCount;
159  const char *entryList;
160 };
161 struct ReinterpretedConfig {
162  const char *configString;
163  const char *reinterpreted;
164 };
165 struct FailingConfig {
166  const char *configString;
167  GB_ERROR error;
168 };
169 
170 static TestedConfig testedCfg[] = {
171  { "", 0, ""}, // empty config
172  { "tag='value'", 1, "tag" },
173  { "321='1st';abc='2nd'", 2, "321/abc" },
174  { "t-ha t='2';t;hi/s='1'", 2, "t-ha t,t;hi/s" }, // tags can contain anything but '='
175  { "t'ag'='value'", 1, "t'ag'" }, // even quotes are possible in tags
176 };
177 static FailingConfig failedCfg[] = {
178  { "nix=;was='ia'", "expected quote \"'\"" }, // no value
179  { "tag=value", "expected quote \"'\"" }, // unquoted value
180  { "hasto='match", "could not find matching quote" }, // unmatched quote
181 };
182 static ReinterpretedConfig reinterpretCfg[] = {
183  { "laksjd", "" }, // is reinterpreted as "" (empty config)
184  { "this='1';that='2'", "that='2';this='1'" }, // sorted by tagname
185 };
186 
187 inline bool anyStringContains(const CharPtrArray& strings, char c) {
188  for (int i = 0; strings[i]; ++i) {
189  if (strchr(strings[i], c)) {
190  return true;
191  }
192  }
193  return false;
194 }
195 static char autodetectSeparator(const CharPtrArray& strings) {
196  const char *sep = "/;,:|#*";
197  for (int i = 0; sep[i]; ++i) {
198  if (!anyStringContains(strings, sep[i])) {
199  return sep[i];
200  }
201  }
202  TEST_REJECT(true); // add more sep
203  return 0;
204 }
205 
206 void TEST_ConfigMapping() {
207  for (size_t c = 0; c<ARRAY_ELEMS(testedCfg); ++c) {
208  const TestedConfig& CFG = testedCfg[c];
209  TEST_ANNOTATE(CFG.configString);
210 
211  ConfigMapping config;
212  TEST_EXPECT_NO_ERROR(config.parseFrom(CFG.configString));
213 
214  // convert to string + compare with loaded config string:
215  TEST_EXPECT_EQUAL(config.config_string(), CFG.configString);
216 
217  // test entries:
218  ConstStrArray entries;
219  config.get_entries(entries);
220  TEST_EXPECT_EQUAL(entries.size(), CFG.entryCount);
221 
222  TEST_EXPECT_EQUAL_STRINGCOPY__NOERROREXPORTED(GBT_join_strings(entries, autodetectSeparator(entries)), CFG.entryList);
223  }
224 
225  // test nested quoting:
226  {
227  TEST_ANNOTATE(NULp);
228  const char *stored = "tag='val';'qtag'='\\'qval\\''";
229  ConfigMapping config;
230  TEST_EXPECT_NO_ERROR(config.parseFrom(stored));
231 
232  TEST_EXPECT_EQUAL(config.get_entry("tag"), "val");
233  TEST_EXPECT_EQUAL(config.get_entry("'qtag'"), "'qval'");
234 
235  TEST_EXPECT(config.has_entry("'qtag'"));
236  TEST_REJECT(config.has_entry("qtag"));
237 
238  const char *storedAsValue = "stored='tag=\\'val\\';\\'qtag\\'=\\'\\\\\\'qval\\\\\\'\\''";
240  wrapped.set_entry("stored", stored);
241 
242  TEST_EXPECT_EQUAL(wrapped.config_string(), storedAsValue);
243 
244  ConfigMapping unwrapped;
245  TEST_EXPECT_NO_ERROR(unwrapped.parseFrom(storedAsValue));
246  TEST_EXPECT_EQUAL(unwrapped.get_entry("stored"), stored);
247 
248  // test delete entry
249  unwrapped.delete_entry("stored");
250  TEST_EXPECT_EQUAL(unwrapped.config_string(), "");
251 
252  config.delete_entry("'qtag'");
253  TEST_EXPECT_EQUAL(config.config_string(), "tag='val'");
254 
255  config.set_entry("tag", "lav"); // test overwriting a value
256  TEST_EXPECT_EQUAL(config.config_string(), "tag='lav'");
257 
258  const char *SLASHED = "slashed\\";
259  config.set_entry("key", SLASHED);
260  TEST_EXPECT_EQUAL(config.config_string(), "key='slashed\\\\';tag='lav'");
261 
262  {
263  ConfigMapping slashed;
264  string cfgStr = config.config_string();
265 
266  TEST_EXPECT_NO_ERROR(slashed.parseFrom(cfgStr));
267  TEST_EXPECT_EQUAL(slashed.get_entry("key"), SLASHED);
268  TEST_EXPECT_EQUAL(slashed.config_string(), cfgStr);
269  }
270  }
271 
272  // test reinterpretation of configs:
273  for (size_t c = 0; c<ARRAY_ELEMS(reinterpretCfg); ++c) {
274  const ReinterpretedConfig& CFG = reinterpretCfg[c];
275  TEST_ANNOTATE(CFG.configString);
276  ConfigMapping config;
277  TEST_EXPECT_NO_ERROR(config.parseFrom(CFG.configString));
278 
279  // convert to string + compare with expected reinterpretation:
280  TEST_EXPECT_EQUAL(config.config_string(), CFG.reinterpreted);
281  }
282 
283  // test error-config:
284  for (size_t c = 0; c<ARRAY_ELEMS(failedCfg); ++c) {
285  const FailingConfig& CFG = failedCfg[c];
286  TEST_ANNOTATE(CFG.configString);
287  ConfigMapping config;
288  TEST_EXPECT_ERROR_CONTAINS(config.parseFrom(CFG.configString), CFG.error);
289  }
290 }
291 
292 #endif // UNIT_TESTS
293 
294 // --------------------------------------------------------------------------------
295 
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:204
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:1313
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:1315
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:1107
#define NULp
Definition: cxxforward.h:97
#define TEST_EXPECT_ERROR_CONTAINS(call, part)
Definition: test_unit.h:1103
static void wrapped()
#define TEST_EXPECT_EQUAL(expr, want)
Definition: test_unit.h:1283
GB_write_int const char s
Definition: AW_awar.cxx:156