XML configs implemented
[public/netxms.git] / src / libnetxms / config.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** NetXMS Foundation Library
4 ** Copyright (C) 2003-2009 Victor Kirhenshtein
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, write to the Free Software
18 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 **
20 ** File: config.cpp
21 **
22 **/
23
24 #include "libnetxms.h"
25 #include <nxconfig.h>
26 #include <expat.h>
27
28
29 //
30 // Constructor for config entry
31 //
32
33 ConfigEntry::ConfigEntry(const TCHAR *name, ConfigEntry *parent, const TCHAR *file, int line)
34 {
35 m_name = _tcsdup(CHECK_NULL(name));
36 m_childs = NULL;
37 m_next = NULL;
38 if (parent != NULL)
39 parent->addEntry(this);
40 m_valueCount = 0;
41 m_values = NULL;
42 m_file = _tcsdup(CHECK_NULL(file));
43 m_line = line;
44 }
45
46
47 //
48 // Destructor for config entry
49 //
50
51 ConfigEntry::~ConfigEntry()
52 {
53 ConfigEntry *entry, *next;
54
55 for(entry = m_childs; entry != NULL; entry = next)
56 {
57 next = entry->getNext();
58 delete entry;
59 }
60 safe_free(m_name);
61
62 for(int i = 0; i < m_valueCount; i++)
63 safe_free(m_values[i]);
64 safe_free(m_values);
65 }
66
67
68 //
69 // Find entry by name
70 //
71
72 ConfigEntry *ConfigEntry::findEntry(const TCHAR *name)
73 {
74 ConfigEntry *e;
75
76 for(e = m_childs; e != NULL; e = e->getNext())
77 if (!_tcsicmp(e->getName(), name))
78 return e;
79 return NULL;
80 }
81
82
83 //
84 // Get value
85 //
86
87 const TCHAR *ConfigEntry::getValue(int index)
88 {
89 if ((index < 0) || (index >= m_valueCount))
90 return NULL;
91 return m_values[index];
92 }
93
94
95 //
96 // Set value
97 //
98
99 void ConfigEntry::setValue(const TCHAR *value)
100 {
101 for(int i = 0; i < m_valueCount; i++)
102 safe_free(m_values[i]);
103 m_valueCount = 1;
104 m_values = (TCHAR **)realloc(m_values, sizeof(TCHAR *));
105 m_values[0] = _tcsdup(value);
106 }
107
108
109 //
110 // Add value
111 //
112
113 void ConfigEntry::addValue(const TCHAR *value)
114 {
115 m_values = (TCHAR **)realloc(m_values, sizeof(TCHAR *) * (m_valueCount + 1));
116 m_values[m_valueCount] = _tcsdup(value);
117 m_valueCount++;
118 }
119
120
121 //
122 // Get summary length of all values as if they was concatenated with separator character
123 //
124
125 int ConfigEntry::getConcatenatedValuesLength()
126 {
127 int i, len;
128
129 if (m_valueCount < 1)
130 return 0;
131
132 for(i = 0, len = 0; i < m_valueCount; i++)
133 len += _tcslen(m_values[i]);
134 return len + (m_valueCount - 1);
135 }
136
137
138 //
139 // Constructor for config
140 //
141
142 Config::Config()
143 {
144 m_root = new ConfigEntry(_T("[root]"), NULL, NULL, 0);
145 m_errorCount = 0;
146 }
147
148
149 //
150 // Destructor
151 //
152
153 Config::~Config()
154 {
155 delete m_root;
156 }
157
158
159 //
160 // Default error handler
161 //
162
163 void Config::onError(const TCHAR *errorMessage)
164 {
165 _ftprintf(stderr, _T("%s\n"), errorMessage);
166 }
167
168
169 //
170 // Report error
171 //
172
173 void Config::error(const TCHAR *format, ...)
174 {
175 va_list args;
176 TCHAR buffer[4096];
177
178 m_errorCount++;
179 va_start(args, format);
180 _vsntprintf(buffer, 4096, format, args);
181 va_end(args);
182 onError(buffer);
183 }
184
185
186 //
187 // Bind parameters to variables: simulation of old NxLoadConfig() API
188 //
189
190 bool Config::bindParameters(const TCHAR *section, NX_CFG_TEMPLATE *cfgTemplate)
191 {
192 TCHAR name[MAX_PATH], *curr, *eptr;
193 int i, j, pos;
194 ConfigEntry *entry;
195
196 name[0] = _T('/');
197 nx_strncpy(&name[1], section, MAX_PATH - 2);
198 _tcscat(name, _T("/"));
199 pos = _tcslen(name);
200
201 for(i = 0; cfgTemplate[i].iType != CT_END_OF_LIST; i++)
202 {
203 nx_strncpy(&name[pos], cfgTemplate[i].szToken, MAX_PATH - pos);
204 entry = getEntry(name);
205 if (entry != NULL)
206 {
207 const TCHAR *value = CHECK_NULL(entry->getValue());
208 switch(cfgTemplate[i].iType)
209 {
210 case CT_LONG:
211 *((LONG *)cfgTemplate[i].pBuffer) = _tcstol(value, &eptr, 0);
212 if (*eptr != 0)
213 {
214 error(_T("Invalid number '%s' in configuration file %s at line %d\n"), value, entry->getFile(), entry->getLine());
215 }
216 break;
217 case CT_WORD:
218 *((WORD *)cfgTemplate[i].pBuffer) = (WORD)_tcstoul(value, &eptr, 0);
219 if (*eptr != 0)
220 {
221 error(_T("Invalid number '%s' in configuration file %s at line %d\n"), value, entry->getFile(), entry->getLine());
222 }
223 break;
224 case CT_BOOLEAN:
225 if (!_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) ||
226 !_tcsicmp(value, _T("on")) || !_tcsicmp(value, _T("1")))
227 {
228 *((DWORD *)cfgTemplate[i].pBuffer) |= cfgTemplate[i].dwBufferSize;
229 }
230 else
231 {
232 *((DWORD *)cfgTemplate[i].pBuffer) &= ~(cfgTemplate[i].dwBufferSize);
233 }
234 break;
235 case CT_STRING:
236 nx_strncpy((TCHAR *)cfgTemplate[i].pBuffer, value, cfgTemplate[i].dwBufferSize);
237 break;
238 case CT_STRING_LIST:
239 *((TCHAR **)cfgTemplate[i].pBuffer) = (TCHAR *)malloc(sizeof(TCHAR) * (entry->getConcatenatedValuesLength() + 1));
240 for(j = 0, curr = *((TCHAR **)cfgTemplate[i].pBuffer); j < entry->getValueCount(); j++)
241 {
242 _tcscpy(curr, entry->getValue(j));
243 curr += _tcslen(curr);
244 *curr = cfgTemplate[i].cSeparator;
245 curr++;
246 }
247 if (j > 0)
248 curr--;
249 *curr = 0;
250 break;
251 case CT_IGNORE:
252 break;
253 default:
254 break;
255 }
256 }
257 }
258
259 return m_errorCount == 0;
260 }
261
262
263 //
264 // Get value
265 //
266
267 const TCHAR *Config::getValue(const TCHAR *path)
268 {
269 ConfigEntry *entry = getEntry(path);
270 return (entry != NULL) ? entry->getValue() : NULL;
271 }
272
273
274 //
275 // Get entry
276 //
277
278 ConfigEntry *Config::getEntry(const TCHAR *path)
279 {
280 const TCHAR *curr, *end;
281 TCHAR name[256];
282 ConfigEntry *entry = m_root;
283
284 if ((path == NULL) || (*path != _T('/')))
285 return NULL;
286
287 if (!_tcscmp(path, _T("/")))
288 return m_root;
289
290 curr = path + 1;
291 while(entry != NULL)
292 {
293 end = _tcschr(curr, _T('/'));
294 if (end != NULL)
295 {
296 int len = min((int)(end - curr), 255);
297 _tcsncpy(name, curr, len);
298 name[len] = 0;
299 entry = entry->findEntry(name);
300 curr = end + 1;
301 }
302 else
303 {
304 return entry->findEntry(curr);
305 }
306 }
307 return NULL;
308 }
309
310
311 //
312 // Load INI-style config
313 //
314
315 bool Config::loadIniConfig(const TCHAR *file, const TCHAR *defaultIniSection)
316 {
317 FILE *cfg;
318 TCHAR buffer[4096], *ptr;
319 ConfigEntry *currentSection;
320 int sourceLine = 0;
321
322 cfg = _tfopen(file, _T("r"));
323 if (cfg == NULL)
324 {
325 error(_T("Cannot open file %s"), file);
326 return false;
327 }
328
329 currentSection = m_root->findEntry(defaultIniSection);
330 if (currentSection == NULL)
331 {
332 currentSection = new ConfigEntry(defaultIniSection, m_root, file, 0);
333 }
334
335 while(!feof(cfg))
336 {
337 // Read line from file
338 buffer[0] = 0;
339 _fgetts(buffer, 4095, cfg);
340 sourceLine++;
341 ptr = _tcschr(buffer, _T('\n'));
342 if (ptr != NULL)
343 *ptr = 0;
344 ptr = _tcschr(buffer, _T('#'));
345 if (ptr != NULL)
346 *ptr = 0;
347
348 StrStrip(buffer);
349 if (buffer[0] == 0)
350 continue;
351
352 // Check if it's a section name
353 if ((buffer[0] == _T('*')) || (buffer[0] == _T('[')))
354 {
355 if (buffer[0] == _T('['))
356 {
357 TCHAR *end = _tcschr(buffer, _T(']'));
358 if (end != NULL)
359 *end = 0;
360 }
361 currentSection = m_root->findEntry(&buffer[1]);
362 if (currentSection == NULL)
363 {
364 currentSection = new ConfigEntry(&buffer[1], m_root, file, sourceLine);
365 }
366 }
367 else
368 {
369 // Divide on two parts at = sign
370 ptr = _tcschr(buffer, _T('='));
371 if (ptr == NULL)
372 {
373 error(_T("Syntax error in configuration file %s at line %d"), file, sourceLine);
374 continue;
375 }
376 *ptr = 0;
377 ptr++;
378 StrStrip(buffer);
379 StrStrip(ptr);
380
381 ConfigEntry *entry = currentSection->findEntry(buffer);
382 if (entry == NULL)
383 entry = new ConfigEntry(buffer, currentSection, file, sourceLine);
384 entry->addValue(ptr);
385 }
386 }
387 fclose(cfg);
388 return true;
389 }
390
391
392 //
393 // Load config from XML file
394 //
395
396 #define MAX_STACK_DEPTH 256
397
398 typedef struct
399 {
400 XML_Parser parser;
401 Config *config;
402 const TCHAR *file;
403 int level;
404 ConfigEntry *stack[MAX_STACK_DEPTH];
405 String charData[MAX_STACK_DEPTH];
406 } XML_PARSER_STATE;
407
408 static void StartElement(void *userData, const char *name, const char **attrs)
409 {
410 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
411
412 if (ps->level == 0)
413 {
414 if (!stricmp(name, "config"))
415 {
416 ps->stack[ps->level++] = ps->config->getEntry(_T("/"));
417 }
418 else
419 {
420 ps->level = -1;
421 }
422 }
423 else if (ps->level > 0)
424 {
425 if (ps->level < MAX_STACK_DEPTH)
426 {
427 #ifdef UNICODE
428 WCHAR wname[256];
429
430 MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, 256);
431 wname[255] = 0;
432 ps->stack[ps->level] = ps->stack[ps->level - 1]->findEntry(wname);
433 if (ps->stack[ps->level] == NULL)
434 ps->stack[ps->level] = new ConfigEntry(wname, ps->stack[ps->level - 1], ps->file, XML_GetCurrentLineNumber(ps->parser));
435 #else
436 ps->stack[ps->level] = ps->stack[ps->level - 1]->findEntry(name);
437 if (ps->stack[ps->level] == NULL)
438 ps->stack[ps->level] = new ConfigEntry(name, ps->stack[ps->level - 1], ps->file, XML_GetCurrentLineNumber(ps->parser));
439 #endif
440 ps->charData[ps->level] = _T("");
441 ps->level++;
442 }
443 else
444 {
445 ps->level++;
446 }
447 }
448 }
449
450 static void EndElement(void *userData, const char *name)
451 {
452 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
453
454 if (ps->level > MAX_STACK_DEPTH)
455 {
456 ps->level--;
457 }
458 else if (ps->level > 0)
459 {
460 ps->level--;
461 ps->stack[ps->level]->addValue(ps->charData[ps->level]);
462 }
463 }
464
465 static void CharData(void *userData, const XML_Char *s, int len)
466 {
467 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
468
469 if ((ps->level > 0) && (ps->level <= MAX_STACK_DEPTH))
470 ps->charData[ps->level - 1].AddMultiByteString(s, len, CP_UTF8);
471 }
472
473 bool Config::loadXmlConfig(const TCHAR *file)
474 {
475 BYTE *xml;
476 DWORD size;
477 bool success;
478
479 xml = LoadFile(file, &size);
480 if (xml != NULL)
481 {
482 XML_PARSER_STATE state;
483
484 XML_Parser parser = XML_ParserCreate(NULL);
485 XML_SetUserData(parser, &state);
486 XML_SetElementHandler(parser, StartElement, EndElement);
487 XML_SetCharacterDataHandler(parser, CharData);
488
489 state.config = this;
490 state.level = 0;
491 state.parser = parser;
492 state.file = file;
493
494 success = (XML_Parse(parser, (char *)xml, size, TRUE) != XML_STATUS_ERROR);
495 if (!success)
496 {
497 error(_T("%s at line %d"), XML_ErrorString(XML_GetErrorCode(parser)),
498 XML_GetCurrentLineNumber(parser));
499 }
500 }
501 else
502 {
503 success = false;
504 }
505
506 return success;
507 }
508
509
510 //
511 // Load config file with format auto detection
512 //
513
514 bool Config::loadConfig(const TCHAR *file, const TCHAR *defaultIniSection)
515 {
516 FILE *f;
517 int ch;
518
519 f = _tfopen(file, _T("r"));
520 if (f == NULL)
521 {
522 error(_T("Cannot open file %s"), file);
523 return false;
524 }
525
526 // Skip all space/newline characters
527 do
528 {
529 ch = fgetc(f);
530 } while(isspace(ch));
531
532 fclose(f);
533
534 // If first non-space character is < assume XML format, otherwise assume INI format
535 return (ch == '<') ? loadXmlConfig(file) : loadIniConfig(file, defaultIniSection);
536 }