Imported Upstream version 1.0beta3
[platform/upstream/syncevolution.git] / src / synthesis / src / sysync / configelement.cpp
1 /*
2  *  File:         ConfigElement.cpp
3  *
4  *  Author:                       Lukas Zeller (luz@synthesis.ch)
5  *
6  *  TConfigElement
7  *    Element of hierarchical configuration
8  *
9  *  Copyright (c) 2001-2009 by Synthesis AG (www.synthesis.ch)
10  *
11  *  2001-11-14 : luz : created
12  */
13
14 // includes
15 #include "prefix_file.h"
16 #include "sysync.h"
17 #include "configelement.h"
18 #include "syncappbase.h"
19 #include "scriptcontext.h"
20 #include "multifielditem.h"
21 #include "sysync_crc16.h"
22 #include "vtimezone.h"
23
24
25 using namespace sysync;
26
27 #ifndef RELEASE_VERSION
28 // if defined, parsing debug info goes to console
29 //#define CONSDEBUG
30 #endif
31
32 #ifdef CONSDEBUG
33 #define CONSDBGPRINTF(m) CONSOLEPRINTF(m)
34
35 const char * const ParseModeNames[numParseModes] = {
36   "element",  // normal, expecting sub-elements
37   "nested",   // like pamo_element, but scanning nested elements in same TConfigElement
38   "delegated", // I have delegated parsing of a single sub-element of mine to another element (without XML nesting)
39   "end",   // expecting end of element
40         "endnested", // expecting end of nested element (i.e. will call nestedElementEnd())
41   "all",   // read over all content
42   "string",   // string, but with all WSP converted to space and removed at beginning an end
43   "rawstring", // string without any changes
44   "cstring", // string with \\, \t,\n,\r and \xXX escape conversion
45   "macrostring", // string which can contain macros to substitute config vars in $xxx or $() format
46   #ifdef SCRIPT_SUPPORT
47   "script", // tokenized script
48   "functiondef", // script function definition
49   #endif
50   "field",
51   "path",  // string is updated such that a filename can be appended directly
52   "boolean",
53   "tristate",
54   "timestamp",
55   "timezone",
56   "vtimezone",
57   "idcode",
58   "char",
59   "int64",
60   "int32",
61   "int16",
62   "enum1by",
63   "enum2by",
64   "enum4by"
65 };
66 #else
67 #define CONSDBGPRINTF(m)
68 #endif
69
70
71
72 /*
73  * Implementation of TConfigElement
74  */
75
76 /* public TConfigElement members */
77
78
79 TConfigElement::TConfigElement(const char *aElementName, TConfigElement *aParentElementP)
80 {
81   // set element name
82   fElementName=aElementName;
83   fResolveImmediately=false; // by default, elements do not resolve immediately, but when entire config is read
84   // set parent and root element pointers
85   fParentElementP=aParentElementP;
86   if (fParentElementP) {
87     // has parent, get root from parent
88     fRootElementP=fParentElementP->getRootElement();
89   }
90   else {
91     // base element cannot be root
92     fRootElementP=NULL;
93   }
94   #ifndef HARDCODED_CONFIG
95   // init parsing
96   ResetParsing();
97   #endif
98   fCfgVarExp=0;
99 } // TConfigElement::TConfigElement
100
101
102 TConfigElement::~TConfigElement()
103 {
104   clear();
105 } // TConfigElement::~TConfigElement
106
107
108 TSyncAppBase *TConfigElement::getSyncAppBase(void)
109 {
110   return fRootElementP ? fRootElementP->fSyncAppBaseP : NULL;
111 } // TConfigElement::getSyncAppBase
112
113
114 // - convenience version for getting time
115 lineartime_t TConfigElement::getSystemNowAs(timecontext_t aContext)
116 {
117   return sysync::getSystemNowAs(aContext,getSyncAppBase()->getAppZones());
118 } // TConfigElement::getSystemNowAs
119
120
121 #ifndef HARDCODED_CONFIG
122
123 // static helper, returns attribute value or NULL if none
124 const char *TConfigElement::getAttr(const char **aAttributes, const char *aAttrName)
125 {
126   if (!aAttributes) return NULL;
127   const char *attname;
128   while ((attname=*aAttributes)!=0) {
129     if (strucmp(attname,aAttrName)==0) {
130       return *(++aAttributes); // found, return value
131     }
132     aAttributes+=2; // skip value, go to next name
133   }
134   return NULL; // not found
135 } // TConfigElement::getAttr
136
137
138 // get attribute value, check for macro expansion
139 // @param aDefaultExpand : if set, non-recursive expansion is done anyway, otherwise, a "expand" attribute is required
140 bool TConfigElement::getAttrExpanded(const char **aAttributes, const char *aAttrName, string &aValue, bool aDefaultExpand)
141 {
142   cAppCharP val = getAttr(aAttributes, aAttrName);
143   if (!val) return false; // no value
144   aValue = val;
145   getSyncAppBase()->expandConfigVars(aValue, aDefaultExpand ? 1 : fCfgVarExp, this, getName());
146   return true;
147 } // TConfigElement::getAttrExpanded
148
149
150 // static helper, returns true if attribute has valid (or none, if aOpt) bool value
151 bool TConfigElement::getAttrBool(const char **aAttributes, const char *aAttrName, bool &aBool, bool aOpt)
152 {
153   const char *v = getAttr(aAttributes,aAttrName);
154   if (!v) return aOpt; // not existing, is ok if optional
155   return StrToBool(v,aBool);
156 } // TConfigElement::getAttrBool
157
158
159 // static helper, returns true if attribute has valid (or none, if aOpt) short value
160 bool TConfigElement::getAttrShort(const char **aAttributes, const char *aAttrName, sInt16 &aShort, bool aOpt)
161 {
162   const char *v = getAttr(aAttributes,aAttrName);
163   if (!v) return aOpt; // not existing, is ok if optional
164   return StrToShort(v,aShort);
165 } // TConfigElement::getAttrShort
166
167
168 // static helper, returns true if attribute has valid (or none, if aOpt) short value
169 bool TConfigElement::getAttrLong(const char **aAttributes, const char *aAttrName, sInt32 &aLong, bool aOpt)
170 {
171   const char *v = getAttr(aAttributes,aAttrName);
172   if (!v) return aOpt; // not existing, is ok if optional
173   return StrToLong(v,aLong);
174 } // TConfigElement::getAttrLong
175
176
177 #ifdef SCRIPT_SUPPORT
178 sInt16 TConfigElement::getFieldIndex(cAppCharP aFieldName, TFieldListConfig *aFieldListP, TScriptContext *aScriptContextP)
179 {
180   // fields or local script variables (if any) can be mapped
181   if (aScriptContextP)
182     return aScriptContextP->getIdentifierIndex(OBJ_AUTO, aFieldListP,aFieldName);
183   else
184     return aFieldListP ? aFieldListP->fieldIndex(aFieldName) : VARIDX_UNDEFINED;
185 }
186 #else
187 sInt16 TConfigElement::getFieldIndex(cAppCharP aFieldName, TFieldListConfig *aFieldListP)
188 {
189   // only direct mapping of MultiFieldItem fields
190   return aFieldListP ? aFieldListP->fieldIndex(aFieldName) : VARIDX_UNDEFINED;
191 }
192 #endif
193
194 #endif // HARDCODED_CONFIG
195
196
197
198 void TConfigElement::clear(void)
199 {
200   // nop
201 } // TConfigElement::clear
202
203
204 // resolve (finish after all data is parsed)
205 void TConfigElement::Resolve(bool aLastPass)
206 {
207   #ifndef HARDCODED_CONFIG
208   // Only resolve if element was not already resolved when it finished parsing
209   // or if it was not parsed at all (that is, it did not appear in the config
210   // at all and only contains default values)
211   if (!fResolveImmediately || !fCompleted) {
212     // try to finally resolve private stuff now that all children are resolved
213     localResolve(aLastPass);
214   }
215   #else
216   // hardcoded config is never resolved early
217   localResolve(aLastPass);
218   #endif
219 }; // TConfigElement::Resolve
220
221
222 #ifdef SYDEBUG
223
224 TDebugLogger *TConfigElement::getDbgLogger(void)
225 {
226   // commands log to session's logger
227   TSyncAppBase *appBase = getSyncAppBase();
228   return appBase ? appBase->getDbgLogger() : NULL;
229 } // TConfigElement::getDbgLogger
230
231 uInt32 TConfigElement::getDbgMask(void)
232 {
233   TSyncAppBase *appBase = getSyncAppBase();
234   if (!appBase) return 0; // no session, no debug
235   return appBase->getDbgMask();
236 } // TConfigElement::getDbgMask
237
238 #endif
239
240
241 #ifndef HARDCODED_CONFIG
242
243 void TConfigElement::ResetParsing(void)
244 {
245   fChildParser=NULL;
246   fParseMode=pamo_element; // expecting elements
247   fNest=0; // normal elements start with Nest=0
248   fExpectAllNestStart=-1; // no expectAll called yet
249   fCompleted=false; // not yet completed parsing
250   fTempString.erase(); // nothing accumulated yet
251   #ifdef SYSER_REGISTRATION
252   fLockedElement=false;
253   fHadLockedSubs=false;
254   #endif
255 } // TConfigElement::ResetParsing(void)
256
257
258 // report error
259 void TConfigElement::ReportError(bool aFatal, const char *aMessage, ...)
260 {
261   const sInt32 maxmsglen=1024;
262   char msg[maxmsglen];
263   va_list args;
264
265   msg[0]='\0';
266   va_start(args, aMessage);
267   char *p = msg;
268   int n=0;
269   // show fatal flag
270   if (aFatal) {
271     strcat(p,"Fatal: ");
272     n=strlen(p);
273     p+=n;
274   }
275   // assemble the message string
276   vsnprintf(p, maxmsglen-n, aMessage, args);
277   va_end(args);
278   // set the message
279   TRootConfigElement *rootP = getRootElement();
280   if (!rootP)
281     SYSYNC_THROW(TConfigParseException("Element without root"));
282   rootP->setError(aFatal,msg);
283 } // TConfigElement::ReportError
284
285
286 // fail in parsing (short form of ReportError)
287 bool TConfigElement::fail(const char *aMessage, ...)
288 {
289   const sInt32 maxmsglen=1024;
290   char msg[maxmsglen];
291   va_list args;
292
293   msg[0]='\0';
294   va_start(args, aMessage);
295   // assemble the message string
296   vsnprintf(msg, maxmsglen, aMessage, args);
297   va_end(args);
298   // report the error
299   ReportError(true,msg);
300   // skip the rest
301   expectAll();
302   // return value
303   return true;
304 } // TConfigElement::fail
305
306
307 // start of element, this config element decides who processes this element
308 bool TConfigElement::startElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
309 {
310   if (fChildParser) {
311     // parsing in a sub-level, delegate
312     return fChildParser->startElement(aElementName,aAttributes,aLine);
313   }
314   else {
315     CONSDBGPRINTF((
316       "'%s' starts in configElement='%s' with parsemode='%s' nest=%hd at source line=%ld",
317       aElementName,
318       getName(),
319       ParseModeNames[fParseMode],
320       fNest,
321       aLine
322     ));
323     #ifdef SYSER_REGISTRATION
324     // check for locked elements or subtrees
325     // - copy parent's lock status
326     if (getParentElement()) fLockedElement=getParentElement()->fLockedElement;
327     // - if not already locked, see if locked section starts with this element
328     if (!fLockedElement) {
329       getAttrBool(aAttributes,"locked",fLockedElement,true);
330     }
331     // Perform CRC sum if locked element
332     if (fLockedElement) {
333       CONSDBGPRINTF((
334         "'%s' is locked and is included in LockCRC = 0x%04hX",
335         aElementName,
336         getRootElement()->getConfigLockCRC()
337       ));
338       // CRC over opening element name
339       getRootElement()->addToLockCRC(aElementName);
340       // CRC over all attributes
341       if (aAttributes) {
342         const char **attrs=aAttributes;
343         const char *attelem;
344         while ((attelem=*attrs++)!=0) {
345           // alternating names and values
346           getRootElement()->addToLockCRC(attelem);
347         }
348       }
349     }
350     #endif
351     // check conditional parsing
352     bool condmet=true; // assume parsing
353     cAppCharP cond;
354     string vv,val;
355     // check platform filter
356     if (condmet) {
357       cAppCharP pf=getAttr(aAttributes,"platform");
358       if (pf && strucmp(pf,SYSYNC_PLATFORM_NAME)!=0) {
359         // tag is not for this platform, ignore
360         condmet=false;
361       }
362     }
363     // check for config var conditionals
364     if (condmet) {
365       cond=getAttr(aAttributes,"if");
366       if (cond) {
367         // check for value comparison
368         string nam;
369         // - determine comparison
370         cAppCharP p2,p = cond;
371         int cmpres = 2; // 2: invalid, 1: var > cond, -1: var < cond, 0: var=cond
372         bool neg=false;
373         appChar c;
374         while ((c=*p)) {
375           p2=p++;
376           if (c=='=') {
377             if (*p=='=') p++; // = and == are equivalent
378             cmpres=0; // must be equal
379             break;
380           }
381           else if (c=='!' && *p=='=') {
382             p++;
383             cmpres=0;
384             neg=true; // must not be equal
385             break;
386           }
387           else if (c=='>') {
388             if (*p=='=') {
389               p++;
390               cmpres=-1; // var >= cond is equal to NOT val < cond
391               neg=true;
392             }
393             else {
394               cmpres=1; // var > cond
395             }
396             break;
397           }
398           else if (c=='<') {
399             if (*p=='=') {
400               p++;
401               cmpres=1; // var <= cond is equal to NOT val > cond
402               neg=true;
403             }
404             else {
405               cmpres=-1; // var < cond
406             }
407             break;
408           }
409         }
410         // now value is at *p or we have not found a comparison op
411         if (cmpres<2) {
412           nam.assign(cond,p2-cond);
413           val=p;
414         }
415         else {
416           nam=cond;
417           val.erase();
418         }
419         // get var - if it does not exist, comparison always renders false
420         if (nam=="version") {
421           // special comparison mode for version
422           // - parse reference into version components
423           uInt16 maj=0,min=0,rev=0,bld=0;
424           p = val.c_str();
425           do {
426             p+=StrToUShort(p,maj); if (*p++!='.') break;
427             p+=StrToUShort(p,min); if (*p++!='.') break;
428             p+=StrToUShort(p,rev); if (*p++!='.') break;
429             p+=StrToUShort(p,bld);
430           } while(false);
431           // - compare with hexversion
432           StringObjPrintf(val,"%02X%02X%02X%02X",maj,min,rev,bld);
433           nam="hexversion";
434         }
435         // get config var to perform condition check
436         condmet=getSyncAppBase()->getConfigVar(nam.c_str(),vv);
437         if (condmet) {
438           // var exists, perform comparison
439           if (cmpres>=2) {
440             // no comparison, but just boolean check. Non-bool but empty=false, non-bool-non-empty=true
441             condmet = !vv.empty();
442             if (condmet) StrToBool(vv.c_str(), condmet);
443           }
444           else {
445             int res = strcmp(vv.c_str(),val.c_str());
446             res = res>0 ? 1 : (res<0 ? -1 : 0);
447             condmet = neg != (cmpres==res);
448           }
449         }
450       }
451     }
452     // check for ifdef
453     if (condmet) {
454       cond=getAttr(aAttributes,"ifdef");
455       if (cond)
456         condmet = getSyncAppBase()->getConfigVar(cond,vv);
457     }
458     // check for ifndef
459     if (condmet) {
460       cond=getAttr(aAttributes,"ifndef");
461       if (cond)
462         condmet = !getSyncAppBase()->getConfigVar(cond,vv);
463     }
464     // skip this tag if conditions not met
465     if (!condmet) {
466       // skip everything inside
467       CONSDBGPRINTF(("Condition for parsing not met - skipping tag"));
468       expectAll();
469       return true; // ok, go on
470     }
471     // Now we know that we must actually parse this tag
472     // check configvar expansion mode
473     fCfgVarExp=0; // default to automatic (i.e. certain content such as paths or macrostrings will be expanded without "expand" attr)
474     cAppCharP cm=getAttr(aAttributes,"expand");
475     if (cm) {
476         // explicit expand directive for this tag (includes expandable attributes of this tag,
477       // but only attributes queried with getAttrExpanded() can expand at all)
478       bool b;
479       if (strucmp(cm,"single")==0) fCfgVarExp=1;
480       else if (StrToBool(cm,b)) fCfgVarExp = b ? 2 : -1;
481     }
482     // check for use-everywhere special tags
483     if (strucmp(aElementName,"configmsg")==0) {
484       bool iserr=true;
485       cAppCharP msg=getAttr(aAttributes,"error");
486       if (!msg) {
487         msg=getAttr(aAttributes,"warning");
488         iserr=false;
489       }
490       if (!msg) msg="Error: found <configmsg>";
491       ReportError(iserr,msg);
492       expectAll();
493       return false; // generate error message
494     }
495     // check if we are re-entering the same object again (trying to overwrite)
496     if (fCompleted) {
497       ReportError(true,"Duplicate definition of <%s>",aElementName);
498       expectAll();
499       return false; // not allowed, generate error message
500     }
501     if (fParseMode==pamo_element || fParseMode==pamo_nested) {
502       // expecting element
503       fTempString.erase();
504       if (localStartElement(aElementName,aAttributes,aLine)) {
505         // element known and parsing initialized ok
506         #ifdef SYSER_REGISTRATION
507         if (fLockedElement && fChildParser==NULL && fParseMode!=pamo_element && fParseMode!=pamo_nested)
508           fHadLockedSubs=true; // flag that this element has processed non-child subelements in locked mode
509         #endif
510         return true;
511       }
512       else {
513         // unknown element: read over all its contents
514         ReportError(false,"invalid tag <%s>",aElementName);
515         expectAll();
516         return false; // element not known, generate error message
517       }
518     }
519     else if (fParseMode==pamo_all) {
520       // read over contents
521       fNest++;
522     }
523     else {
524       ReportError(false,"no XML tag expected here");
525     }
526     return true;
527   }
528 } // TConfigElement::startElement
529
530
531 // character data of current element
532 void TConfigElement::charData(const char *aCharData, sInt32 aNumChars)
533 {
534   if (fChildParser) {
535     // parsing in a sub-level, delegate
536     return fChildParser->charData(aCharData,aNumChars);
537   }
538   else {
539     if (fParseMode==pamo_all /* %%% not needed, I think || fNest>0 */) {
540       // just ignore
541     }
542     else if (fParseMode==pamo_element || fParseMode==pamo_nested || fParseMode==pamo_end || fParseMode==pamo_endnested) {
543       // only whitespace allowed here
544       while (aNumChars--) {
545         if (!isspace(*aCharData++)) {
546           ReportError(false,"no character data expected");
547           break;
548         }
549       }
550     }
551     else {
552       // accumulate char data in string
553       fTempString.append(aCharData,aNumChars);
554     }
555   }
556 } // TConfigElement::charData
557
558
559
560 // read over all contents of current TConfigElement
561 void TConfigElement::expectAll(void)
562 {
563   // we are already in an element but have no non-decrementing
564   // parse mode set like expectEmpty() or expectString() etc.
565   // so nest count must be incremented to balance decrement occurring
566   // at next element end.
567   fExpectAllNestStart = fParseMode==pamo_nested ? fNest : -1; // remember where we left nested mode and entered ignoring mode
568   fNest++;
569   fParseMode=pamo_all;
570 } // TConfigElement::expectAll
571
572
573 // expect Enum element
574 void TConfigElement::expectEnum(sInt16 aDestSize,void *aPtr, const char * const aEnumNames[], sInt16 aNumEnums)
575 {
576   // save params
577   fParseEnumArray = aEnumNames;
578   fParseEnumNum = aNumEnums;
579   // determine mode and destination
580   switch (aDestSize) {
581     case 1 :
582       fParseMode=pamo_enum1by;
583       fResultPtr.fCharP=(char *)aPtr;
584       break;
585     case 2 :
586       fParseMode=pamo_enum2by;
587       fResultPtr.fShortP=(sInt16 *)aPtr;
588       break;
589     case 4 :
590       fParseMode=pamo_enum4by;
591       fResultPtr.fLongP=(sInt32 *)aPtr;
592       break;
593     default:
594       SYSYNC_THROW(TConfigParseException("expectEnum: invalid enum size"));
595   }
596 } // TConfigElement::expectEnum
597
598
599 // delegate parsing of a single element to another config element
600 // after processing aElementName and all subtags, processing will return to this object
601 // (rather than waiting for an end tag in aConfigElemP)
602 bool TConfigElement::delegateParsingTo(TConfigElement *aConfigElemP, const char *aElementName, const char **aAttributes, sInt32 aLine)
603 {
604   if (aConfigElemP) {
605     expectChildParsing(*aConfigElemP);
606     fParseMode=pamo_delegated;
607     // let child parse the current tag right away
608     return aConfigElemP->localStartElement(aElementName,aAttributes,aLine);
609   }
610   else
611     return false; // if we have no delegate, we can't understand this tag
612 } // TConfigElement::delegateParsingTo
613
614
615 // end of element, returns true when this config element is done parsing
616 bool TConfigElement::endElement(const char *aElementName, bool aIsDelegated)
617 {
618   if (fChildParser) {
619     // parsing in a real or simulated (delegateParsingTo) sub-level
620     if (fChildParser->endElement(aElementName,fParseMode==pamo_delegated)) {
621       // child has finished parsing
622       fChildParser=NULL; // handle next element myself
623       if (aIsDelegated) {
624         // - we were delegated to process a single element from another element.
625         //   So, we nust not continue parsing here, but pass back to parent
626         //   for next element
627         return true;
628       }
629       // otherwise, wait here for next element to start (or encosing element to end)
630       fParseMode=pamo_element; // expect another element or end of myself
631     }
632     else {
633       // child is still parsing, so am I
634       return false;
635     }
636   }
637   else {
638     CONSDBGPRINTF((
639       "'%s' ends in configElement='%s' with parsemode='%s' nest=%hd, aIsDelegated=%d",
640       aElementName,
641       getName(),
642       ParseModeNames[fParseMode],
643       fNest,
644       aIsDelegated
645     ));
646     #ifdef SYSER_REGISTRATION
647     // Perform CRC sum if locked element
648     if (fLockedElement) {
649       // CRC over CharData
650       getRootElement()->addToLockCRC(fTempString.c_str());
651     }
652     #endif
653     const char *p; // BCPPB: declaring vars in case does not work.
654     size_t n,lnwsp;
655     bool hlp;
656     timecontext_t tctx;
657     // expand macros in string first
658     if (fCfgVarExp==0) {
659       // auto mode
660       fCfgVarExp = fParseMode==pamo_path || fParseMode==pamo_macrostring ? 2 : -1;
661     }
662     // do config variable expansion now (or not, according to fCfgVarExp)
663     getSyncAppBase()->expandConfigVars(fTempString,fCfgVarExp,this,aElementName);
664     // now parse
665     switch (fParseMode) {
666       case pamo_end:
667       case pamo_endnested:
668         if (fNest>0) {
669           // Note: normal empty elements are NOT considered nested elements by default, only if
670           //       they request endnested mode explicitly
671                 if (fParseMode==pamo_endnested)
672                   nestedElementEnd(); // inform possible parser of nested element that a nested element ends here
673           // back to nested
674                 fParseMode=pamo_nested;
675         }
676         else fParseMode=pamo_element; // back to normal element parsing
677         return false; // do not exit
678       case pamo_all:
679       case pamo_nested:
680         if (fNest>0) {
681                 // not yet finished with startlevel, continue with pamo_all/pamo_nested
682           fNest--;
683           if (fExpectAllNestStart>0) {
684                 // we are in expectAll mode within pamo_nested
685             if(fNest==fExpectAllNestStart) {
686               // reached level where we started ignoring contents before
687               fExpectAllNestStart = -1; // processed now
688               fParseMode = pamo_nested; // back to nested (but active) parsing, like in Mime-Dir profile
689                 }
690             else {
691                 // NOP here - do NOT call nestedElementEnd()
692             }
693           }
694           else {
695                 // end of active nested element
696             nestedElementEnd(); // inform possible parser of nested element
697             if (fNest==0) {
698               // if back on nest level 0, switch to pamo_element
699               fParseMode = pamo_element;
700             }
701           }
702           return false; // stay in this element
703         }
704         // nested or all at level 0 cause handling like pamo_element
705       case pamo_element:
706         // end of element while looking for elements:
707         // this is end of this config element itself
708         // - flag completion of this element
709         fCompleted=true; // prevents re-entry
710         // Resolve if this is element says it is self-contained (no references to other elements
711         // that might follow later in the config file)
712         if (fResolveImmediately) {
713           // resolve element NOW, last pass
714           localResolve(true);
715         }
716         // - return parsing authority to caller
717         return true;
718       case pamo_cstring:
719         // interpret C-type escapes (only \t,\r,\n and \xXX, no octal)
720         #ifdef SYSER_REGISTRATION
721         if (fHadLockedSubs && !fResultPtr.fStringP->empty())
722           ReportError(true,"Duplicate definition of <%s>",aElementName);
723         #endif
724         fResultPtr.fStringP->erase();
725         CStrToStrAppend(fTempString.c_str(), *(fResultPtr.fStringP));
726         break;
727       case pamo_string:
728       case pamo_macrostring:
729       case pamo_path:
730         #ifdef SYSER_REGISTRATION
731         if (fHadLockedSubs && !fResultPtr.fStringP->empty())
732           ReportError(true,"Duplicate definition of <%s>",aElementName);
733         #endif
734         // remove spaces at beginning and end
735         fResultPtr.fStringP->erase();
736         p = fTempString.c_str();
737         // - skip leading spaces
738         while (*p && isspace(*p)) ++p;
739         // - copy chars and convert all wsp to spaces
740         n=0; lnwsp=0;
741         while (*p) {
742           ++n; // count char
743           if (isspace(*p))
744             fResultPtr.fStringP->append(" ");
745           else {
746             fResultPtr.fStringP->append(p,1);
747             lnwsp=n; // remember last non-whitespace
748           }
749           ++p;
750         }
751         // - remove trailing spaces
752         fResultPtr.fStringP->resize(lnwsp);
753         // - if path requested, shape it up if needed
754         if (fParseMode==pamo_path) {
755           makeOSDirPath(*(fResultPtr.fStringP));
756         }
757         break;
758       case pamo_rawstring:
759         #ifdef SYSER_REGISTRATION
760         if (fHadLockedSubs && !fResultPtr.fStringP->empty())
761           ReportError(true,"Duplicate definition of <%s>",aElementName);
762         #endif
763         (*(fResultPtr.fStringP))=fTempString;
764         break;
765       case pamo_field:
766         #ifdef SCRIPT_SUPPORT
767         *(fResultPtr.fShortP) = getFieldIndex(fTempString.c_str(),fFieldListP,fScriptContextP);
768         #else
769         *(fResultPtr.fShortP) = getFieldIndex(fTempString.c_str(),fFieldListP);
770         #endif
771         break;
772       #ifdef SCRIPT_SUPPORT
773       case pamo_script:
774       case pamo_functiondef:
775         SYSYNC_TRY {
776           if (fParseMode==pamo_functiondef)
777             TScriptContext::TokenizeAndResolveFunction(getSyncAppBase(),fExpectLine,fTempString.c_str(),(*(fResultPtr.fFuncDefP)));
778           else {
779             #ifdef SYSER_REGISTRATION
780             if (fHadLockedSubs && !fResultPtr.fStringP->empty())
781               ReportError(true,"Duplicate definition of <%s>",aElementName);
782             #endif
783             TScriptContext::Tokenize(getSyncAppBase(),aElementName,fExpectLine,fTempString.c_str(),(*(fResultPtr.fStringP)),fExpectContextFuncs,false,fExpectNoDeclarations);
784           }
785         }
786         SYSYNC_CATCH (exception &e)
787           ReportError(true,"Script Error: %s",e.what());
788         SYSYNC_ENDCATCH
789         break;
790       #endif
791       case pamo_timestamp:
792         // expect timestamp, store as UTC
793         if (ISO8601StrToTimestamp(fTempString.c_str(), *(fResultPtr.fTimestampP), tctx)==0)
794           ReportError(false,"bad ISO8601 timestamp value");
795         if (TCTX_IS_UNKNOWN(tctx)) tctx=TCTX_SYSTEM;
796         TzConvertTimestamp(*(fResultPtr.fTimestampP),tctx,TCTX_UTC,getSyncAppBase()->getAppZones());
797         break;
798       case pamo_timezone:
799         // time zone by name
800         if (!TimeZoneNameToContext(fTempString.c_str(), *(fResultPtr.fTimeContextP), getSyncAppBase()->getAppZones()))
801           ReportError(false,"invalid/unknown timezone name");
802         break;
803       case pamo_vtimezone:
804         // definition of custom time zone in vTimezone format
805         if (!VTIMEZONEtoInternal(fTempString.c_str(), tctx, fResultPtr.fGZonesP, getDbgLogger()))
806           ReportError(false,"invalid vTimezone defintion");
807         break;
808       case pamo_boolean:
809         if (!StrToBool(fTempString.c_str(),*(fResultPtr.fBoolP)))
810           ReportError(false,"bad boolean value");
811         break;
812       case pamo_tristate:
813         if (fTempString.empty() || fTempString=="unspecified" || fTempString=="default")
814           *(fResultPtr.fByteP)=-1; // unspecified
815         else {
816           if (!StrToBool(fTempString.c_str(),hlp))
817             ReportError(false,"bad boolean value");
818           else
819             *(fResultPtr.fByteP)= hlp ? 1 : 0;
820         }
821         break;
822       case pamo_int64:
823         if (!StrToLongLong(fTempString.c_str(),*(fResultPtr.fLongLongP)))
824           ReportError(false,"bad integer (64bit) value");
825         break;
826       case pamo_int32:
827         if (!StrToLong(fTempString.c_str(),*(fResultPtr.fLongP)))
828           ReportError(false,"bad integer (32bit) value");
829         break;
830       case pamo_char:
831         if (fTempString.size()>1)
832           ReportError(false,"single char or nothing (=NUL char) expected");
833         if (fTempString.size()==0)
834               *(fResultPtr.fCharP) = 0;
835         else    
836           *(fResultPtr.fCharP) = fTempString[0];
837         break;
838       case pamo_idCode:
839         // Palm/MacOS-type 4-char code
840         if (fTempString.size()!=4)
841           ReportError(false,"id code must be exactly 4 characters");
842         *(fResultPtr.fLongP) =
843           ((uInt32)fTempString[0] << 24) +
844           ((uInt32)fTempString[1] << 16) +
845           ((uInt16)fTempString[2] << 8) +
846           ((uInt8)fTempString[3]);
847         break;
848       case pamo_int16:
849         if (!StrToShort(fTempString.c_str(),*(fResultPtr.fShortP)))
850           ReportError(false,"bad integer (16bit) value");
851         break;
852       case pamo_enum1by:
853       case pamo_enum2by:
854       case pamo_enum4by:
855         sInt16 tempenum;
856         if (!StrToEnum(fParseEnumArray,fParseEnumNum,tempenum,fTempString.c_str()))
857           ReportError(false,"bad enumeration value '%s'",fTempString.c_str());
858         else {
859           // now assign enum
860           if (fParseMode==pamo_enum1by) *fResultPtr.fCharP = tempenum;
861           else if (fParseMode==pamo_enum2by) *fResultPtr.fShortP = tempenum;
862           else if (fParseMode==pamo_enum4by) *fResultPtr.fLongP = tempenum;
863         }
864         break;
865       default:
866         SYSYNC_THROW(TConfigParseException(DEBUGTEXT("Unknown parse mode","ce1")));
867     }
868     // normal case: end of simple element parsed at same level as parent
869     if (aIsDelegated) {
870       // - we were delegated to process a single element from another element.
871       //   So, we nust not continue parsing here, but pass back to parent
872       //   for next element
873       return true;
874     }
875     else {
876       // - expect next element
877       fParseMode=pamo_element;
878     }
879   }
880   // end of embedded element, but not of myself
881   #ifdef SYSER_REGISTRATION
882   #ifdef CONSDEBUG
883   if (fLockedElement) {
884     CONSDBGPRINTF((
885       "'%s' was locked and was included in LockCRC = 0x%04hX",
886       aElementName,
887       getRootElement()->getConfigLockCRC()
888     ));
889   }
890   #endif
891   // - back to parent's lock status (or false if no parent)
892   fLockedElement=getParentElement() ? getParentElement()->fLockedElement : false;
893   #endif
894   // do not return to parent config element
895   return false;
896 } // TConfigElement::endElement
897
898
899
900 // reset error
901 void TRootConfigElement::resetError(void)
902 {
903   fError=false;
904   fErrorMessage.erase();
905 } // TRootConfigElement::ResetError
906
907
908 // set error
909 void TRootConfigElement::setError(bool aFatal, const char *aMsg)
910 {
911   if (!fErrorMessage.empty())
912     fErrorMessage += '\n'; // multiple messages on multiple lines
913   fErrorMessage.append(aMsg);
914   if (aFatal && fFatalError==LOCERR_OK)
915     fFatalError=LOCERR_CFGPARSE; // this is a config parse error
916   fError=true;
917 } // TRootConfigElement::setError
918
919
920 // check for error message
921 const char *TRootConfigElement::getErrorMsg(void)
922 {
923   if (!fError) return NULL;
924   return fErrorMessage.c_str();
925 } // TRootConfigElement::GetErrorMsg
926
927
928 // reset parsing (=reset error and fatal errors)
929 void TRootConfigElement::ResetParsing(void)
930 {
931   resetError();
932   fDocStarted=false;
933   fFatalError=LOCERR_OK;
934   #ifdef SYSER_REGISTRATION
935   fLockCRC=0; // no CRC lock sum yet
936   #endif
937   TConfigElement::ResetParsing();
938 } // TRootConfigElement::ResetParsing
939
940
941 #endif
942
943 // resolve config tree and catch errors
944 bool TRootConfigElement::ResolveAll(void)
945 {
946   SYSYNC_TRY {
947     Resolve(true);
948     return true;
949   }
950   SYSYNC_CATCH (TConfigParseException &e)
951     #ifndef HARDCODED_CONFIG
952     ReportError(true,e.what());
953     #endif
954     return false;
955   SYSYNC_ENDCATCH
956 } // TRootConfigElement::ResolveAll
957
958
959 #ifndef HARDCODED_CONFIG
960
961 // start of element, this config element decides who processes this element
962 bool TRootConfigElement::startElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
963 {
964   if (!fDocStarted) {
965     // document not yet started
966     if (strucmp(aElementName,XMLCONFIG_DOCNAME)==0) {
967       // check version
968       const char* vers = getAttr(aAttributes,"version");
969       if (!vers) {
970         ReportError(true,"Missing version attribute for document");
971         expectAll(); // ignore everything
972       }
973       else if (strucmp(vers,XMLCONFIG_DOCVERSION)!=0) {
974         ReportError(true,
975           "Bad config document version (expected %s, found %s)",
976           XMLCONFIG_DOCVERSION,
977           vers
978         );
979         expectAll(); // ignore everything inside that element
980       }
981       fDocStarted=true; // started normally
982       return true;
983     }
984     else {
985       // invalid element
986       return false;
987     }
988   }
989   else {
990     return TConfigElement::startElement(aElementName,aAttributes,aLine);
991   }
992 } // TRootConfigElement::startElement
993
994
995 #ifdef SYSER_REGISTRATION
996 // add config text to locking CRC
997 void TRootConfigElement::addToLockCRC(const char *aCharData, size_t aNumChars)
998 {
999   size_t n = aNumChars ? aNumChars : strlen(aCharData);
1000   char c;
1001   bool lastwasctrl=false;
1002   while (n--) {
1003     c=*aCharData++;
1004     // compact multiple whitespace to single char
1005     if ((uInt8)c<=' ') {
1006       if (lastwasctrl) continue;
1007       lastwasctrl=true;
1008       // treat them all as spaces
1009       c=' ';
1010     }
1011     else {
1012       lastwasctrl=false; // this is a non-space
1013     }
1014     // add to CRC
1015     fLockCRC=sysync::sysync_crc16(fLockCRC,(uInt8)c);
1016   }
1017 } // TRootConfigElement::addToLockCRC
1018 #endif
1019
1020 #endif // no hardcode config
1021
1022
1023 /* end of TConfigElement implementation */
1024
1025 // eof