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