97a68227202eb53ac16c4f7a13ff24602ee8df5c
[public/netxms.git] / src / libnxlp / parser.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Log Parsing Library
4 ** Copyright (C) 2003-2013 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 by
8 ** 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: parser.cpp
21 **
22 **/
23
24 #include "libnxlp.h"
25 #include <expat.h>
26
27 /**
28 * Context state texts
29 */
30 static const TCHAR *s_states[] = { _T("MANUAL"), _T("AUTO"), _T("INACTIVE") };
31
32 /**
33 * XML parser state for creating LogParser object from XML
34 */
35 #define XML_STATE_INIT -1
36 #define XML_STATE_END -2
37 #define XML_STATE_ERROR -255
38 #define XML_STATE_PARSER 0
39 #define XML_STATE_RULES 1
40 #define XML_STATE_RULE 2
41 #define XML_STATE_MATCH 3
42 #define XML_STATE_EVENT 4
43 #define XML_STATE_FILE 5
44 #define XML_STATE_ID 6
45 #define XML_STATE_LEVEL 7
46 #define XML_STATE_SOURCE 8
47 #define XML_STATE_CONTEXT 9
48 #define XML_STATE_MACROS 10
49 #define XML_STATE_MACRO 11
50 #define XML_STATE_DESCRIPTION 12
51
52 struct XML_PARSER_STATE
53 {
54 LogParser *parser;
55 int state;
56 String regexp;
57 String event;
58 String file;
59 StringList files;
60 ObjectArray<int> encodings;
61 String id;
62 String level;
63 String source;
64 String context;
65 String description;
66 int contextAction;
67 String ruleContext;
68 int numEventParams;
69 String errorText;
70 String macroName;
71 String macro;
72 bool invertedRule;
73 bool breakFlag;
74 XML_PARSER_STATE(): encodings(1, 1, true) {}
75 };
76
77 /**
78 * Parser default constructor
79 */
80 LogParser::LogParser()
81 {
82 m_numRules = 0;
83 m_rules = NULL;
84 m_cb = NULL;
85 m_userArg = NULL;
86 m_name = NULL;
87 m_fileName = NULL;
88 m_fileEncoding = LP_FCP_ACP;
89 m_eventNameList = NULL;
90 m_eventResolver = NULL;
91 m_thread = INVALID_THREAD_HANDLE;
92 m_recordsProcessed = 0;
93 m_recordsMatched = 0;
94 m_processAllRules = false;
95 m_traceLevel = 0;
96 m_traceCallback = NULL;
97 _tcscpy(m_status, LPS_INIT);
98 #ifdef _WIN32
99 m_marker = NULL;
100 #endif
101 }
102
103 /**
104 * Parser copy constructor
105 */
106 LogParser::LogParser(LogParser *src)
107 {
108 m_numRules = src->m_numRules;
109 m_rules = (LogParserRule **)malloc(sizeof(LogParserRule *) * m_numRules);
110 for(int i = 0; i < m_numRules; i++)
111 m_rules[i] = new LogParserRule(src->m_rules[i], this);
112
113 m_cb = src->m_cb;
114 m_userArg = src->m_userArg;
115 m_name = (src->m_name != NULL) ? _tcsdup(src->m_name) : NULL;
116 m_fileName = (src->m_fileName != NULL) ? _tcsdup(src->m_fileName) : NULL;
117 m_fileEncoding = src->m_fileEncoding;
118
119 if (src->m_eventNameList != NULL)
120 {
121 int count;
122 for(count = 0; src->m_eventNameList[count].text != NULL; count++);
123 m_eventNameList = (count > 0) ? (CODE_TO_TEXT *)nx_memdup(src->m_eventNameList, sizeof(CODE_TO_TEXT) * (count + 1)) : NULL;
124 }
125 else
126 {
127 m_eventNameList = NULL;
128 }
129
130 m_eventResolver = src->m_eventResolver;
131 m_thread = INVALID_THREAD_HANDLE;
132 m_recordsProcessed = 0;
133 m_recordsMatched = 0;
134 m_processAllRules = src->m_processAllRules;
135 m_traceLevel = src->m_traceLevel;
136 m_traceCallback = src->m_traceCallback;
137 _tcscpy(m_status, LPS_INIT);
138 #ifdef _WIN32
139 m_marker = _tcsdup_ex(src->m_marker);
140 #endif
141 }
142
143 /**
144 * Destructor
145 */
146 LogParser::~LogParser()
147 {
148 int i;
149
150 for(i = 0; i < m_numRules; i++)
151 delete m_rules[i];
152 safe_free(m_rules);
153 safe_free(m_name);
154 safe_free(m_fileName);
155 #ifdef _WIN32
156 safe_free(m_marker);
157 #endif
158 }
159
160 /**
161 * Trace
162 */
163 void LogParser::trace(int level, const TCHAR *format, ...)
164 {
165 va_list args;
166
167 if ((m_traceCallback == NULL) || (level > m_traceLevel))
168 return;
169
170 va_start(args, format);
171 m_traceCallback(format, args);
172 va_end(args);
173 }
174
175 /**
176 * Add rule
177 */
178 bool LogParser::addRule(LogParserRule *rule)
179 {
180 bool isOK;
181
182 isOK = rule->isValid();
183 if (isOK)
184 {
185 m_rules = (LogParserRule **)realloc(m_rules, sizeof(LogParserRule *) * (m_numRules + 1));
186 m_rules[m_numRules++] = rule;
187 }
188 else
189 {
190 delete rule;
191 }
192 return isOK;
193 }
194
195 /**
196 * Create and add rule
197 */
198 bool LogParser::addRule(const TCHAR *regexp, UINT32 eventCode, const TCHAR *eventName, int numParams)
199 {
200 return addRule(new LogParserRule(this, regexp, eventCode, eventName, numParams));
201 }
202
203 /**
204 * Check context
205 */
206 const TCHAR *LogParser::checkContext(LogParserRule *rule)
207 {
208 const TCHAR *state;
209
210 if (rule->getContext() == NULL)
211 {
212 trace(5, _T(" rule has no context"));
213 return s_states[CONTEXT_SET_MANUAL];
214 }
215
216 state = m_contexts.get(rule->getContext());
217 if (state == NULL)
218 {
219 trace(5, _T(" context '%s' inactive, rule should be skipped"), rule->getContext());
220 return NULL; // Context inactive, don't use this rule
221 }
222
223 if (!_tcscmp(state, s_states[CONTEXT_CLEAR]))
224 {
225 trace(5, _T(" context '%s' inactive, rule should be skipped"), rule->getContext());
226 return NULL;
227 }
228 else
229 {
230 trace(5, _T(" context '%s' active (mode=%s)"), rule->getContext(), state);
231 return state;
232 }
233 }
234
235 /**
236 * Match log record
237 */
238 bool LogParser::matchLogRecord(bool hasAttributes, const TCHAR *source, UINT32 eventId,
239 UINT32 level, const TCHAR *line, UINT32 objectId)
240 {
241 int i;
242 const TCHAR *state;
243 bool matched = false;
244
245 if (hasAttributes)
246 trace(5, _T("Match event: source=\"%s\" id=%u level=%d text=\"%s\""), source, eventId, level, line);
247 else
248 trace(5, _T("Match line: \"%s\""), line);
249
250 m_recordsProcessed++;
251 for(i = 0; i < m_numRules; i++)
252 {
253 trace(6, _T("checking rule %d \"%s\""), i + 1, m_rules[i]->getDescription());
254 if ((state = checkContext(m_rules[i])) != NULL)
255 {
256 bool ruleMatched = hasAttributes ?
257 m_rules[i]->matchEx(source, eventId, level, line, m_cb, objectId, m_userArg) :
258 m_rules[i]->match(line, m_cb, objectId, m_userArg);
259 if (ruleMatched)
260 {
261 trace(5, _T("rule %d \"%s\" matched"), i + 1, m_rules[i]->getDescription());
262 if (!matched)
263 m_recordsMatched++;
264
265 // Update context
266 if (m_rules[i]->getContextToChange() != NULL)
267 {
268 m_contexts.set(m_rules[i]->getContextToChange(), s_states[m_rules[i]->getContextAction()]);
269 trace(5, _T("rule %d \"%s\": context %s set to %s"), i + 1, m_rules[i]->getDescription(), m_rules[i]->getContextToChange(), s_states[m_rules[i]->getContextAction()]);
270 }
271
272 // Set context of this rule to inactive if rule context mode is "automatic reset"
273 if (!_tcscmp(state, s_states[CONTEXT_SET_AUTOMATIC]))
274 {
275 m_contexts.set(m_rules[i]->getContext(), s_states[CONTEXT_CLEAR]);
276 trace(5, _T("rule %d \"%s\": context %s cleared because it was set to automatic reset mode"),
277 i + 1, m_rules[i]->getDescription(), m_rules[i]->getContext());
278 }
279 matched = true;
280 if (!m_processAllRules || m_rules[i]->getBreakFlag())
281 break;
282 }
283 }
284 }
285 if (i < m_numRules)
286 trace(5, _T("processing stopped at rule %d \"%s\"; result = %s"), i + 1,
287 m_rules[i]->getDescription(), matched ? _T("true") : _T("false"));
288 else
289 trace(5, _T("Processing stopped at end of rules list; result = %s"), matched ? _T("true") : _T("false"));
290 return matched;
291 }
292
293 /**
294 * Match simple log line
295 */
296 bool LogParser::matchLine(const TCHAR *line, UINT32 objectId)
297 {
298 return matchLogRecord(false, NULL, 0, 0, line, objectId);
299 }
300
301 /**
302 * Match log event (text with additional attributes - source, severity, event id)
303 */
304 bool LogParser::matchEvent(const TCHAR *source, UINT32 eventId, UINT32 level, const TCHAR *line, UINT32 objectId)
305 {
306 return matchLogRecord(true, source, eventId, level, line, objectId);
307 }
308
309 /**
310 * Set associated file name
311 */
312 void LogParser::setFileName(const TCHAR *name)
313 {
314 safe_free(m_fileName);
315 m_fileName = (name != NULL) ? _tcsdup(name) : NULL;
316 if (m_name == NULL)
317 m_name = _tcsdup(name); // Set parser name to file name
318 }
319
320 /**
321 * Set parser name
322 */
323 void LogParser::setName(const TCHAR *name)
324 {
325 safe_free(m_name);
326 m_name = _tcsdup((name != NULL) ? name : CHECK_NULL(m_fileName));
327 }
328
329 /**
330 * Add macro
331 */
332 void LogParser::addMacro(const TCHAR *name, const TCHAR *value)
333 {
334 m_macros.set(name, value);
335 }
336
337 /**
338 * Get macro
339 */
340 const TCHAR *LogParser::getMacro(const TCHAR *name)
341 {
342 const TCHAR *value;
343
344 value = m_macros.get(name);
345 return CHECK_NULL_EX(value);
346 }
347
348 /**
349 * Create parser configuration from XML - callback for element start
350 */
351 static void StartElement(void *userData, const char *name, const char **attrs)
352 {
353 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
354
355 if (!strcmp(name, "parser"))
356 {
357 ps->state = XML_STATE_PARSER;
358 ps->parser->setProcessAllFlag(XMLGetAttrBoolean(attrs, "processAll", false));
359 ps->parser->setTraceLevel(XMLGetAttrInt(attrs, "trace", 0));
360 const char *name = XMLGetAttr(attrs, "name");
361 if (name != NULL)
362 {
363 #ifdef UNICODE
364 WCHAR *wname = WideStringFromUTF8String(name);
365 ps->parser->setName(wname);
366 free(wname);
367 #else
368 ps->parser->setName(name);
369 #endif
370 }
371 }
372 else if (!strcmp(name, "file"))
373 {
374 ps->state = XML_STATE_FILE;
375 const char *encoding = XMLGetAttr(attrs, "encoding");
376 if (encoding != NULL)
377 {
378 if (!stricmp(encoding, "acp") || (*encoding == 0))
379 {
380 ps->encodings.add(new int(LP_FCP_ACP));
381 }
382 else if (!stricmp(encoding, "utf8") || !stricmp(encoding, "utf-8"))
383 {
384 ps->encodings.add(new int(LP_FCP_UTF8));
385 }
386 else if (!stricmp(encoding, "ucs2") || !stricmp(encoding, "ucs-2") || !stricmp(encoding, "utf-16"))
387 {
388 ps->encodings.add(new int(LP_FCP_UCS2));
389 }
390 else if (!stricmp(encoding, "ucs4") || !stricmp(encoding, "ucs-4") || !stricmp(encoding, "utf-32"))
391 {
392 ps->encodings.add(new int(LP_FCP_UCS4));
393 }
394 else
395 {
396 ps->errorText = _T("Invalid file encoding");
397 ps->state = XML_STATE_ERROR;
398 }
399 }
400 else
401 {
402 ps->encodings.add(new int(LP_FCP_ACP));
403 }
404 }
405 else if (!strcmp(name, "macros"))
406 {
407 ps->state = XML_STATE_MACROS;
408 }
409 else if (!strcmp(name, "macro"))
410 {
411 const char *name;
412
413 ps->state = XML_STATE_MACRO;
414 name = XMLGetAttr(attrs, "name");
415 #ifdef UNICODE
416 ps->macroName = L"";
417 ps->macroName.addMultiByteString(name, (UINT32)strlen(name), CP_UTF8);
418 #else
419 ps->macroName = CHECK_NULL_A(name);
420 #endif
421 ps->macro = NULL;
422 }
423 else if (!strcmp(name, "rules"))
424 {
425 ps->state = XML_STATE_RULES;
426 }
427 else if (!strcmp(name, "rule"))
428 {
429 ps->regexp = NULL;
430 ps->invertedRule = false;
431 ps->event = NULL;
432 ps->context = NULL;
433 ps->contextAction = CONTEXT_SET_AUTOMATIC;
434 ps->description = NULL;
435 ps->id = NULL;
436 ps->source = NULL;
437 ps->level = NULL;
438 #ifdef UNICODE
439 ps->ruleContext = L"";
440 const char *context = XMLGetAttr(attrs, "context");
441 if (context != NULL)
442 ps->ruleContext.addMultiByteString(context, (UINT32)strlen(context), CP_UTF8);
443 #else
444 ps->ruleContext = XMLGetAttr(attrs, "context");
445 #endif
446 ps->breakFlag = XMLGetAttrBoolean(attrs, "break", false);
447 ps->state = XML_STATE_RULE;
448 ps->numEventParams = 0;
449 }
450 else if (!strcmp(name, "match"))
451 {
452 ps->state = XML_STATE_MATCH;
453 ps->invertedRule = XMLGetAttrBoolean(attrs, "invert", false);
454 }
455 else if (!strcmp(name, "id") || !strcmp(name, "facility"))
456 {
457 ps->state = XML_STATE_ID;
458 }
459 else if (!strcmp(name, "level") || !strcmp(name, "severity"))
460 {
461 ps->state = XML_STATE_LEVEL;
462 }
463 else if (!strcmp(name, "source") || !strcmp(name, "tag"))
464 {
465 ps->state = XML_STATE_SOURCE;
466 }
467 else if (!strcmp(name, "event"))
468 {
469 ps->numEventParams = XMLGetAttrUINT32(attrs, "params", 0);
470 ps->state = XML_STATE_EVENT;
471 }
472 else if (!strcmp(name, "context"))
473 {
474 const char *action;
475
476 ps->state = XML_STATE_CONTEXT;
477
478 action = XMLGetAttr(attrs, "action");
479 if (action == NULL)
480 action = "set";
481
482 if (!strcmp(action, "set"))
483 {
484 const char *mode;
485
486 mode = XMLGetAttr(attrs, "reset");
487 if (mode == NULL)
488 mode = "auto";
489
490 if (!strcmp(mode, "auto"))
491 {
492 ps->contextAction = CONTEXT_SET_AUTOMATIC;
493 }
494 else if (!strcmp(mode, "manual"))
495 {
496 ps->contextAction = CONTEXT_SET_MANUAL;
497 }
498 else
499 {
500 ps->errorText = _T("Invalid context reset mode");
501 ps->state = XML_STATE_ERROR;
502 }
503 }
504 else if (!strcmp(action, "clear"))
505 {
506 ps->contextAction = CONTEXT_CLEAR;
507 }
508 else
509 {
510 ps->errorText = _T("Invalid context action");
511 ps->state = XML_STATE_ERROR;
512 }
513 }
514 else if (!strcmp(name, "description"))
515 {
516 ps->state = XML_STATE_DESCRIPTION;
517 }
518 else
519 {
520 ps->state = XML_STATE_ERROR;
521 }
522 }
523
524 /**
525 * Create parser configuration from XML - callback for element end
526 */
527 static void EndElement(void *userData, const char *name)
528 {
529 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
530
531 if (!strcmp(name, "parser"))
532 {
533 ps->state = XML_STATE_END;
534 }
535 else if (!strcmp(name, "file"))
536 {
537 ps->files.add(ps->file);
538 ps->file = _T("");
539 ps->state = XML_STATE_PARSER;
540 }
541 else if (!strcmp(name, "macros"))
542 {
543 ps->state = XML_STATE_PARSER;
544 }
545 else if (!strcmp(name, "macro"))
546 {
547 ps->parser->addMacro(ps->macroName, ps->macro);
548 ps->state = XML_STATE_MACROS;
549 }
550 else if (!strcmp(name, "rules"))
551 {
552 ps->state = XML_STATE_PARSER;
553 }
554 else if (!strcmp(name, "rule"))
555 {
556 UINT32 eventCode;
557 const TCHAR *eventName = NULL;
558 TCHAR *eptr;
559 LogParserRule *rule;
560
561 ps->event.trim();
562 eventCode = _tcstoul(ps->event, &eptr, 0);
563 if (*eptr != 0)
564 {
565 eventCode = ps->parser->resolveEventName(ps->event, 0);
566 if (eventCode == 0)
567 {
568 eventName = (const TCHAR *)ps->event;
569 }
570 }
571 if (ps->regexp.isEmpty())
572 ps->regexp = _T(".*");
573 rule = new LogParserRule(ps->parser, (const TCHAR *)ps->regexp, eventCode, eventName, ps->numEventParams);
574 if (!ps->ruleContext.isEmpty())
575 rule->setContext(ps->ruleContext);
576 if (!ps->context.isEmpty())
577 {
578 rule->setContextToChange(ps->context);
579 rule->setContextAction(ps->contextAction);
580 }
581
582 if (!ps->description.isEmpty())
583 rule->setDescription(ps->description);
584
585 if (!ps->source.isEmpty())
586 rule->setSource(ps->source);
587
588 if (!ps->level.isEmpty())
589 rule->setLevel(_tcstoul(ps->level, NULL, 0));
590
591 if (!ps->id.isEmpty())
592 {
593 UINT32 start, end;
594 TCHAR *eptr;
595
596 start = _tcstoul(ps->id, &eptr, 0);
597 if (*eptr == 0)
598 {
599 end = start;
600 }
601 else /* TODO: add better error handling */
602 {
603 while(!_istdigit(*eptr))
604 eptr++;
605 end = _tcstoul(eptr, NULL, 0);
606 }
607 rule->setIdRange(start, end);
608 }
609
610 rule->setInverted(ps->invertedRule);
611 rule->setBreakFlag(ps->breakFlag);
612
613 ps->parser->addRule(rule);
614 ps->state = XML_STATE_RULES;
615 }
616 else if (!strcmp(name, "match"))
617 {
618 ps->state = XML_STATE_RULE;
619 }
620 else if (!strcmp(name, "id") || !strcmp(name, "facility"))
621 {
622 ps->state = XML_STATE_RULE;
623 }
624 else if (!strcmp(name, "level") || !strcmp(name, "severity"))
625 {
626 ps->state = XML_STATE_RULE;
627 }
628 else if (!strcmp(name, "source") || !strcmp(name, "tag"))
629 {
630 ps->state = XML_STATE_RULE;
631 }
632 else if (!strcmp(name, "event"))
633 {
634 ps->state = XML_STATE_RULE;
635 }
636 else if (!strcmp(name, "context"))
637 {
638 ps->state = XML_STATE_RULE;
639 }
640 else if (!strcmp(name, "description"))
641 {
642 ps->state = XML_STATE_RULE;
643 }
644 }
645
646 /**
647 * Create parser configuration from XML - callback for element data
648 */
649 static void CharData(void *userData, const XML_Char *s, int len)
650 {
651 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
652
653 switch(ps->state)
654 {
655 case XML_STATE_MATCH:
656 ps->regexp.addMultiByteString(s, len, CP_UTF8);
657 break;
658 case XML_STATE_ID:
659 ps->id.addMultiByteString(s, len, CP_UTF8);
660 break;
661 case XML_STATE_LEVEL:
662 ps->level.addMultiByteString(s, len, CP_UTF8);
663 break;
664 case XML_STATE_SOURCE:
665 ps->source.addMultiByteString(s, len, CP_UTF8);
666 break;
667 case XML_STATE_EVENT:
668 ps->event.addMultiByteString(s, len, CP_UTF8);
669 break;
670 case XML_STATE_FILE:
671 ps->file.addMultiByteString(s, len, CP_UTF8);
672 break;
673 case XML_STATE_CONTEXT:
674 ps->context.addMultiByteString(s, len, CP_UTF8);
675 break;
676 case XML_STATE_DESCRIPTION:
677 ps->description.addMultiByteString(s, len, CP_UTF8);
678 break;
679 case XML_STATE_MACRO:
680 ps->macro.addMultiByteString(s, len, CP_UTF8);
681 break;
682 default:
683 break;
684 }
685 }
686
687 /**
688 * Create parser configuration from XML. Returns array of identical parsers,
689 * one for each <file> tag in source XML. Resulting parsers only differs with file names.
690 */
691 ObjectArray<LogParser> *LogParser::createFromXml(const char *xml, int xmlLen, TCHAR *errorText, int errBufSize, bool (*eventResolver)(const TCHAR *, UINT32 *))
692 {
693 ObjectArray<LogParser> *parsers = NULL;
694 bool success;
695
696 XML_Parser parser = XML_ParserCreate(NULL);
697 XML_PARSER_STATE state;
698 state.parser = new LogParser;
699 state.parser->setEventNameResolver(eventResolver);
700 state.state = -1;
701 XML_SetUserData(parser, &state);
702 XML_SetElementHandler(parser, StartElement, EndElement);
703 XML_SetCharacterDataHandler(parser, CharData);
704 success = (XML_Parse(parser, xml, (xmlLen == -1) ? (int)strlen(xml) : xmlLen, TRUE) != XML_STATUS_ERROR);
705 if (!success && (errorText != NULL))
706 {
707 _sntprintf(errorText, errBufSize, _T("%hs at line %d"),
708 XML_ErrorString(XML_GetErrorCode(parser)),
709 (int)XML_GetCurrentLineNumber(parser));
710 }
711 XML_ParserFree(parser);
712 if (success && (state.state == XML_STATE_ERROR))
713 {
714 success = false;
715 if (errorText != NULL)
716 nx_strncpy(errorText, state.errorText, errBufSize);
717 }
718 else if (success)
719 {
720 parsers = new ObjectArray<LogParser>;
721 if (state.files.size() > 0)
722 {
723 for(int i = 0; i < state.files.size(); i++)
724 {
725 LogParser *p = (i > 0) ? new LogParser(state.parser) : state.parser;
726 p->setFileName(state.files.get(i));
727 p->setFileEncoding(*state.encodings.get(i));
728 parsers->add(p);
729 }
730 }
731 else
732 {
733 // It is possible to have parser without <file> tag, syslog parser for example
734 parsers->add(state.parser);
735 }
736 }
737
738 return parsers;
739 }
740
741 /**
742 * Resolve event name
743 */
744 UINT32 LogParser::resolveEventName(const TCHAR *name, UINT32 defVal)
745 {
746 if (m_eventNameList != NULL)
747 {
748 int i;
749
750 for(i = 0; m_eventNameList[i].text != NULL; i++)
751 if (!_tcsicmp(name, m_eventNameList[i].text))
752 return m_eventNameList[i].code;
753 }
754
755 if (m_eventResolver != NULL)
756 {
757 UINT32 val;
758
759 if (m_eventResolver(name, &val))
760 return val;
761 }
762
763 return defVal;
764 }