c57290c7ea52dd79365a848bd05dad8d1f9ee301
[public/netxms.git] / src / libnetxms / config.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** NetXMS Foundation Library
4 ** Copyright (C) 2003-2010 Victor Kirhenshtein
5 **
6 ** This program is free software; you can redistribute it and/or modify
7 ** it under the terms of the GNU Lesser General Public License as published
8 ** by the Free Software Foundation; either version 3 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 Lesser 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, int id)
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 m_id = id;
45 }
46
47
48 //
49 // Destructor for config entry
50 //
51
52 ConfigEntry::~ConfigEntry()
53 {
54 ConfigEntry *entry, *next;
55
56 for(entry = m_childs; entry != NULL; entry = next)
57 {
58 next = entry->getNext();
59 delete entry;
60 }
61 safe_free(m_name);
62 safe_free(m_file);
63
64 for(int i = 0; i < m_valueCount; i++)
65 safe_free(m_values[i]);
66 safe_free(m_values);
67 }
68
69
70 //
71 // Set entry's name
72 //
73
74 void ConfigEntry::setName(const TCHAR *name)
75 {
76 safe_free(m_name);
77 m_name = _tcsdup(CHECK_NULL(name));
78 }
79
80
81 //
82 // Find entry by name
83 //
84
85 ConfigEntry *ConfigEntry::findEntry(const TCHAR *name)
86 {
87 ConfigEntry *e;
88
89 for(e = m_childs; e != NULL; e = e->getNext())
90 if (!_tcsicmp(e->getName(), name))
91 return e;
92 return NULL;
93 }
94
95
96 //
97 // Get all subentries with names matched to mask
98 //
99
100 ConfigEntryList *ConfigEntry::getSubEntries(const TCHAR *mask)
101 {
102 ConfigEntry *e, **list = NULL;
103 int count = 0, allocated = 0;
104
105 for(e = m_childs; e != NULL; e = e->getNext())
106 if (MatchString(mask, e->getName(), FALSE))
107 {
108 if (count == allocated)
109 {
110 allocated += 10;
111 list = (ConfigEntry **)realloc(list, sizeof(ConfigEntry *) * allocated);
112 }
113 list[count++] = e;
114 }
115 return new ConfigEntryList(list, count);
116 }
117
118
119 //
120 // Get all subentries with names matched to mask ordered by id
121 //
122
123 ConfigEntryList *ConfigEntry::getOrderedSubEntries(const TCHAR *mask)
124 {
125 ConfigEntryList *list = getSubEntries(mask);
126 list->sortById();
127 return list;
128 }
129
130
131 //
132 // Get value
133 //
134
135 const TCHAR *ConfigEntry::getValue(int index)
136 {
137 if ((index < 0) || (index >= m_valueCount))
138 return NULL;
139 return m_values[index];
140 }
141
142 LONG ConfigEntry::getValueInt(int index, LONG defaultValue)
143 {
144 const TCHAR *value = getValue(index);
145 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
146 }
147
148 DWORD ConfigEntry::getValueUInt(int index, DWORD defaultValue)
149 {
150 const TCHAR *value = getValue(index);
151 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
152 }
153
154 INT64 ConfigEntry::getValueInt64(int index, INT64 defaultValue)
155 {
156 const TCHAR *value = getValue(index);
157 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
158 }
159
160 QWORD ConfigEntry::getValueUInt64(int index, QWORD defaultValue)
161 {
162 const TCHAR *value = getValue(index);
163 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
164 }
165
166 bool ConfigEntry::getValueBoolean(int index, bool defaultValue)
167 {
168 const TCHAR *value = getValue(index);
169 if (value != NULL)
170 {
171 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) ||
172 !_tcsicmp(value, _T("on")) || (_tcstol(value, NULL, 0) != 0);
173 }
174 else
175 {
176 return defaultValue;
177 }
178 }
179
180
181 //
182 // Set value
183 //
184
185 void ConfigEntry::setValue(const TCHAR *value)
186 {
187 for(int i = 0; i < m_valueCount; i++)
188 safe_free(m_values[i]);
189 m_valueCount = 1;
190 m_values = (TCHAR **)realloc(m_values, sizeof(TCHAR *));
191 m_values[0] = _tcsdup(value);
192 }
193
194
195 //
196 // Add value
197 //
198
199 void ConfigEntry::addValue(const TCHAR *value)
200 {
201 m_values = (TCHAR **)realloc(m_values, sizeof(TCHAR *) * (m_valueCount + 1));
202 m_values[m_valueCount] = _tcsdup(value);
203 m_valueCount++;
204 }
205
206
207 //
208 // Get summary length of all values as if they was concatenated with separator character
209 //
210
211 int ConfigEntry::getConcatenatedValuesLength()
212 {
213 int i, len;
214
215 if (m_valueCount < 1)
216 return 0;
217
218 for(i = 0, len = 0; i < m_valueCount; i++)
219 len += (int)_tcslen(m_values[i]);
220 return len + m_valueCount;
221 }
222
223
224 //
225 // Get value for given subentry
226 //
227
228 const TCHAR *ConfigEntry::getSubEntryValue(const TCHAR *name, int index, const TCHAR *defaultValue)
229 {
230 ConfigEntry *e = findEntry(name);
231 if (e == NULL)
232 return defaultValue;
233 const TCHAR *value = e->getValue(index);
234 return (value != NULL) ? value : defaultValue;
235 }
236
237 LONG ConfigEntry::getSubEntryValueInt(const TCHAR *name, int index, LONG defaultValue)
238 {
239 const TCHAR *value = getSubEntryValue(name, index);
240 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
241 }
242
243 DWORD ConfigEntry::getSubEntryValueUInt(const TCHAR *name, int index, DWORD defaultValue)
244 {
245 const TCHAR *value = getSubEntryValue(name, index);
246 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
247 }
248
249 INT64 ConfigEntry::getSubEntryValueInt64(const TCHAR *name, int index, INT64 defaultValue)
250 {
251 const TCHAR *value = getSubEntryValue(name, index);
252 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
253 }
254
255 QWORD ConfigEntry::getSubEntryValueUInt64(const TCHAR *name, int index, QWORD defaultValue)
256 {
257 const TCHAR *value = getSubEntryValue(name, index);
258 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
259 }
260
261 bool ConfigEntry::getSubEntryValueBoolean(const TCHAR *name, int index, bool defaultValue)
262 {
263 const TCHAR *value = getSubEntryValue(name, index);
264 if (value != NULL)
265 {
266 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) ||
267 !_tcsicmp(value, _T("on")) || (_tcstol(value, NULL, 0) != 0);
268 }
269 else
270 {
271 return defaultValue;
272 }
273 }
274
275
276 //
277 // Print config entry
278 //
279
280 void ConfigEntry::print(FILE *file, int level)
281 {
282 _tprintf(_T("%*s%s\n"), level * 4, _T(""), m_name);
283 for(ConfigEntry *e = m_childs; e != NULL; e = e->getNext())
284 e->print(file, level + 1);
285
286 for(int i = 0, len = 0; i < m_valueCount; i++)
287 _tprintf(_T("%*svalue: %s\n"), level * 4 + 2, _T(""), m_values[i]);
288 }
289
290
291 //
292 // Create XML element(s) from config entry
293 //
294
295 void ConfigEntry::createXml(String &xml, int level)
296 {
297 TCHAR *name = _tcsdup(m_name);
298 TCHAR *ptr = _tcschr(name, _T('#'));
299 if (ptr != NULL)
300 *ptr = 0;
301
302 if (m_id == 0)
303 xml.addFormattedString(_T("%*s<%s>"), level * 4, _T(""), name);
304 else
305 xml.addFormattedString(_T("%*s<%s id=\"%d\">"), level * 4, _T(""), name, m_id);
306
307 if (m_childs != NULL)
308 {
309 xml += _T("\n");
310 for(ConfigEntry *e = m_childs; e != NULL; e = e->getNext())
311 e->createXml(xml, level + 1);
312 xml.addFormattedString(_T("%*s"), level * 4, _T(""));
313 }
314
315 if (m_valueCount > 0)
316 xml.addDynamicString(EscapeStringForXML(m_values[0], -1));
317 xml.addFormattedString(_T("</%s>\n"), name);
318
319 for(int i = 1, len = 0; i < m_valueCount; i++)
320 {
321 if (m_id == 0)
322 xml.addFormattedString(_T("%*s<%s>"), level * 4, _T(""), name);
323 else
324 xml.addFormattedString(_T("%*s<%s id=\"%d\">"), level * 4, _T(""), name, m_id);
325 xml.addDynamicString(EscapeStringForXML(m_values[i], -1));
326 xml.addFormattedString(_T("</%s>\n"), name);
327 }
328
329 free(name);
330 }
331
332
333 //
334 // Sort entry list
335 //
336
337 static int CompareById(const void *p1, const void *p2)
338 {
339 ConfigEntry *e1 = *((ConfigEntry **)p1);
340 ConfigEntry *e2 = *((ConfigEntry **)p2);
341 return e1->getId() - e2->getId();
342 }
343
344 void ConfigEntryList::sortById()
345 {
346 qsort(m_list, m_size, sizeof(ConfigEntry *), CompareById);
347 }
348
349
350 //
351 // Constructor for config
352 //
353
354 Config::Config()
355 {
356 m_root = new ConfigEntry(_T("[root]"), NULL, NULL, 0, 0);
357 m_errorCount = 0;
358 }
359
360
361 //
362 // Destructor
363 //
364
365 Config::~Config()
366 {
367 delete m_root;
368 }
369
370
371 //
372 // Default error handler
373 //
374
375 void Config::onError(const TCHAR *errorMessage)
376 {
377 _ftprintf(stderr, _T("%s\n"), errorMessage);
378 }
379
380
381 //
382 // Report error
383 //
384
385 void Config::error(const TCHAR *format, ...)
386 {
387 va_list args;
388 TCHAR buffer[4096];
389
390 m_errorCount++;
391 va_start(args, format);
392 _vsntprintf(buffer, 4096, format, args);
393 va_end(args);
394 onError(buffer);
395 }
396
397
398 //
399 // Simulation of old NxLoadConfig() API
400 //
401
402 bool Config::parseTemplate(const TCHAR *section, NX_CFG_TEMPLATE *cfgTemplate)
403 {
404 TCHAR name[MAX_PATH], *curr, *eptr;
405 int i, j, pos, initialErrorCount = m_errorCount;
406 ConfigEntry *entry;
407
408 name[0] = _T('/');
409 nx_strncpy(&name[1], section, MAX_PATH - 2);
410 _tcscat(name, _T("/"));
411 pos = (int)_tcslen(name);
412
413 for(i = 0; cfgTemplate[i].iType != CT_END_OF_LIST; i++)
414 {
415 nx_strncpy(&name[pos], cfgTemplate[i].szToken, MAX_PATH - pos);
416 entry = getEntry(name);
417 if (entry != NULL)
418 {
419 const TCHAR *value = CHECK_NULL(entry->getValue(entry->getValueCount() - 1));
420 switch(cfgTemplate[i].iType)
421 {
422 case CT_LONG:
423 *((LONG *)cfgTemplate[i].pBuffer) = _tcstol(value, &eptr, 0);
424 if (*eptr != 0)
425 {
426 error(_T("Invalid number '%s' in configuration file %s at line %d\n"), value, entry->getFile(), entry->getLine());
427 }
428 break;
429 case CT_WORD:
430 *((WORD *)cfgTemplate[i].pBuffer) = (WORD)_tcstoul(value, &eptr, 0);
431 if (*eptr != 0)
432 {
433 error(_T("Invalid number '%s' in configuration file %s at line %d\n"), value, entry->getFile(), entry->getLine());
434 }
435 break;
436 case CT_BOOLEAN:
437 if (!_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) ||
438 !_tcsicmp(value, _T("on")) || !_tcsicmp(value, _T("1")))
439 {
440 *((DWORD *)cfgTemplate[i].pBuffer) |= cfgTemplate[i].dwBufferSize;
441 }
442 else
443 {
444 *((DWORD *)cfgTemplate[i].pBuffer) &= ~(cfgTemplate[i].dwBufferSize);
445 }
446 break;
447 case CT_STRING:
448 nx_strncpy((TCHAR *)cfgTemplate[i].pBuffer, value, cfgTemplate[i].dwBufferSize);
449 break;
450 case CT_STRING_LIST:
451 *((TCHAR **)cfgTemplate[i].pBuffer) = (TCHAR *)malloc(sizeof(TCHAR) * (entry->getConcatenatedValuesLength() + 1));
452 for(j = 0, curr = *((TCHAR **)cfgTemplate[i].pBuffer); j < entry->getValueCount(); j++)
453 {
454 _tcscpy(curr, entry->getValue(j));
455 curr += _tcslen(curr);
456 *curr = cfgTemplate[i].cSeparator;
457 curr++;
458 }
459 *curr = 0;
460 break;
461 case CT_IGNORE:
462 break;
463 default:
464 break;
465 }
466 }
467 }
468
469 return (m_errorCount - initialErrorCount) == 0;
470 }
471
472
473 //
474 // Get value
475 //
476
477 const TCHAR *Config::getValue(const TCHAR *path, const TCHAR *defaultValue)
478 {
479 const TCHAR *value;
480 ConfigEntry *entry = getEntry(path);
481 if (entry != NULL)
482 {
483 value = entry->getValue();
484 if (value == NULL)
485 value = defaultValue;
486 }
487 else
488 {
489 value = defaultValue;
490 }
491 return value;
492 }
493
494 LONG Config::getValueInt(const TCHAR *path, LONG defaultValue)
495 {
496 const TCHAR *value = getValue(path);
497 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
498 }
499
500 DWORD Config::getValueUInt(const TCHAR *path, DWORD defaultValue)
501 {
502 const TCHAR *value = getValue(path);
503 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
504 }
505
506 INT64 Config::getValueInt64(const TCHAR *path, INT64 defaultValue)
507 {
508 const TCHAR *value = getValue(path);
509 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
510 }
511
512 QWORD Config::getValueUInt64(const TCHAR *path, QWORD defaultValue)
513 {
514 const TCHAR *value = getValue(path);
515 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
516 }
517
518 bool Config::getValueBoolean(const TCHAR *path, bool defaultValue)
519 {
520 const TCHAR *value = getValue(path);
521 if (value != NULL)
522 {
523 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) ||
524 !_tcsicmp(value, _T("on")) || (_tcstol(value, NULL, 0) != 0);
525 }
526 else
527 {
528 return defaultValue;
529 }
530 }
531
532
533 //
534 // Get subentries
535 //
536
537 ConfigEntryList *Config::getSubEntries(const TCHAR *path, const TCHAR *mask)
538 {
539 ConfigEntry *entry = getEntry(path);
540 return (entry != NULL) ? entry->getSubEntries(mask) : NULL;
541 }
542
543
544 //
545 // Get subentries ordered by id
546 //
547
548 ConfigEntryList *Config::getOrderedSubEntries(const TCHAR *path, const TCHAR *mask)
549 {
550 ConfigEntry *entry = getEntry(path);
551 return (entry != NULL) ? entry->getOrderedSubEntries(mask) : NULL;
552 }
553
554
555 //
556 // Get entry
557 //
558
559 ConfigEntry *Config::getEntry(const TCHAR *path)
560 {
561 const TCHAR *curr, *end;
562 TCHAR name[256];
563 ConfigEntry *entry = m_root;
564
565 if ((path == NULL) || (*path != _T('/')))
566 return NULL;
567
568 if (!_tcscmp(path, _T("/")))
569 return m_root;
570
571 curr = path + 1;
572 while(entry != NULL)
573 {
574 end = _tcschr(curr, _T('/'));
575 if (end != NULL)
576 {
577 int len = min((int)(end - curr), 255);
578 _tcsncpy(name, curr, len);
579 name[len] = 0;
580 entry = entry->findEntry(name);
581 curr = end + 1;
582 }
583 else
584 {
585 return entry->findEntry(curr);
586 }
587 }
588 return NULL;
589 }
590
591
592 //
593 // Create entry if does not exist, or return existing
594 // Will return NULL on error
595 //
596
597 ConfigEntry *Config::createEntry(const TCHAR *path)
598 {
599 const TCHAR *curr, *end;
600 TCHAR name[256];
601 ConfigEntry *entry, *parent;
602
603 if ((path == NULL) || (*path != _T('/')))
604 return NULL;
605
606 if (!_tcscmp(path, _T("/")))
607 return m_root;
608
609 curr = path + 1;
610 parent = m_root;
611 do
612 {
613 end = _tcschr(curr, _T('/'));
614 if (end != NULL)
615 {
616 int len = min((int)(end - curr), 255);
617 _tcsncpy(name, curr, len);
618 name[len] = 0;
619 entry = parent->findEntry(name);
620 curr = end + 1;
621 if (entry == NULL)
622 entry = new ConfigEntry(name, parent, _T("<memory>"), 0, 0);
623 parent = entry;
624 }
625 else
626 {
627 entry = parent->findEntry(curr);
628 if (entry == NULL)
629 entry = new ConfigEntry(curr, parent, _T("<memory>"), 0, 0);
630 }
631 }
632 while(end != NULL);
633 return entry;
634 }
635
636
637 //
638 // Set value
639 // Returns false on error (usually caused by incorrect path)
640 //
641
642 bool Config::setValue(const TCHAR *path, const TCHAR *value)
643 {
644 ConfigEntry *entry = createEntry(path);
645 if (entry == NULL)
646 return false;
647 entry->setValue(value);
648 return true;
649 }
650
651 bool Config::setValue(const TCHAR *path, LONG value)
652 {
653 TCHAR buffer[32];
654 _sntprintf(buffer, 32, _T("%ld"), (long)value);
655 return setValue(path, buffer);
656 }
657
658 bool Config::setValue(const TCHAR *path, DWORD value)
659 {
660 TCHAR buffer[32];
661 _sntprintf(buffer, 32, _T("%lu"), (unsigned long)value);
662 return setValue(path, buffer);
663 }
664
665 bool Config::setValue(const TCHAR *path, INT64 value)
666 {
667 TCHAR buffer[32];
668 _sntprintf(buffer, 32, INT64_FMT, value);
669 return setValue(path, buffer);
670 }
671
672 bool Config::setValue(const TCHAR *path, QWORD value)
673 {
674 TCHAR buffer[32];
675 _sntprintf(buffer, 32, UINT64_FMT, value);
676 return setValue(path, buffer);
677 }
678
679 bool Config::setValue(const TCHAR *path, double value)
680 {
681 TCHAR buffer[32];
682 _sntprintf(buffer, 32, _T("%f"), value);
683 return setValue(path, buffer);
684 }
685
686
687 //
688 // Find comment start in INI style config line
689 // Comment starts with # character, characters within double quotes ignored
690 //
691
692 static TCHAR *FindComment(TCHAR *str)
693 {
694 TCHAR *curr;
695 bool quotes;
696
697 for(curr = str, quotes = false; *curr != 0; curr++)
698 {
699 if (*curr == _T('"'))
700 {
701 quotes = !quotes;
702 }
703 else if ((*curr == _T('#')) && !quotes)
704 {
705 return curr;
706 }
707 }
708 return NULL;
709 }
710
711
712 //
713 // Load INI-style config
714 //
715
716 bool Config::loadIniConfig(const TCHAR *file, const TCHAR *defaultIniSection)
717 {
718 FILE *cfg;
719 TCHAR buffer[4096], *ptr;
720 ConfigEntry *currentSection;
721 int sourceLine = 0;
722
723 cfg = _tfopen(file, _T("r"));
724 if (cfg == NULL)
725 {
726 error(_T("Cannot open file %s"), file);
727 return false;
728 }
729
730 currentSection = m_root->findEntry(defaultIniSection);
731 if (currentSection == NULL)
732 {
733 currentSection = new ConfigEntry(defaultIniSection, m_root, file, 0, 0);
734 }
735
736 while(!feof(cfg))
737 {
738 // Read line from file
739 buffer[0] = 0;
740 if (_fgetts(buffer, 4095, cfg) == NULL)
741 break; // EOF or error
742 sourceLine++;
743 ptr = _tcschr(buffer, _T('\n'));
744 if (ptr != NULL)
745 *ptr = 0;
746 ptr = FindComment(buffer);
747 if (ptr != NULL)
748 *ptr = 0;
749
750 StrStrip(buffer);
751 if (buffer[0] == 0)
752 continue;
753
754 // Check if it's a section name
755 if ((buffer[0] == _T('*')) || (buffer[0] == _T('[')))
756 {
757 if (buffer[0] == _T('['))
758 {
759 TCHAR *end = _tcschr(buffer, _T(']'));
760 if (end != NULL)
761 *end = 0;
762 }
763 currentSection = m_root->findEntry(&buffer[1]);
764 if (currentSection == NULL)
765 {
766 currentSection = new ConfigEntry(&buffer[1], m_root, file, sourceLine, 0);
767 }
768 }
769 else
770 {
771 // Divide on two parts at = sign
772 ptr = _tcschr(buffer, _T('='));
773 if (ptr == NULL)
774 {
775 error(_T("Syntax error in configuration file %s at line %d"), file, sourceLine);
776 continue;
777 }
778 *ptr = 0;
779 ptr++;
780 StrStrip(buffer);
781 StrStrip(ptr);
782
783 ConfigEntry *entry = currentSection->findEntry(buffer);
784 if (entry == NULL)
785 {
786 entry = new ConfigEntry(buffer, currentSection, file, sourceLine, 0);
787 }
788 entry->addValue(ptr);
789 }
790 }
791 fclose(cfg);
792 return true;
793 }
794
795
796 //
797 // Load config from XML file
798 //
799
800 #define MAX_STACK_DEPTH 256
801
802 typedef struct
803 {
804 const char *topLevelTag;
805 XML_Parser parser;
806 Config *config;
807 const TCHAR *file;
808 int level;
809 ConfigEntry *stack[MAX_STACK_DEPTH];
810 String charData[MAX_STACK_DEPTH];
811 bool trimValue[MAX_STACK_DEPTH];
812 } XML_PARSER_STATE;
813
814 static void StartElement(void *userData, const char *name, const char **attrs)
815 {
816 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
817
818 if (ps->level == 0)
819 {
820 if (!stricmp(name, ps->topLevelTag))
821 {
822 ps->stack[ps->level++] = ps->config->getEntry(_T("/"));
823 }
824 else
825 {
826 ps->level = -1;
827 }
828 }
829 else if (ps->level > 0)
830 {
831 if (ps->level < MAX_STACK_DEPTH)
832 {
833 TCHAR entryName[MAX_PATH];
834
835 DWORD id = XMLGetAttrDWORD(attrs, "id", 0);
836 #ifdef UNICODE
837 if (id != 0)
838 {
839 WCHAR wname[MAX_PATH];
840
841 MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, MAX_PATH);
842 wname[MAX_PATH - 1] = 0;
843 _snwprintf(entryName, MAX_PATH, L"%s#%u", wname, (unsigned int)id);
844 }
845 else
846 {
847 MultiByteToWideChar(CP_UTF8, 0, name, -1, entryName, MAX_PATH);
848 entryName[MAX_PATH - 1] = 0;
849 }
850 #else
851 if (id != 0)
852 snprintf(entryName, MAX_PATH, "%s#%u", name, (unsigned int)id);
853 else
854 nx_strncpy(entryName, name, MAX_PATH);
855 #endif
856 ps->stack[ps->level] = ps->stack[ps->level - 1]->findEntry(entryName);
857 if (ps->stack[ps->level] == NULL)
858 ps->stack[ps->level] = new ConfigEntry(entryName, ps->stack[ps->level - 1], ps->file, XML_GetCurrentLineNumber(ps->parser), (int)id);
859 ps->charData[ps->level] = _T("");
860 ps->trimValue[ps->level] = XMLGetAttrBoolean(attrs, "trim", true);
861 ps->level++;
862 }
863 else
864 {
865 ps->level++;
866 }
867 }
868 }
869
870 static void EndElement(void *userData, const char *name)
871 {
872 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
873
874 if (ps->level > MAX_STACK_DEPTH)
875 {
876 ps->level--;
877 }
878 else if (ps->level > 0)
879 {
880 ps->level--;
881 if (ps->trimValue[ps->level])
882 ps->charData[ps->level].trim();
883 ps->stack[ps->level]->addValue(ps->charData[ps->level]);
884 }
885 }
886
887 static void CharData(void *userData, const XML_Char *s, int len)
888 {
889 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
890
891 if ((ps->level > 0) && (ps->level <= MAX_STACK_DEPTH))
892 ps->charData[ps->level - 1].addMultiByteString(s, len, CP_UTF8);
893 }
894
895 bool Config::loadXmlConfigFromMemory(const char *xml, int xmlSize, const TCHAR *name, const char *topLevelTag)
896 {
897 XML_PARSER_STATE state;
898
899 XML_Parser parser = XML_ParserCreate(NULL);
900 XML_SetUserData(parser, &state);
901 XML_SetElementHandler(parser, StartElement, EndElement);
902 XML_SetCharacterDataHandler(parser, CharData);
903
904 state.topLevelTag = (topLevelTag != NULL) ? topLevelTag : "config";
905 state.config = this;
906 state.level = 0;
907 state.parser = parser;
908 state.file = (name != NULL) ? name : _T("<mem>");
909
910 bool success = (XML_Parse(parser, xml, xmlSize, TRUE) != XML_STATUS_ERROR);
911 if (!success)
912 {
913 error(_T("%s at line %d"), XML_ErrorString(XML_GetErrorCode(parser)),
914 XML_GetCurrentLineNumber(parser));
915 }
916 return success;
917 }
918
919 bool Config::loadXmlConfig(const TCHAR *file, const char *topLevelTag)
920 {
921 BYTE *xml;
922 DWORD size;
923 bool success;
924
925 xml = LoadFile(file, &size);
926 if (xml != NULL)
927 {
928 success = loadXmlConfigFromMemory((char *)xml, (int)size, file, topLevelTag);
929 }
930 else
931 {
932 success = false;
933 }
934
935 return success;
936 }
937
938
939 //
940 // Load config file with format auto detection
941 //
942
943 bool Config::loadConfig(const TCHAR *file, const TCHAR *defaultIniSection)
944 {
945 FILE *f;
946 int ch;
947
948 f = _tfopen(file, _T("r"));
949 if (f == NULL)
950 {
951 error(_T("Cannot open file %s"), file);
952 return false;
953 }
954
955 // Skip all space/newline characters
956 do
957 {
958 ch = fgetc(f);
959 } while(isspace(ch));
960
961 fclose(f);
962
963 // If first non-space character is < assume XML format, otherwise assume INI format
964 return (ch == '<') ? loadXmlConfig(file) : loadIniConfig(file, defaultIniSection);
965 }
966
967
968 //
969 // Load all files in given directory
970 //
971
972 bool Config::loadConfigDirectory(const TCHAR *path, const TCHAR *defaultIniSection)
973 {
974 DIR *dir;
975 struct dirent *file;
976 TCHAR fileName[MAX_PATH];
977 bool success;
978
979 #ifdef UNICODE
980 char mbpath[MAX_PATH];
981 WCHAR wname[MAX_PATH];
982
983 WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR, path, -1, mbpath, MAX_PATH, NULL, NULL);
984 mbpath[MAX_PATH - 1] = 0;
985 dir = opendir(mbpath);
986 #else
987 dir = opendir(path);
988 #endif
989 if (dir != NULL)
990 {
991 success = true;
992 while(1)
993 {
994 file = readdir(dir);
995 if (file == NULL)
996 break;
997
998 if (!strcmp(file->d_name, ".") || !strcmp(file->d_name, ".."))
999 continue;
1000
1001 size_t len = _tcslen(path) + strlen(file->d_name) + 2;
1002 if (len > MAX_PATH)
1003 continue; // Full file name is too long
1004
1005 _tcscpy(fileName, path);
1006 _tcscat(fileName, FS_PATH_SEPARATOR);
1007 #ifdef UNICODE
1008 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, file->d_name, -1, wname, MAX_PATH);
1009 wcscat(fileName, wname);
1010 #else
1011 strcat(fileName, file->d_name);
1012 #endif
1013
1014 success = success && loadConfig(fileName, defaultIniSection);
1015 }
1016 closedir(dir);
1017 }
1018 else
1019 {
1020 success = false;
1021 }
1022
1023 return success;
1024 }
1025
1026
1027 //
1028 // Print config to output stream
1029 //
1030
1031 void Config::print(FILE *file)
1032 {
1033 if (m_root != NULL)
1034 m_root->print(file, 0);
1035 }
1036
1037
1038 //
1039 // Create XML from config
1040 //
1041
1042 String Config::createXml()
1043 {
1044 String xml;
1045 m_root->createXml(xml);
1046 return xml;
1047 }