]> err.no Git - sope/blob - skyrix-sope/NGObjWeb/Templates/WOHTMLParser.m
added svn:keywords and svn:ignore where appropriate. removed CVS artifacts.
[sope] / skyrix-sope / NGObjWeb / Templates / WOHTMLParser.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo 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   OGo 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 OGo; 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 // $Id$
22
23 #include "WOHTMLParser.h"
24 #include <NGObjWeb/WODynamicElement.h>
25 #include <NGObjWeb/WOElement.h>
26 #include "common.h"
27
28 /*
29   Internals
30   
31   The root parse function is _parseElement() which calls either 
32   _parseWOElement() or _parseHashElement() if it finds a NGObjWeb tag at the 
33   beginning of the buffer. 
34   If it doesn't it collects all content till it encounteres an NGObjWeb tag, 
35   and reports that content as "static text" to the callback.
36   
37   Parsing a dynamic element is:
38     - parse the start tag
39     - parse the attributes
40     - parse the contents, static strings and elements
41       - add content to a children array
42     - produce WOElement by calling
43       -dynamicElementWithName:attributes:contentElements:
44     - parse close tag
45 */
46
47 @interface WOElement(StaticStringElement)
48 - (id)initWithBuffer:(const char *)_buffer length:(unsigned)_len;
49 @end
50
51 @implementation WOHTMLParser
52
53 static WOElement *_parseElement(NSZone *_zone,
54                                 const char *_buffer, unsigned *_idx,
55                                 unsigned _len, NSException **_exception,
56                                 WOHTMLParser *self);
57
58 static Class StrClass      = Nil;
59 static Class DictClass     = Nil;
60 static Class NumberClass   = Nil;
61 static Class WOStringClass = Nil;
62 static BOOL  skipPlainTags = NO; /* do process markers inside HTML tags ? */
63 static BOOL  compressHTMLWhitespace = YES;
64 static BOOL  useUTF8 = NO;
65
66 + (void)initialize {
67   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
68   
69   StrClass      = [NSString            class];
70   DictClass     = [NSMutableDictionary class];
71   NumberClass   = [NSNumber            class];
72   WOStringClass = NSClassFromString(@"_WOStaticHTMLElement");
73
74   useUTF8       = [ud boolForKey:@"WOParsersUseUTF8"];
75 }
76
77 - (id)initWithHandler:(id<NSObject,WOHTMLParserHandler>)_handler {
78   self->callback = [_handler retain];
79   return self;
80 }
81 - (void)dealloc {
82   [self->parsingException release];
83   [self->callback         release];
84   [super dealloc];
85 }
86
87 /* callbacks */
88
89 - (NSException *)_makeSyntaxErrorException {
90   return [NSException exceptionWithName:@"SyntaxError"
91                       reason:@"template syntax error"
92                       userInfo:nil];
93 }
94
95 - (WOElement *)dynamicElementWithName:(NSString *)_element
96   attributes:(NSDictionary *)_attributes // not the associations !
97   contentElements:(NSArray *)_subElements
98 {
99   return [self->callback dynamicElementWithName:_element
100                          attributes:_attributes
101                          contentElements:_subElements];
102 }
103
104 - (id)_makeConstantStringElementWithBuffer:(const unsigned char *)_buf
105   length:(unsigned)_len
106 {
107   return [[WOStringClass allocWithZone:NULL] initWithBuffer:_buf length:_len];
108 }
109
110 - (NSString *)_makeStringForBuffer:(const unsigned char *)_buf 
111   length:(unsigned)_len
112 {
113   NSString *r;
114   NSData   *data;
115   
116   if (_len == 0)
117     return @"";
118   
119   if (!useUTF8)
120     return [[StrClass alloc] initWithCString:_buf length:_len];
121   
122   // Note: we cast the pointer because we are not going to modify _buf for the
123   //       duration and we are never going to write the data - should work
124   //       with any Foundation, but isn't strictly API compatible
125   data = [[NSData alloc] initWithBytesNoCopy:(void *)_buf length:_len 
126                          freeWhenDone:NO];
127   r = [[StrClass alloc] initWithData:data encoding:NSUTF8StringEncoding];
128   [data release];
129   return r;
130 }
131
132 /* accessors */
133
134 - (NSException *)parsingException {
135   return self->parsingException;
136 }
137
138 /* parsing API */
139
140 - (NSArray *)parseHTMLData:(NSData *)_html {
141   NSMutableArray *topLevel;
142   const char     *html;
143   unsigned       idx, len;
144   NSException    *exception = nil;
145
146   if (![self->callback parser:self willParseHTMLData:_html])
147     return nil;
148   
149   [self->parsingException release]; self->parsingException = nil;
150   
151   if (_html == nil)
152     return nil;
153   
154   topLevel = [NSMutableArray arrayWithCapacity:64];
155   idx  = 0;
156   len  = [_html length];
157   html = [_html bytes];
158   
159   while ((idx < len) && (exception == nil)) {
160     WOElement *element;
161     
162     if ((element = _parseElement(NULL, html, &idx, len, &exception, self))) {
163       [topLevel addObject:element];
164       [element release]; element = nil;
165     }
166   }
167   
168   ASSIGN(self->parsingException, exception);
169   
170   if (exception) {
171     [self->callback parser:self 
172                     failedParsingHTMLData:_html exception:exception];
173   }
174   else {
175     [self->callback parser:self 
176                     finishedParsingHTMLData:_html elements:topLevel];
177   }
178   
179   return self->parsingException ? nil : topLevel;
180 }
181
182 /* internal parsing */
183
184 static int _numberOfLines(const char *_buffer, unsigned _lastIdx) {
185   register int pos, lineCount = 1;
186   
187   for (pos = 0; (pos < (int)_lastIdx) && (_buffer[pos] != '\0'); pos++) {
188     if (_buffer[pos] == '\n')
189       lineCount++;
190   }
191   return lineCount;
192 }
193
194 static inline BOOL _isHTMLSpace(char c) {
195   switch (c) {
196     case ' ': case '\t': case '\r': case '\n':
197       return YES;
198
199     default:
200       return NO;
201   }
202 }
203
204 static NSException *_makeHtmlException(NSException *_exception,
205                                        const char *_buffer, unsigned _idx,
206                                        unsigned _len, NSString *_text,
207                                        WOHTMLParser *self)
208 {
209   NSMutableDictionary *ui = nil;
210   NSException *exception = nil;
211   int         numLines   = _numberOfLines(_buffer, _idx);
212   BOOL        atEof      = (_idx >= _len) ? YES : NO;
213
214   if (_exception)
215     // error resulted from a previous error (exception already set)
216     return _exception;
217   
218   exception = [self _makeSyntaxErrorException];
219
220   if (atEof)
221     _text = [@"Unexpected end: " stringByAppendingString:[_text stringValue]];
222   else {
223     _text = [StrClass stringWithFormat:@"Syntax error in line %i: %@",
224                       numLines, _text];
225   }
226   
227   [exception setReason:_text];
228
229   /* user info */
230   {
231     ui = [[exception userInfo] mutableCopy];
232     if (ui == nil)
233       ui = [[DictClass alloc] initWithCapacity:8];
234     
235     [ui setObject:[NumberClass numberWithInt:numLines] forKey:@"line"];
236     [ui setObject:[NumberClass numberWithInt:_len]     forKey:@"size"];
237     [ui setObject:[NumberClass numberWithInt:_idx]     forKey:@"position"];
238     
239     if (self)
240       [ui setObject:self forKey:@"handler"];
241     
242     if (!atEof && (_idx > 0)) {
243       register unsigned pos;
244       const unsigned char *startPos, *endPos;
245
246       for (pos = _idx; (pos >= 0) && (_buffer[pos] != '\n'); pos--)
247         ;
248       startPos = &(_buffer[pos + 1]);
249
250       for (pos = _idx; ((pos < _len) && (_buffer[pos] != '\n')); pos++)
251         ;
252       endPos = &(_buffer[pos - 1]);
253       
254       if (startPos < endPos) {
255         NSString *ll;
256         
257         ll = [self _makeStringForBuffer:startPos length:(endPos - startPos)];
258         [ui setObject:ll forKey:@"lastLine"];
259         [ll release];
260       }
261 #if HEAVY_DEBUG
262       else {
263         //NSLog(@"startPos=0x%08X endPos=0x%08X", startPos, endPos);
264       }
265 #endif
266     }
267     
268 #if NeXT_Foundation_LIBRARY || APPLE_FOUNDATION_LIBRARY || \
269     COCOA_Foundation_LIBRARY
270     exception = [NSException exceptionWithName:[exception name] reason:[exception reason] userInfo:ui];
271 #else
272     [exception setUserInfo:ui];
273 #endif
274
275     [ui release]; ui = nil;
276   }
277
278   return exception;
279 }
280
281 static inline BOOL
282 _isComment(const char *_buffer, unsigned _idx, unsigned _len)
283 {
284   // <!----> - 7 chars
285   if ((_idx + 7) >= _len)  // check whether it is long enough
286     return NO;
287   if (_buffer[_idx] != '<') // check whether it is a tag
288     return NO;
289
290   _idx++; if (_buffer[_idx] != '!') return NO;
291   _idx++; if (_buffer[_idx] != '-') return NO;
292   _idx++; if (_buffer[_idx] != '-') return NO;
293
294   return YES;
295 }
296
297 static inline BOOL _isHashTag(const char *_buf, unsigned _idx, unsigned _len) {
298   /* check for "<#.>" (len 4) */
299   if ((_idx + 3) >= _len)  // check whether it is long enough
300     return NO;
301   return (_buf[_idx] == '<' && _buf[_idx + 1] == '#') ? YES : NO;
302 }
303 static inline BOOL _isHashCloseTag(const char *_buf, 
304                                    unsigned _idx, unsigned _len) 
305 {
306   /* check for "</#.>" (len 5) */
307   if ((_idx + 5) >= _len)  // check whether it is long enough
308     return NO;
309   return (_buf[_idx] == '<' && _buf[_idx + 1] == '/' && _buf[_idx + 2] == '#') 
310     ? YES : NO;
311 }
312
313 static inline BOOL _isWOTag(const char *_buf, unsigned _idx, unsigned _len) {
314   /* check for "<WEBOBJECT .......>" (len 19) (lowercase is allowed) */
315   if ((_idx + 18) >= _len)  // check whether it is long enough
316     return NO;
317   if (_buf[_idx] != '<') // check whether it is a tag
318     return NO;
319   
320   // now check for '<WEBOBJECT'
321   return (strncasecmp(&(_buf[_idx]), "<WEBOBJECT", 10) == 0) ? YES : NO;
322 }
323
324 static inline BOOL
325 _isWOCloseTag(const char *_buf, unsigned _idx, unsigned _len)
326 {
327   /* check for </WEBOBJECT> (len=12) */
328   if ((_idx + 12) > _len)  // check whether it is long enough
329     return NO;
330   if (_buf[_idx] != '<') // check whether it is a tag
331     return NO;
332   
333   return (strncasecmp(&(_buf[_idx]), "</WEBOBJECT>", 12) == 0) ? YES : NO;
334 }
335
336 static inline void _skipSpaces(register const char *_buffer, unsigned *_idx,
337                                unsigned _len)
338 {
339   register unsigned pos = *_idx;
340
341   if (pos >= _len) return; // EOF
342
343   while ((pos < _len) && _isHTMLSpace(_buffer[pos]))
344     pos++;
345
346   *_idx = pos;
347 }
348
349 static NSString *_parseStringValue(NSZone *_zone,
350                                    register const char *_buffer,
351                                    unsigned *_idx, unsigned _len,
352                                    NSException **_exception,
353                                    WOHTMLParser *self)
354 {
355   register unsigned pos = *_idx;
356   
357   _skipSpaces(_buffer, _idx, _len);
358   if (pos >= _len) return nil; // EOF
359   
360   if (_buffer[pos] == '>') return nil;
361   if (_buffer[pos] == '/') return nil;
362   if (_buffer[pos] == '=') return nil;
363   
364   if (_buffer[pos] == '"') { // quoted string
365     register unsigned len = 0;
366     unsigned startPos = pos + 1;
367
368     pos++; // skip starting quote ('"')
369     
370     // loop until closing quote
371     while ((_buffer[pos] != '"') && (pos < _len)) {
372       pos++;
373       len++;
374     }
375     
376     if (pos == _len) { // syntax error, quote not closed
377       *_idx = pos;
378       *_exception = _makeHtmlException(*_exception, _buffer, *_idx, _len,
379                                    @"quoted string not closed (expected '\"')",
380                                    nil);
381       return nil;
382     }
383
384     NSCAssert(_buffer[pos] == '"', @"invalid parser state ..");
385     pos++;       // skip closing quote
386     *_idx = pos; // store pointer
387
388     if (len == 0) // empty string
389       return @"";
390     
391     return [self _makeStringForBuffer:&(_buffer[startPos]) length:len];
392   }
393   else {
394     unsigned startPos = pos;
395
396     //NSLog(@"parsing id at '%c'[%i] ..", _buffer[pos], pos);
397     
398     // loop until '>' or '=' or '/' or space
399     while ((_buffer[pos] != '>') &&
400            (_buffer[pos] != '=') &&
401            (_buffer[pos] != '/') &&
402            (!_isHTMLSpace(_buffer[pos])) &&
403            (pos < _len)) {
404       pos++;
405     }
406     *_idx = pos;
407
408     if ((pos - startPos) == 0) // wasn't a string ..
409       return nil;
410
411     return [self _makeStringForBuffer:&(_buffer[startPos]) 
412                  length:(pos - startPos)];
413   }
414 }
415
416 static WOElement *_parseHashElement(NSZone *_zone, const char *_buffer,
417                                     unsigned *_idx, unsigned _len,
418                                     NSException **_exc,
419                                     WOHTMLParser *self)
420 {
421   /*
422     parses:
423       <#dynelem>....</#dynelem>
424     or
425       <#dynelem/>
426   */
427   static NSString *nameKey = @"NAME";
428   WOElement      *element    = nil;
429   BOOL           foundEndTag = NO;
430   BOOL           isAutoClose = NO;
431   NSMutableArray *children   = nil;
432   NSString       *name;
433   NSDictionary   *nameDict;
434   
435   if (*_idx >= _len) return nil; // EOF
436   
437   if (!_isHashTag(_buffer, *_idx, _len))
438     return nil; // not a hash tag ..
439   
440   // skip '<#'
441   *_idx += 2;
442   
443   if ((name = _parseStringValue(_zone, _buffer, _idx,_len,_exc,self)) == nil) {
444 #if HEAVY_DEBUG
445     NSLog(@"ERROR: got no name for hash tag '<#NAME>'");
446 #endif
447     if (_exc) // if there was an error ..
448       return nil;
449   }
450   
451   _skipSpaces(_buffer, _idx, _len);
452   if (*_idx >= _len) {
453     *_exc =
454       _makeHtmlException(*_exc, _buffer, *_idx, _len,
455                      @"unexpected EOF: missing '>' in hash element tag (EOF).",
456                      self);
457     [name release]; name = nil;
458     return nil; // unexpected EOF
459   }
460   if (_buffer[*_idx] != '>' && _buffer[*_idx] != '/') {
461     *_exc = _makeHtmlException(*_exc, _buffer, *_idx, _len,
462                                      @"missing '>' in hash element tag.", self);
463     [name release]; name = nil;
464     return nil; // unexpected EOF
465   }
466
467   if (_buffer[*_idx] == '>') {
468     /* has sub-elements (<#name>...</#name>) */
469     *_idx += 1; // skip '>'
470   
471     while ((*_idx < _len) && (*_exc == nil)) {
472       id subElement = nil;
473     
474 #if HEAVY_DEBUG
475       NSLog(@"subelement at '%c'[%i] ..", _buffer[*_idx], *_idx);
476 #endif
477     
478       if (_isHashCloseTag(_buffer, *_idx, _len)) {
479         foundEndTag = YES;
480         break;
481       }
482
483       subElement = _parseElement(_zone, _buffer, _idx, _len, _exc, self);
484     
485 #if HEAVY_DEBUG
486       NSLog(@"  parsed subelement '%@' ..", subElement);
487 #endif
488     
489       if (subElement) {
490         if (children == nil)
491           children = [NSMutableArray arrayWithCapacity:10];
492         [children addObject:subElement];
493         [subElement release]; subElement = nil;
494       }
495     }
496   }
497   else {
498     /* has no sub-elements (<#name/>) */
499     *_idx += 1; // skip '/'
500     isAutoClose = YES;
501     if (_buffer[*_idx] != '>') {
502       *_exc = _makeHtmlException(*_exc, _buffer, *_idx, _len,
503                                  @"missing '>' in hash element tag.", self);
504       [name release]; name = nil;
505       return nil; // unexpected EOF
506     }
507     *_idx += 1; // skip '>'
508   }
509   
510   /* produce elements */
511
512   if ([name length] < 1) {
513     element = nil;
514     *_exc = _makeHtmlException(*_exc, NULL, 0, 0,
515                                      @"missing name in hash element tag.",
516                                      nil);
517     [name release];
518     return nil;
519   }
520   
521   nameDict = [[NSDictionary alloc] initWithObjects:&name forKeys:&nameKey 
522                                    count:1];
523   element = [self dynamicElementWithName:name
524                   attributes:nameDict
525                   contentElements:children];
526   [name release];     name = nil;
527   [nameDict release]; nameDict = nil;
528   
529   if (element == nil) { // build error
530     *_exc = _makeHtmlException(*_exc, _buffer, *_idx, _len,
531                                  @"could not build hash element !.", self);
532     return nil;
533   }
534   
535   if (!foundEndTag && !isAutoClose) {
536     *_exc = _makeHtmlException(*_exc, _buffer, *_idx, _len,
537                                  @"did not find hash end tag (</#...>) ..",
538                                  self);
539     [element release]; element = nil;
540     return nil;
541   }
542   else if (!isAutoClose) {
543     /* skip close tag ('</#name>') */
544     NSCAssert(_isHashCloseTag(_buffer, *_idx, _len), 
545               @"invalid parser state ..");
546     
547     *_idx += 3; // skip '</#'
548     while ((*_idx < _len) && (_buffer[*_idx] != '>'))
549       *_idx += 1;
550     *_idx += 1; // skip '>'
551 #if HEAVY_DEBUG
552     NSLog(@"parsed close tag, now at '%c'[%i] ..", _buffer[*_idx], *_idx);
553 #endif
554   }
555   return element;
556 }
557
558 static NSMutableDictionary *
559 _parseTagAttributes(NSZone *_zone, const char *_buffer,
560                     unsigned *_idx, unsigned _len,
561                     NSException **_exception, WOHTMLParser *self)
562 {
563   NSMutableDictionary *dict = nil;
564
565   _skipSpaces(_buffer, _idx, _len);
566   if (*_idx >= _len) return nil; // EOF
567
568 #if HEAVY_DEBUG
569   NSLog(@"parsing attributes at '%c'[%i] ..", _buffer[*_idx], *_idx);
570 #endif
571   
572   do {
573     NSString *key   = nil;
574     NSString *value = nil;
575     
576     _skipSpaces(_buffer, _idx, _len);
577     if (*_idx >= _len) break; // EOF
578
579     // read key
580     key = _parseStringValue(_zone, _buffer, _idx, _len, _exception, self);
581     if (key == nil) // ended
582       break;
583
584     /* The following parses:  space* '=' space* */
585
586     _skipSpaces(_buffer, _idx, _len);
587     if (*_idx >= _len) {
588       *_exception = _makeHtmlException(*_exception, _buffer, *_idx, _len,
589                                    @"expected '=' after key in attributes ..",
590                                    nil);
591       break; // unexpected EOF
592     }
593     if (_buffer[*_idx] != '=') {
594       *_exception = _makeHtmlException(*_exception, _buffer, *_idx, _len,
595                                    @"expected '=' after key in attributes ..",
596                                    nil);
597       break;
598     }
599     NSCAssert(_buffer[*_idx] == '=', @"invalid parser state ..");
600     *_idx += 1; // skip '='
601     _skipSpaces(_buffer, _idx, _len);
602     if (*_idx >= _len) {
603       *_exception = _makeHtmlException(*_exception, _buffer, *_idx, _len,
604                                  @"expected value after key in attributes ..",
605                                  nil);
606       break; // unexpected EOF
607     }
608
609     // read value
610     value = _parseStringValue(_zone, _buffer, _idx, _len, _exception, self);
611     if (value == nil) {
612       *_exception = _makeHtmlException(*_exception, _buffer, *_idx, _len,
613                                  @"expected value after key in attributes ..",
614                                  nil);
615       break; // unexpected EOF
616     }
617
618     NSCAssert(key,   @"invalid key ..");
619     NSCAssert(value, @"invalid value ..");
620
621     if (dict == nil)
622       dict = [[DictClass allocWithZone:_zone] init];
623     NSCAssert(dict, @"no attributes dictionary ?");
624     [dict setObject:value forKey:key];
625     
626     [key   release]; key   = nil;
627     [value release]; value = nil;
628   }
629   while (*_idx < _len);
630
631   return dict;
632 }
633 static WOElement *_parseWOElement(NSZone *_zone, const char *_buffer,
634                                   unsigned *_idx, unsigned _len,
635                                   NSException **_exception,
636                                   WOHTMLParser *self)
637 {
638   WOElement           *element    = nil;
639   NSMutableDictionary *attrs      = nil;
640   BOOL                foundEndTag = NO;
641   NSMutableArray      *children   = nil;
642   
643   if (*_idx >= _len) return nil; // EOF
644   
645   if (!_isWOTag(_buffer, *_idx, _len))
646     return nil; // not a WO tag ..
647
648   NSCAssert(strncasecmp("<WEBOBJECT", &(_buffer[*_idx]), 10) == 0,
649             @"invalid parser state ..");
650   
651   // skip '<WEBOBJECT'
652   *_idx += 10;
653   
654   attrs = _parseTagAttributes(_zone, _buffer, _idx, _len, _exception, self);
655   if (attrs == nil) {
656     //NSLog(@"ERROR: got no attributes for WO tag (need at least 'NAME')..");
657     
658     if (_exception) // if there was an error ..
659       return nil;
660   }
661   
662   _skipSpaces(_buffer, _idx, _len);
663   if (*_idx >= _len) {
664     *_exception =
665       _makeHtmlException(*_exception, _buffer, *_idx, _len,
666                      @"unexpected EOF: missing '>' in WEBOBJECT tag.",
667                      self);
668     [attrs release]; attrs = nil;
669     return nil; // unexpected EOF
670   }
671   if (_buffer[*_idx] != '>') {
672     *_exception = _makeHtmlException(*_exception, _buffer, *_idx, _len,
673                                  @"missing '>' in WEBOBJECT tag.", self);
674     [attrs release]; attrs = nil;
675     return nil; // unexpected EOF
676   }
677   NSCAssert(_buffer[*_idx] == '>', @"invalid parser state ..");
678
679   *_idx += 1; // skip '>'
680
681   // parse sub-elements
682   
683   while ((*_idx < _len) && (*_exception == nil)) {
684     id subElement = nil;
685
686     //NSLog(@"subelement at '%c'[%i] ..", _buffer[*_idx], *_idx);
687
688     if (_isWOCloseTag(_buffer, *_idx, _len)) {
689       foundEndTag = YES;
690       break;
691     }
692
693     subElement = _parseElement(_zone, _buffer, _idx, _len, _exception, self);
694
695     //NSLog(@"  parsed subelement '%@' ..", subElement);
696
697     if (subElement) {
698       if (children == nil)
699         children = [NSMutableArray arrayWithCapacity:10];
700       [children addObject:subElement];
701       [subElement release]; subElement = nil;
702     }
703   }
704
705   /* produce elements */
706   {
707     NSString *name;
708     
709     if ((name = [attrs objectForKey:@"NAME"]) == nil)
710       name = [attrs objectForKey:@"name"];
711     if (name == nil) {
712       if ((name = [attrs objectForKey:@"name"])) {
713         NSLog(@"%s: missing 'name' attribute !",
714               __PRETTY_FUNCTION__);
715       }
716     }
717     
718     if ([name length] < 1) {
719       element = nil;
720       *_exception = _makeHtmlException(*_exception, NULL, 0, 0,
721                                        @"no NAME attribute in WEBOBJECT tag.",
722                                        nil);
723       return nil;
724     }
725     else {
726       element = [self dynamicElementWithName:name
727                       attributes:attrs
728                       contentElements:children];
729     }
730   }
731   [attrs release]; attrs = nil;
732
733   if (element == nil) { // build error
734     *_exception = _makeHtmlException(*_exception, _buffer, *_idx, _len,
735                                  @"could not build WEBOBJECT.", self);
736     return nil;
737   }
738   
739   if (!foundEndTag) {
740     *_exception = _makeHtmlException(*_exception, _buffer, *_idx, _len,
741                                  @"did not find WEBOBJECT end tag ..",
742                                  self);
743     [element release]; element = nil;
744     return nil;
745   }
746   else {
747     NSCAssert(_isWOCloseTag(_buffer, *_idx, _len), @"invalid parser state ..");
748     
749     // skip close tag ('</WEBOBJECT>')
750     *_idx += 11; // skip '</WEBOBJECT'
751     while ((*_idx < _len) && (_buffer[*_idx] != '>'))
752       *_idx += 1;
753     *_idx += 1; // skip '>'
754
755     //NSLog(@"parsed close tag, now at '%c'[%i] ..", _buffer[*_idx], *_idx);
756   }
757   return element;
758 }
759
760 static inline NSString *_makeTextString(NSZone *_zone, const char *_buffer,
761                                         unsigned _len, WOHTMLParser *self)
762 {
763   NSString *result = nil;
764   register unsigned char *buffer;
765   register unsigned pos, bufPos;
766   
767   if (_len == 0) // empty string
768     return @"";
769
770   if (!compressHTMLWhitespace)
771     /* deliver whitespace as in template */
772     return [self _makeStringForBuffer:_buffer length:_len];
773   
774   buffer = malloc(_len + 3);
775
776   for (pos = 0, bufPos = 0; pos < _len; ) {
777       buffer[bufPos] = _buffer[pos];
778
779       if ((_buffer[pos] == ' ') || (_buffer[pos] == '\t')) {
780         do {
781           pos++;
782         }
783         while (((_buffer[pos] == ' ') || (_buffer[pos] =='\t')) &&
784                (pos < _len));
785         
786         bufPos++;
787       }
788       else {
789         pos++;
790         bufPos++;
791       }
792   }
793   
794   result = [self _makeStringForBuffer:buffer length:bufPos];
795   if (buffer) free(buffer);
796   return result;
797 }
798
799 static WOElement *_parseElement(NSZone *_zone,
800                                 const char *_buffer, unsigned *_idx,
801                                 unsigned _len, NSException **_exception,
802                                 WOHTMLParser *self)
803 {
804   register unsigned pos = *_idx;
805   unsigned startPos = pos;
806   
807   if (*_idx >= _len) // EOF
808     return nil;
809   
810   if (_isHashTag(_buffer, *_idx, _len)) {
811     /* start parsing of dynamic content */
812     return _parseHashElement(_zone, _buffer, _idx, _len, _exception, self);
813   }
814   if (_isHashCloseTag(_buffer, *_idx, _len)) {
815     /* check for a common template syntax error */
816     *_exception = _makeHtmlException(*_exception, _buffer, *_idx, _len,
817                                      @"unexpected hash close tag (</#...>).",
818                                      self);
819     return nil;
820   }
821   
822   if (_isWOTag(_buffer, *_idx, _len)) {
823     /* start parsing of dynamic content */
824     return _parseWOElement(_zone, _buffer, _idx, _len, _exception, self);
825   }
826   if (_isWOCloseTag(_buffer, *_idx, _len)) {
827     /* check for a common template syntax error */
828     *_exception = _makeHtmlException(*_exception, _buffer, *_idx, _len,
829                                      @"unexpected WEBOBJECT close tag "
830                                      @"(</WEBOBJECT...>).",
831                                      self);
832     return nil;
833   }
834   
835   /* parse text/tag content */
836   do {
837     while ((_buffer[pos] != '<') && (pos < _len))
838       pos++;
839     
840     if (pos >= _len) // EOF was reached
841       break;
842     
843     NSCAssert(_buffer[pos] == '<', @"invalid parser state ..");
844     
845     if (_isHashTag(_buffer, pos, _len)) /* found Hash */
846       break;
847     if (_isHashCloseTag(_buffer, pos, _len))
848       break;
849     if (_isWOTag(_buffer, pos, _len)) /* found Hash */
850       break;
851     if (_isWOCloseTag(_buffer, pos, _len))
852       break;
853     
854 #if HEAVY_DEBUG
855     NSLog(@"is comment ? from '%c%c%c'[%i]",
856           _buffer[pos], _buffer[pos+1], _buffer[pos+2], pos);
857 #endif
858     if (_isComment(_buffer, pos, _len)) {
859       pos += 3; // skip '<--'
860
861       while (pos < _len) {
862         if (_buffer[pos] == '-') {
863           if (pos + 2 < _len) {
864             if ((_buffer[pos + 1] == '-') && (_buffer[pos + 2] == '>')) {
865               // found '-->'
866               pos += 3; // skip '-->'
867               *_idx = pos;
868               break;
869             }
870           }
871         }
872         pos++;
873       }
874       if (pos >= _len) // EOF was reached
875         break;
876     }
877     else {
878       // skip '<', read usual tag
879       pos++;
880       if (pos >= _len) { // EOF was reached with opening '<'
881         NSLog(@"WARNING: reached EOF with '<' at end !");
882         break;
883       }
884       
885       if (skipPlainTags) {
886         /* skip until end of HTML tag (not #-tag) */
887         do {
888           pos++;
889         }
890         while ((_buffer[pos] != '>') && (pos < _len));
891         if (pos >= _len) break; // EOF
892       }
893       
894       pos++;
895     }
896   }
897   while (pos < _len);
898   
899   /* store back position */
900   *_idx = pos;
901   
902 #if HEAVY_DEBUG
903   NSLog(@"Debug: stopped parsing at '%c'[%i]", _buffer[pos], pos);
904 #endif
905
906   if ((pos - startPos) > 0) {
907     return [self _makeConstantStringElementWithBuffer:&(_buffer[startPos])
908                  length:(pos - startPos)];
909   }
910   else
911     return nil;
912 }
913
914 @end /* WOHTMLParser */