]> err.no Git - sope/blob - sope-appserver/NGObjWeb/Templates/WODParser.m
fixed some NGMail framework build issue
[sope] / sope-appserver / NGObjWeb / Templates / WODParser.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "WODParser.h"
23 #include "common.h"
24
25 @implementation WODParser
26
27 static Class    StrClass    = Nil;
28 static Class    DictClass   = Nil;
29 static Class    NumberClass = Nil;
30 static NSNumber *yesNum     = nil;
31 static NSNumber *noNum      = nil;
32 static BOOL     useUTF8     = NO;
33
34 + (void)initialize {
35   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
36
37   StrClass    = [NSString class];
38   DictClass   = [NSMutableDictionary class];
39   NumberClass = [NSNumber      class];
40   
41   if (yesNum == nil) yesNum = [[NumberClass numberWithBool:YES] retain];
42   if (noNum  == nil) noNum  = [[NumberClass numberWithBool:NO]  retain];
43   
44   useUTF8 = [ud boolForKey:@"WOParsersUseUTF8"];
45 }
46
47 - (id)initWithHandler:(id<WODParserHandler,NSObject>)_handler {
48   ASSIGN(self->callback, _handler);
49   return self;
50 }
51 - (void)dealloc {
52   [self->callback release];
53   [super dealloc];
54 }
55
56 /* callbacks */
57
58 - (id)associationWithValue:(id)_value {
59   return [self->callback parser:self makeAssociationWithValue:_value];
60 }
61 - (id)associationWithKeyPath:(NSString *)_keyPath {
62   return [self->callback parser:self makeAssociationWithKeyPath:_keyPath];
63 }
64
65 - (id)elementDefinitionForComponent:(NSString *)_cname
66   associations:(id)_entry
67   elementName:(NSString *)_elemName
68 {
69   return [self->callback parser:self
70                          makeDefinitionForComponentNamed:_cname
71                          associations:_entry
72                          elementName:_elemName];
73 }
74
75 /* parser */
76
77 static id _parseProperty(NSZone *_zone, const unichar *_buffer, unsigned *_idx,
78                          unsigned _len, NSException **_exception,
79                          BOOL _allowAssoc, id self);
80 static id _parseWodEntry(NSZone *_zone, const unichar *_buffer, unsigned *_idx,
81                          unsigned _len, NSException **_exception,
82                          NSString **_name, NSString **_class,
83                          id self);
84
85 - (NSException *)parseDefinitionsFromBuffer:(const unichar *)_buffer
86   length:(unsigned)_len
87   mappings:(NSMutableDictionary *)_mappings
88 {
89   NSException *exception     = nil;
90   NSString    *elementName   = nil;
91   NSString    *componentName = nil;
92   unsigned    idx            = 0;
93   id          entry          = nil;
94   
95   [_mappings removeAllObjects];
96   
97   while ((entry = _parseWodEntry(NULL, _buffer, &idx, _len, &exception,
98                                  &elementName, &componentName,
99                                  self)) != NULL) {
100     id def;
101     
102     if (exception) {
103       [entry         release]; entry         = nil;
104       [elementName   release]; elementName   = nil;
105       [componentName release]; componentName = nil;
106       break;
107     }
108
109     if ([_mappings objectForKey:elementName] != nil)
110       [self warnWithFormat:@"duplicate definition of element %@ !",
111               elementName];
112
113     def = [self elementDefinitionForComponent:componentName
114                 associations:entry
115                 elementName:elementName];
116     
117     [componentName release]; componentName = nil;
118     [entry         release]; entry         = nil;
119
120     if ((def != nil) && (elementName != nil))
121       [_mappings setObject:def forKey:elementName];
122 #if 0
123     NSLog(@"defined element %@ definition=%@", elementName, def);
124 #endif
125     [elementName release]; elementName = nil;
126   }
127
128   return exception;
129 }
130
131 /* parsing */
132
133 - (NSStringEncoding)stringEncodingForData:(NSData *)_data  {
134   // TODO: we could check for UTF-16 marker in front of data
135   return useUTF8 ? NSUTF8StringEncoding : [NSString defaultCStringEncoding];
136 }
137
138 - (NSDictionary *)parseDeclarationData:(NSData *)_decl {
139   NSMutableDictionary *defs;
140   NSException  *ex;
141   NSString     *s;
142   unichar      *buf;
143   unsigned int bufLen;
144   
145   if (![self->callback parser:self willParseDeclarationData:_decl])
146     return nil;
147   
148   /* recode buffer using NSString */
149   
150   s = [[NSString alloc] initWithData:_decl 
151                         encoding:[self stringEncodingForData:_decl]];
152   bufLen = [s length];
153   buf = calloc(bufLen + 2, sizeof(unichar));
154   [s getCharacters:buf];
155   [s release]; s = nil;
156   buf[bufLen] = 0; /* null-terminate buffer, parser might need that */
157   
158   /* start parsing */
159   
160   defs = [NSMutableDictionary dictionaryWithCapacity:100];
161   
162   ex = [self parseDefinitionsFromBuffer:buf length:bufLen mappings:defs];
163   
164   if (buf != NULL) free(buf); buf = NULL;
165
166   /* report results */
167   
168   if (ex != nil) {
169     [self->callback parser:self failedParsingDeclarationData:_decl 
170                     exception:ex];
171   }
172   else {
173     [self->callback parser:self finishedParsingDeclarationData:_decl
174                     declarations:defs];
175   }
176   
177   return defs;
178 }
179
180
181 static int _numberOfLines(const unichar *_buffer, unsigned _lastIdx) {
182   register unsigned pos, lineCount = 1;
183   
184   for (pos = 0; (pos < _lastIdx) && (_buffer[pos] != '\0'); pos++) {
185     if (_buffer[pos] == '\n')
186       lineCount++;
187   }
188   return lineCount;
189 }
190
191 static inline BOOL _isBreakChar(const unichar _c) {
192   switch (_c) {
193     case ' ': case '\t': case '\n': case '\r':
194     case '=':  case ';':  case ',':
195     case '{': case '(':  case '"':  case '<':
196     case '.': case ':':
197     case ')': case '}':
198       return YES;
199
200     default:
201       return NO;
202   }
203 }
204 static inline BOOL _isIdChar(const unichar _c) {
205   return (_isBreakChar(_c) && (_c != '.')) ? NO : YES;
206 }
207
208 static inline int _valueOfHexChar(const unichar _c) {
209   switch (_c) {
210     case '0': case '1': case '2': case '3': case '4':
211     case '5': case '6': case '7': case '8': case '9':
212       return (_c - 48); // 0-9 (ascii-char)'0' - 48 => (int)0
213       
214     case 'A': case 'B': case 'C':
215     case 'D': case 'E': case 'F':
216       return (_c - 55); // A-F, A=10..F=15, 'A'=65..'F'=70
217       
218     case 'a': case 'b': case 'c':
219     case 'd': case 'e': case 'f':
220       return (_c - 87); // a-f, a=10..F=15, 'a'=97..'f'=102
221
222     default:
223       return -1;
224   }
225 }
226 static inline BOOL _isHexDigit(const unichar _c) {
227   switch (_c) {
228     case '0': case '1': case '2': case '3': case '4':
229     case '5': case '6': case '7': case '8': case '9':
230     case 'A': case 'B': case 'C':
231     case 'D': case 'E': case 'F':
232     case 'a': case 'b': case 'c':
233     case 'd': case 'e': case 'f':
234       return YES;
235
236     default:
237       return NO;
238   }
239 }
240
241 static NSException *_makeException(NSException *_exception,
242                                    const unichar *_buffer, unsigned _idx,
243                                    unsigned _len, NSString *_text)
244 {
245   NSMutableDictionary *ui = nil;
246   NSException *exception = nil;
247   int         numLines;
248   BOOL        atEof;
249   
250   numLines   = _numberOfLines(_buffer, _idx);
251   atEof      = (_idx >= _len) ? YES : NO;
252   
253   if (_exception)
254     // error resulted from a previous error (exception already set)
255     return _exception;
256
257   if (atEof)
258     _text = [@"Unexpected end: " stringByAppendingString:[_text stringValue]];
259   else {
260     _text = [StrClass stringWithFormat:@"Syntax error in line %i: %@",
261                         numLines, _text];
262   }
263
264   // user info
265   ui = [[exception userInfo] mutableCopy];
266   if (ui == nil)
267     ui = [[DictClass alloc] initWithCapacity:8];
268
269   [ui setObject:[NumberClass numberWithInt:numLines] forKey:@"line"];
270   [ui setObject:[NumberClass numberWithInt:_len]     forKey:@"size"];
271   [ui setObject:[NumberClass numberWithInt:_idx]     forKey:@"position"];
272
273   if (!atEof && (_idx > 0)) {
274     register unsigned pos;
275     const unichar *startPos, *endPos;
276
277     for (pos = _idx; (pos >= 0) && (_buffer[pos] != '\n'); pos--)
278       ;
279     startPos = &(_buffer[pos + 1]);
280
281     for (pos = _idx; ((pos < _len) && (_buffer[pos] != '\n')); pos++)
282       ;
283     endPos = &(_buffer[pos - 1]);
284     
285     if (startPos < endPos) {
286       NSString *ll;
287       
288       ll = [[StrClass alloc] initWithCharacters:startPos
289                              length:endPos - startPos];
290       [ui setObject:ll forKey:@"lastLine"];
291       [ll release];
292     }
293     else {
294       NSLog(@"%s: startPos=0x%08X endPos=0x%08X", __PRETTY_FUNCTION__,
295             startPos, endPos);
296     }
297   }
298
299   exception = [NSException exceptionWithName:@"SyntaxError"
300                            reason:_text
301                            userInfo:ui];
302   [ui release]; ui = nil;
303
304   return exception;
305 }
306
307 static BOOL _skipComments(const unichar *_buffer, 
308                           unsigned *_idx, unsigned _len,
309                           NSException **_exception)
310 {
311   register unsigned pos = *_idx;
312   BOOL lookAgain;
313
314   if (pos >= _len)
315     return NO;
316
317   //NSLog(@"start at '%c' (%i)", _buffer[pos], pos);
318   
319   do { // until all comments are filtered ..
320     lookAgain = NO;
321     
322     if ((_buffer[pos] == '/') && (pos + 1 < _len)) {
323       if (_buffer[pos + 1] == '/') { // single line comments
324         pos += 2; // skip '//'
325
326         // search for '\n' ..
327         while ((pos < _len) && (_buffer[pos] != '\n'))
328           pos++;
329
330         if ((pos < _len) && (_buffer[pos] == '\n')) {
331           pos++; // skip newline, otherwise EOF was reached
332           lookAgain = YES;
333         }
334       }
335       else if (_buffer[pos + 1] == '*') { /* multiline comments */
336         BOOL commentIsClosed = NO;
337       
338         pos += 2; // skip '/*'
339
340         do { // search for '*/'
341           while ((pos < _len) && (_buffer[pos] != '*'))
342             pos++;
343
344           if (pos < _len) { // found '*'
345             if ((pos + 1) < _len) {
346               if (_buffer[pos + 1] == '/') { // found '*/'
347                 commentIsClosed = YES;
348                 pos += 2; // skip '*/'
349                 lookAgain = YES;
350                 break; // leave loop
351               }
352             }
353           }
354         }
355         while (pos < _len);
356
357         if (!commentIsClosed) {
358           // EOF found, comment wasn't closed
359           *_exception =
360             _makeException(*_exception, _buffer, *_idx, _len,
361                            @"comment was not closed (expected '*/')");
362           return NO;
363         }
364       }
365     }
366     else if (isspace((int)_buffer[pos])) {
367       pos++;
368       lookAgain = YES;
369     }
370   }
371   while (lookAgain && (pos < _len));
372   
373   // store position ..
374   *_idx = pos;
375   //NSLog(@"end at '%c' (%i)", _buffer[pos], pos);
376
377   return (pos < _len);
378 }
379
380 static NSString *_parseIdentifier(NSZone *_zone,
381                                   const unichar *_buffer, unsigned *_idx,
382                                   unsigned _len, NSException **_exception)
383 {
384   register unsigned pos = *_idx;
385   register unsigned len = 0;
386   unsigned startPos = pos;
387
388   // skip comments and spaces
389   if (!_skipComments(_buffer, _idx, _len, _exception)) {
390     // EOF reached during comment-skipping
391     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
392                         @"did not find an id (expected 'a-zA-Z0-9') !");
393     return nil;
394   }
395   
396   // loop until break char
397   while (_isIdChar(_buffer[pos]) && (pos < _len)) {
398     pos++;
399     len++;
400   }
401
402   if (len == 0) { // wasn't a string ..
403     *_exception =
404       _makeException(*_exception, _buffer, *_idx, _len,
405                      @"did not find an id (expected 'a-zA-Z0-9') !");
406     return nil;
407   }
408   else {
409     *_idx = pos;
410     return [[StrClass alloc] initWithCharacters:&(_buffer[startPos])
411                              length:len];
412   }
413 }
414 static NSString *_parseKeyPath(NSZone *_zone,
415                                const unichar *_buffer, unsigned *_idx,
416                                unsigned _len, NSException **_exception,
417                                id self)
418 {
419   NSMutableString *keypath   = nil;
420   NSString        *component = nil;
421   
422   if (!_skipComments(_buffer, _idx, _len, _exception)) {
423     // EOF reached during comment-skipping
424     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
425                                  @"did not find keypath (expected id)");
426     return nil;
427   }
428
429   component = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
430   if (component == nil) {
431     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
432                                  @"did not find keypath (expected id)");
433     return nil;
434   }
435   if (_buffer[*_idx] != '.') // single id-keypath
436     return component;
437
438   keypath = [[NSMutableString allocWithZone:_zone] init];
439   [keypath appendString:component];
440   
441   while ((_buffer[*_idx] == '.') && (component != nil)) {
442     *_idx += 1; // skip '.'
443     [keypath appendString:@"."];
444
445     [component release]; component = nil;
446     component = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
447
448     if (component == nil) {
449       [keypath release]; keypath = nil;
450       *_exception =
451         _makeException(*_exception, _buffer, *_idx, _len,
452                        @"expected component after '.' in keypath !");
453       break;
454     }
455
456     [keypath appendString:component];
457   }
458   [component release]; component = nil;
459
460   return keypath;
461 }
462
463 static NSString *_parseQString(NSZone *_zone,
464                                const unichar *_buffer, unsigned *_idx,
465                                unsigned _len, NSException **_exception,
466                                id self)
467 {
468   // skip comments and spaces
469   if (!_skipComments(_buffer, _idx, _len, _exception)) {
470     // EOF reached during comment-skipping
471     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
472                         @"did not find a quoted string (expected '\"') !");
473     return nil;
474   }
475
476   if (_buffer[*_idx] != '"') { // it's not a quoted string that's follows
477     *_exception = 
478       _makeException(*_exception, _buffer, *_idx, _len,
479                      @"did not find quoted string (expected '\"')");
480     return nil;
481   }
482   else { // a quoted string
483     register unsigned pos = *_idx;
484     register unsigned len = 0;
485     unsigned startPos = pos + 1;
486     BOOL     containsEscaped = NO;
487     
488     pos++; // skip starting quote
489
490     // loop until closing quote
491     while ((_buffer[pos] != '"') && (pos < _len)) {
492       if (_buffer[pos] == '\\') {
493         containsEscaped = YES;
494         pos++; // skip following char
495         if (pos == _len) {
496           *_exception =
497             _makeException(*_exception, _buffer, *_idx, _len,
498                            @"escape in quoted string not finished !");
499           return nil;
500         }
501       }
502       pos++;
503       len++;
504     }
505
506     if (pos == _len) { // syntax error, quote not closed
507       *_idx = pos;
508       *_exception =
509         _makeException(*_exception, _buffer, *_idx, _len,
510                        @"quoted string not closed (expected '\"')");
511       return nil;
512     }
513
514     pos++;       // skip closing quote
515     *_idx = pos; // store pointer
516     pos = 0;
517     
518     if (len == 0) /* empty string */
519       return @"";
520     
521     if (containsEscaped) {
522       register unsigned pos2;
523       id   ostr = nil;
524       unichar *str;
525       
526       NSCAssert(len > 0, @"invalid length ..");
527       str = calloc(len + 3, sizeof(unichar));
528       
529       for (pos = startPos, pos2 = 0; _buffer[pos] != '"'; pos++, pos2++) {
530         //NSLog(@"char=%c pos=%i pos2=%i", _buffer[pos], pos2);
531         if (_buffer[pos] == '\\') {
532           pos++;
533           switch (_buffer[pos]) {
534             case 'a':  str[pos2] = '\a'; break;
535             case 'b':  str[pos2] = '\b'; break;
536             case 'f':  str[pos2] = '\f'; break;
537             case 'n':  str[pos2] = '\n'; break;
538             case 't':  str[pos2] = '\t'; break;
539             case 'v':  str[pos2] = '\v'; break;
540             case '\\': str[pos2] = '\\'; break;
541             
542             default:
543               str[pos2] = _buffer[pos];
544               break;
545           }
546         }
547         else {
548           str[pos2] = _buffer[pos];
549         }
550       }
551       str[pos2] = 0;
552       NSCAssert(pos2 == len, @"invalid unescape ..");
553       
554       ostr = [[StrClass alloc] initWithCharacters:str length:pos2];
555       if (str != NULL) free(str); str = NULL;
556
557       return ostr;
558     }
559     else {
560       NSCAssert(len > 0, @"invalid length ..");
561       return [[StrClass alloc] initWithCharacters:&(_buffer[startPos]) 
562                                length:len];
563     }
564   }
565 }
566
567 static NSData *_parseData(NSZone *_zone, const unichar *_buffer,
568                           unsigned *_idx, unsigned _len,
569                           NSException **_exception,
570                           id self)
571 {
572   if (!_skipComments(_buffer, _idx, _len, _exception)) {
573     // EOF reached during comment-skipping
574     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
575                                  @"did not find a data (expected '<') !");
576     return nil;
577   }
578
579   if (_buffer[*_idx] != '<') { // it's not a data that's follows
580     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
581                                  @"did not find a data (expected '<') !");
582     return nil;
583   }
584   else {
585     register      unsigned pos = *_idx + 1;
586     register      unsigned len = 0;
587     unsigned      endPos = 0;
588     NSMutableData *data  = nil;
589     
590     *_idx += 1; // skip '<'
591
592     if (!_skipComments(_buffer, _idx, _len, _exception)) {
593       *_exception = _makeException(*_exception, _buffer, *_idx, _len,
594                                    @"data was not closed (expected '>') ..");
595       return nil; // EOF
596     }
597
598     if (_buffer[*_idx] == '>') { // empty data
599       *_idx += 1; // skip '>'
600       return [[NSData allocWithZone:_zone] init];
601     }
602
603     // count significant chars
604     while ((_buffer[pos] != '>') && (pos < _len)) {
605       if ((_buffer[pos] == ' ') || (_buffer[pos] == '\t'))
606         ;
607       else if (_isHexDigit(_buffer[pos]))
608         len++;
609       else {
610         *_idx = pos;
611         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
612                                      @"invalid char in data property");
613         return nil;
614       }
615       pos++;
616     }
617     if (pos == _len) {
618       *_idx = pos;
619       *_exception = _makeException(*_exception, _buffer, *_idx, _len,
620                                    @"data was not closed (expected '>')");
621       return nil; // EOF
622     }
623     endPos = pos; // store position of closing '>'
624
625     // if odd, then add one byte for trailing nibble
626     len = (len % 2 == 1) ? len / 2 + 1 : len / 2;
627     data = [[NSMutableData allocWithZone:_zone] initWithLength:len];
628
629     /* now copy bytes ... */
630     {
631       register unsigned i;
632       register int pending = -1;
633       char *buf = [data mutableBytes];
634       
635       for (pos = *_idx, i = 0; (pos < endPos) && (i < len); pos++) {
636         int value = _valueOfHexChar(_buffer[pos]);
637
638         if (value != -1) {
639           if (pending == -1)
640             pending = value;
641           else {
642             value = value * 16 + pending;
643             pending = -1;
644
645             buf[i] = value;
646             i++;
647           }
648         }
649       }
650       if (pending != -1) { // was odd, now add the trailer ..
651         NSCAssert(i < len, @"invalid length ..");
652         buf[i] = pending * 16;
653       }
654     }
655     
656     // update global position
657     *_idx = endPos + 1; // endPos + 1 (*endPos == '>', 1 => skips '>')
658
659     return data;
660   }
661 }
662
663 static NSDictionary *_parseDict(NSZone *_zone,
664                                 const unichar *_buffer, unsigned *_idx,
665                                 unsigned _len, NSException **_exception,
666                                 id self)
667 {
668   if (!_skipComments(_buffer, _idx, _len, _exception)) {
669     // EOF reached during comment-skipping
670     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
671                                  @"did not find dictionary (expected '{')");
672     return nil;
673   }
674   
675   if (_buffer[*_idx] != '{') { // it's not a dict that's follows
676     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
677                                  @"did not find dictionary (expected '{')");
678     return nil;
679   }
680   else {
681     NSMutableDictionary *result = nil;
682     id   key     = nil;
683     id   value   = nil;
684     BOOL didFail = NO;
685     
686     *_idx += 1; // skip '{'
687
688     if (!_skipComments(_buffer, _idx, _len, _exception)) {
689       *_exception = _makeException(*_exception, _buffer, *_idx, _len,
690                                    @"dictionary was not closed (expected '}')");
691       return nil; // EOF
692     }
693
694     if (_buffer[*_idx] == '}') { // an empty dictionary
695       *_idx += 1; // skip the '}'
696       return [[DictClass allocWithZone:_zone] init];
697     }
698
699     result = [[DictClass allocWithZone:_zone] init];
700     do {
701       key   = nil;
702       value = nil;
703       
704       if (!_skipComments(_buffer, _idx, _len, _exception)) {
705         *_exception =
706           _makeException(*_exception, _buffer, *_idx, _len,
707                          @"dictionary was not closed (expected '}')");
708         didFail = YES;
709         break; // unexpected EOF
710       }
711
712       if (_buffer[*_idx] == '}') { // dictionary closed
713         *_idx += 1; // skip the '}'
714         break;
715       }
716       
717       // read key property
718       key = _parseProperty(_zone, _buffer, _idx, _len, _exception, NO, self);
719       if (key == nil) { // syntax error
720         if (*_exception == nil) {
721           *_exception = _makeException(*_exception, _buffer, *_idx, _len,
722                                        @"got nil-key in dictionary ..");
723         }
724         didFail = YES;
725         break;
726       }
727
728       /* The following parses:  (comment|space)* '=' (comment|space)* */
729       if (!_skipComments(_buffer, _idx, _len, _exception)) {
730         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
731                                      @"expected '=' after key in dictionary");
732         didFail = YES;
733         break; // unexpected EOF
734       }
735       // no we need a '=' assignment
736       if (_buffer[*_idx] != '=') {
737         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
738                                      @"expected '=' after key in dictionary");
739         didFail = YES;
740         break;
741       }
742       *_idx += 1; // skip '='
743       if (!_skipComments(_buffer, _idx, _len, _exception)) {
744         *_exception =
745           _makeException(*_exception, _buffer, *_idx, _len,
746                          @"expected value after key '=' in dictionary");
747         didFail = YES;
748         break; // unexpected EOF
749       }
750
751       // read value property
752       value = _parseProperty(_zone, _buffer, _idx, _len, _exception, NO, self);
753 #if 1
754       if (*_exception) {
755         didFail = YES;
756         break;
757       }
758 #else
759       if (value == nil) { // syntax error
760         if (*_exception == nil) {
761           *_exception = _makeException(*_exception, _buffer, *_idx, _len,
762                                        @"got nil-value in dictionary");
763         }
764         didFail = YES;
765         break;
766       }
767 #endif
768       
769       if ((key != nil) && (value != nil))
770         [result setObject:value forKey:key];
771       
772       // release key and value
773       RELEASE(key);   key   = nil;
774       RELEASE(value); value = nil;
775
776       // read trailing ';' if available
777       if (!_skipComments(_buffer, _idx, _len, _exception)) {
778         *_exception =
779           _makeException(*_exception, _buffer, *_idx, _len,
780                          @"dictionary was not closed (expected '}')");
781         didFail = YES;
782         break; // unexpected EOF
783       }
784       if (_buffer[*_idx] == ';') {
785         *_idx += 1; // skip ';'
786       }
787       else { // no ';' at end of pair, only allowed at end of dictionary
788         if (!_skipComments(_buffer, _idx, _len, _exception)) {
789           *_exception =
790             _makeException(*_exception, _buffer, *_idx, _len,
791                            @"dictionary was not closed (expected '}')");
792           didFail = YES;
793           break; // unexpected EOF
794         }
795
796         if (_buffer[*_idx] != '}') { // dictionary wasn't closed
797           *_exception =
798             _makeException(*_exception, _buffer, *_idx, _len,
799                            @"key-value pair without ';' at the end");
800           didFail = YES;
801           break;
802         }
803       }
804     }
805     while ((*_idx < _len) && (result != nil) && !didFail);
806
807     RELEASE(key);    key    = nil;
808     RELEASE(value);  value  = nil;
809     if (didFail) {
810       [result release]; result = nil;
811       return nil;
812     }
813     else
814       return result;
815   }
816 }
817
818 static NSArray *_parseArray(NSZone *_zone, const unichar *_buffer, unsigned *_idx,
819                             unsigned _len, NSException **_exception,
820                             id self)
821 {
822   if (!_skipComments(_buffer, _idx, _len, _exception)) {
823     // EOF reached during comment-skipping
824     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
825                                  @"did not find array (expected '(')");
826     return nil;
827   }
828
829   if (_buffer[*_idx] != '(') { // it's not an array that's follows
830     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
831                                  @"did not find array (expected '(')");
832     return nil;
833   }
834   else {
835     NSMutableArray *result = nil;
836     id element = nil;
837
838     *_idx += 1; // skip '('
839
840     if (!_skipComments(_buffer, _idx, _len, _exception)) {
841       *_exception = _makeException(*_exception, _buffer, *_idx, _len,
842                                    @"array was not closed (expected ')')");
843       return nil; // EOF
844     }
845
846     if (_buffer[*_idx] == ')') { // an empty array
847       *_idx += 1; // skip the ')'
848       return [[NSArray allocWithZone:_zone] init];
849     }
850     
851     result = [[NSMutableArray allocWithZone:_zone] init];
852     do {
853       element = _parseProperty(_zone, _buffer, _idx, _len, _exception, NO, self);
854       if (element == nil) {
855         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
856                                      @"expected element in array");
857         [result release]; result = nil;
858         break;
859       }
860       [result addObject:element];
861       [element release]; element = nil;
862       
863       if (!_skipComments(_buffer, _idx, _len, _exception)) {
864         *_exception =
865           _makeException(*_exception, _buffer, *_idx, _len,
866                          @"array was not closed (expected ')' or ',')");
867         [result release];
868         result = nil;
869         break;
870       }
871       
872       if (_buffer[*_idx] == ')') { // closed array
873         *_idx += 1; // skip ')'
874         break;
875       }
876       else if (_buffer[*_idx] == ',') { // next element
877         *_idx += 1; // skip ','
878         
879         if (!_skipComments(_buffer, _idx, _len, _exception)) {
880           *_exception = _makeException(*_exception, _buffer, *_idx, _len,
881                                        @"array was not closed (expected ')')");
882           [result release];
883           result = nil;
884           break;
885         }
886         if (_buffer[*_idx] == ')') { // closed array, like this '(1,2,)'
887           *_idx += 1; // skip ')'
888           break;
889         }
890       }
891       else { // syntax error
892         *_exception =
893           _makeException(*_exception, _buffer, *_idx, _len,
894                          @"expected ')' or ',' after array element");
895         [result release]; result = nil;
896         break;
897       }
898     }
899     while ((*_idx < _len) && (result != nil));
900     
901     [element release]; element = nil;
902
903     return result;
904   }
905 }
906
907 static NSNumber *_parseDigitPath(NSString *digitPath) {
908   NSRange  r;
909
910   r = [digitPath rangeOfString:@"."];
911   return r.length > 0 
912     ? [NumberClass numberWithDouble:[digitPath doubleValue]]
913     : [NumberClass numberWithInt:[digitPath intValue]];
914 }
915
916 static BOOL _ucIsEqual(const unichar *s, char *tok, unsigned len) {
917   switch (len) {
918   case 0: return NO;
919   case 1: return (s[0] == tok[0]) ? YES : NO;
920   case 2:
921     if (s[0] != tok[0] || s[0] == 0) return NO;
922     if (s[1] != tok[1]) return NO;
923     return YES;
924   case 3:
925     if (s[0] != tok[0] || s[0] == 0) return NO;
926     if (s[1] != tok[1] || s[1] == 0) return NO;
927     if (s[2] != tok[2]) return NO;
928     return YES;
929   default: {
930     register unsigned int i;
931     
932     for (i = 0; i < len; i++) {
933       if (s[i] != tok[i] || s[i] == 0) return NO;
934     }
935     return YES;
936   }
937   }
938   return NO;
939 }
940
941 static id _parseProperty(NSZone *_zone, const unichar *_buffer, unsigned *_idx,
942                          unsigned _len,
943                          NSException **_exception, BOOL _allowAssoc,
944                          id self)
945 {
946   BOOL valueProperty = YES;
947   id   result = nil;
948   
949   if (!_skipComments(_buffer, _idx, _len, _exception))
950     return nil; // EOF
951
952   switch (_buffer[*_idx]) {
953     case '"': // quoted string
954       NSCAssert(result == nil, @"result is already set ..");
955       result = _parseQString(_zone, _buffer, _idx, _len, _exception, self);
956       break;
957
958     case '{': // dictionary
959       NSCAssert(result == nil, @"result is already set ..");
960       result = _parseDict(_zone, _buffer, _idx, _len, _exception, self);
961       break;
962
963     case '(': // array
964       NSCAssert(result == nil, @"result is already set ..");
965       result = _parseArray(_zone, _buffer, _idx, _len, _exception, self);
966       break;
967
968     case '<': // data
969       NSCAssert(result == nil, @"result is already set ..");
970       result = _parseData(_zone, _buffer, _idx, _len, _exception, self);
971       break;
972       
973     default:
974       NSCAssert(result == nil, @"result is already set ..");
975       
976       if (isdigit((int)_buffer[*_idx]) || (_buffer[*_idx] == '-')) {
977         id digitPath;
978         NSCAssert(result == nil, @"result is already set ..");
979         
980         digitPath = _parseKeyPath(_zone, _buffer, _idx, _len, _exception,self);
981         result = [_parseDigitPath(digitPath) retain];
982         [digitPath release]; digitPath = nil;
983         valueProperty = YES;
984       }
985       else if (_isIdChar(_buffer[*_idx])) {
986         valueProperty = NO;
987         
988         if ((_buffer[*_idx] == 'Y') || (_buffer[*_idx] == 'N')) {
989           // parse YES and NO
990           if ((*_idx + 4) < _len) {
991             if (_ucIsEqual(&(_buffer[*_idx]), "YES", 3) &&
992                 _isBreakChar(_buffer[*_idx + 3])) {
993               result = [yesNum retain];
994               valueProperty = YES;
995               *_idx += 3; // skip the YES
996             }
997           }
998           if (((*_idx + 3) < _len) && !valueProperty) {
999             if (_ucIsEqual(&(_buffer[*_idx]), "NO", 2) &&
1000                 _isBreakChar(_buffer[*_idx + 2])) {
1001               result = [noNum retain];
1002               valueProperty = YES;
1003               *_idx += 2; // skip the NO
1004             }
1005           }
1006         }
1007         else if ((_buffer[*_idx] == 't') || (_buffer[*_idx] == 'f')) {
1008           // parse true and false
1009           if ((*_idx + 5) < _len) {
1010             if (_ucIsEqual(&(_buffer[*_idx]), "true", 4) &&
1011                 _isBreakChar(_buffer[*_idx + 4])) {
1012               result = [yesNum retain];
1013               valueProperty = YES;
1014               *_idx += 4; // skip the true
1015             }
1016           }
1017           if (((*_idx + 6) < _len) && !valueProperty) {
1018             if (_ucIsEqual(&(_buffer[*_idx]), "false", 5) &&
1019                 _isBreakChar(_buffer[*_idx + 5])) {
1020               result = [noNum retain];
1021               valueProperty = YES;
1022               *_idx += 5; // skip the false
1023             }
1024           }
1025         }
1026         
1027         if (!valueProperty) {
1028           NSCAssert(result == nil, @"result already set ..");
1029           result = _parseKeyPath(_zone, _buffer, _idx, _len, _exception, self);
1030         }
1031       }
1032       else {
1033         NSCAssert(result == nil, @"result already set ..");
1034         
1035         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1036                                      @"invalid char");
1037       }
1038       break;
1039   }
1040
1041   if (*_exception)
1042     return nil;
1043   
1044   if (result == nil) {
1045     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1046                                  @"error in property value");
1047   }
1048
1049   NSCAssert(result, @"missing property value ..");
1050   
1051   if (_allowAssoc) {
1052     id old = result;
1053     
1054     result = valueProperty
1055       ? [self associationWithValue:result]
1056       : [self associationWithKeyPath:result];
1057     
1058 #if 0
1059     NSCAssert(result, @"got no association for property ..");
1060 #endif
1061     [old release]; old = nil;
1062     
1063     return [result retain];
1064   }
1065   
1066   /* result is already retained */
1067   return result;
1068 }
1069
1070 static NSDictionary *_parseWodConfig(NSZone *_zone, const unichar *_buffer,
1071                                      unsigned *_idx, unsigned _len,
1072                                      NSException **_exception,
1073                                      id self)
1074 {
1075   if (!_skipComments(_buffer, _idx, _len, _exception)) {
1076     // EOF reached during comment-skipping
1077     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1078                      @"did not find element configuration (expected '{')");
1079     return nil;
1080   }
1081   
1082   if (_buffer[*_idx] != '{') { // it's not a dict that's follows
1083     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1084                      @"did not find element configuration (expected '{')");
1085     return nil;
1086   }
1087   else { // found '{'
1088     NSMutableDictionary *result = nil;
1089     NSString *key     = nil;
1090     id       value    = nil;
1091     BOOL     didFail  = NO;
1092     
1093     *_idx += 1; // skip '{'
1094
1095     if (!_skipComments(_buffer, _idx, _len, _exception)) {
1096       *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1097                        @"element configuration was not closed (expected '}')");
1098       return nil; // EOF
1099     }
1100
1101     if (_buffer[*_idx] == '}') { // an empty configuration
1102       *_idx += 1; // skip the '}'
1103       return [[DictClass allocWithZone:_zone] init];
1104     }
1105
1106     result = [[DictClass allocWithZone:_zone] init];
1107     do {
1108       key   = nil;
1109       value = nil;
1110       
1111       if (!_skipComments(_buffer, _idx, _len, _exception)) {
1112         *_exception =
1113           _makeException(*_exception, _buffer, *_idx, _len,
1114                          @"dictionary was not closed (expected '}')");
1115         didFail = YES;
1116         break; // unexpected EOF
1117       }
1118
1119       if (_buffer[*_idx] == '}') { // dictionary closed
1120         *_idx += 1; // skip the '}'
1121         break;
1122       }
1123       
1124       // read key property
1125       key = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
1126       if (key == nil) { // syntax error
1127         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1128                          @"expected identifier in element configuration ..");
1129         didFail = YES;
1130         break;
1131       }
1132
1133       /* The following parses:  (comment|space)* '=' (comment|space)* */
1134       if (!_skipComments(_buffer, _idx, _len, _exception)) {
1135         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1136                          @"expected '=' after id in element configuration");
1137         didFail = YES;
1138         break; // unexpected EOF
1139       }
1140       // no we need a '=' assignment
1141       if (_buffer[*_idx] != '=') {
1142         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1143                          @"expected '=' after id in element configuration");
1144         didFail = YES;
1145         break;
1146       }
1147       *_idx += 1; // skip '='
1148       if (!_skipComments(_buffer, _idx, _len, _exception)) {
1149         *_exception =
1150           _makeException(*_exception, _buffer, *_idx, _len,
1151                          @"expected value after id '=' in "
1152                          @"element configuration");
1153         didFail = YES;
1154         break; // unexpected EOF
1155       }
1156
1157       // read value property
1158       value = _parseProperty(_zone, _buffer, _idx, _len, _exception, YES, self);
1159 #if 1
1160       if (*_exception) {
1161         didFail = YES;
1162         break;
1163       }
1164 #else
1165       if (value == nil) { // syntax error
1166         if (*_exception == nil) {
1167           *_exception =
1168             _makeException(*_exception, _buffer, *_idx, _len,
1169                            @"got nil-value in element configuration");
1170         }
1171         didFail = YES;
1172         break;
1173       }
1174       NSCAssert(key,   @"invalid key ..");
1175       NSCAssert(value, @"invalid value ..");
1176 #endif
1177
1178       if ((value != nil) && (key != nil))
1179         [result setObject:value forKey:key];
1180
1181       // release key and value
1182       RELEASE(key);   key   = nil;
1183       RELEASE(value); value = nil;
1184
1185       // read trailing ';' if available
1186       if (!_skipComments(_buffer, _idx, _len, _exception)) {
1187         *_exception =
1188           _makeException(*_exception, _buffer, *_idx, _len,
1189                          @"element configuration was not "
1190                          @"closed (expected '}')");
1191         didFail = YES;
1192         break; // unexpected EOF
1193       }
1194       if (_buffer[*_idx] == ';') {
1195         *_idx += 1; // skip ';'
1196       }
1197       else { // no ';' at end of pair, only allowed at end of dictionary
1198         if (!_skipComments(_buffer, _idx, _len, _exception)) {
1199           *_exception =
1200             _makeException(*_exception, _buffer, *_idx, _len,
1201                            @"element configuration was not "
1202                            @"closed (expected '}')");
1203           didFail = YES;
1204           break; // unexpected EOF
1205         }
1206
1207         if (_buffer[*_idx] != '}') { // config wasn't closed
1208           *_exception =
1209             _makeException(*_exception, _buffer, *_idx, _len,
1210                            @"key-value pair without ';' at the end");
1211           didFail = YES;
1212           break;
1213         }
1214       }
1215     }
1216     while ((*_idx < _len) && (result != nil) && !didFail);
1217
1218     [key   release]; key    = nil;
1219     [value release]; value  = nil;
1220     
1221     if (didFail) {
1222       [result release]; result = nil;
1223       return nil;
1224     }
1225     else
1226       return result;
1227   }
1228 }
1229
1230 static id _parseWodEntry(NSZone *_zone, const unichar *_buffer, unsigned *_idx,
1231                          unsigned _len, NSException **_exception,
1232                          NSString **_name, NSString **_class, id self)
1233 {
1234   NSString     *elementName   = nil;
1235   NSString     *componentName = nil;
1236   NSDictionary *config        = nil;
1237
1238   *_name  = nil;
1239   *_class = nil;
1240   
1241   if (!_skipComments(_buffer, _idx, _len, _exception))
1242     return nil; // EOF
1243
1244   // Element name
1245   elementName = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
1246   if (elementName == nil) {
1247     *_exception = _makeException(nil, _buffer, *_idx, _len,
1248                                  @"expected element name");
1249     goto failed;
1250   }
1251
1252   if (!_skipComments(_buffer, _idx, _len, _exception))
1253     goto failed;
1254
1255   // Element/Component separator
1256   if (_buffer[*_idx] == ':') {
1257     *_idx += 1; // skip ':'
1258   }
1259   else {
1260     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1261                                  @"expected ':' after element name");
1262     goto failed;
1263   }
1264
1265   if (!_skipComments(_buffer, _idx, _len, _exception))
1266     goto failed;
1267
1268   // Component Name
1269   componentName = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
1270   if (componentName == nil) {
1271     *_exception = _makeException(nil, _buffer, *_idx, _len,
1272                                  @"expected component name");
1273     goto failed;
1274   }
1275
1276   if (!_skipComments(_buffer, _idx, _len, _exception))
1277     goto failed;
1278
1279   // Configuration
1280   config = _parseWodConfig(_zone, _buffer, _idx, _len, _exception, self);
1281   if (config == nil)
1282     goto failed;
1283   
1284   //NSLog(@"%@ : %@ %@", elementName, componentName, config);
1285
1286   // read trailing ';' if available
1287   if (_skipComments(_buffer, _idx, _len, _exception)) {
1288     if (_buffer[*_idx] == ';') {
1289       *_idx += 1; // skip ';'
1290     }
1291   }
1292
1293   *_name  = elementName;
1294   *_class = componentName;
1295   return config;
1296   
1297  failed:
1298 #if 0
1299   NSLog(@"failed at %@:%@ ..", elementName, componentName);
1300 #endif
1301   [elementName   release]; elementName   = nil;
1302   [componentName release]; componentName = nil;
1303   [config        release]; config        = nil;
1304   return nil;
1305 }
1306
1307 @end /* WODParser */