2 Copyright (C) 2003-2004 Max Berger
3 Copyright (C) 2004 OpenGroupware.org
5 This file is part of versitSaxDriver, written for the OpenGroupware.org
8 OGo is free software; you can redistribute it and/or modify it under
9 the terms of the GNU Lesser General Public License as published by the
10 Free Software Foundation; either version 2, or (at your option) any
13 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
16 License for more details.
18 You should have received a copy of the GNU Lesser General Public
19 License along with OGo; see the file COPYING. If not, write to the
20 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
25 #include "VSSaxDriver.h"
26 #include "VSStringFormatter.h"
29 @implementation VSSaxDriver
31 static BOOL debugOn = NO;
33 static NSCharacterSet *dotCharSet = nil;
34 static NSCharacterSet *equalSignCharSet = nil;
35 static NSCharacterSet *commaCharSet = nil;
36 static NSCharacterSet *colonAndSemicolonCharSet = nil;
37 static NSCharacterSet *colonSemicolonAndDquoteCharSet = nil;
38 static NSCharacterSet *whitespaceCharSet = nil;
40 static VSStringFormatter *stringFormatter = nil;
43 static BOOL didInit = NO;
50 ud = [NSUserDefaults standardUserDefaults];
51 debugOn = [ud boolForKey:@"VSSaxDriverDebugEnabled"];
54 [[NSCharacterSet characterSetWithCharactersInString:@"."] retain];
56 [[NSCharacterSet characterSetWithCharactersInString:@"="] retain];
58 [[NSCharacterSet characterSetWithCharactersInString:@","] retain];
59 colonAndSemicolonCharSet =
60 [[NSCharacterSet characterSetWithCharactersInString:@":;"] retain];
61 colonSemicolonAndDquoteCharSet =
62 [[NSCharacterSet characterSetWithCharactersInString:@":;\""] retain];
64 [[NSCharacterSet whitespaceCharacterSet] retain];
66 stringFormatter = [VSStringFormatter sharedFormatter];
71 if ((self = [super init])) {
72 self->prefixURI = @"";
73 self->cardStack = [[NSMutableArray alloc] init];
74 self->elementList = [[NSMutableArray alloc] init];
75 self->attributeMapping = [[NSMutableDictionary alloc] init];
76 self->subItemMapping = [[NSMutableDictionary alloc] init];
82 [self->contentHandler release];
83 [self->prefixURI release];
84 [self->cardStack release];
85 [self->elementList release];
86 [self->attributeElements release];
87 [self->elementMapping release];
88 [self->attributeMapping release];
89 [self->subItemMapping release];
95 - (void)setFeature:(NSString *)_name to:(BOOL)_value {
97 - (BOOL)feature:(NSString *)_name {
101 - (void)setProperty:(NSString *)_name to:(id)_value {
103 - (id)property:(NSString *)_name {
109 - (void)setContentHandler:(id<NSObject,SaxContentHandler>)_handler {
110 ASSIGN(self->contentHandler,_handler);
113 - (void)setDTDHandler:(id<NSObject,SaxDTDHandler>)_handler {
117 - (void)setErrorHandler:(id<NSObject,SaxErrorHandler>)_handler {
120 - (void)setEntityResolver:(id<NSObject,SaxEntityResolver>)_handler {
124 - (id<NSObject,SaxContentHandler>)contentHandler {
125 return self->contentHandler;
128 - (id<NSObject,SaxDTDHandler>)dtdHandler {
133 - (id<NSObject,SaxErrorHandler>)errorHandler {
137 - (id<NSObject,SaxEntityResolver>)entityResolver {
142 - (void)setPrefixURI:(NSString *)_uri {
143 ASSIGNCOPY(self->prefixURI, _uri);
145 - (NSString *)prefixURI {
146 return self->prefixURI;
149 - (void)setAttributeElements:(NSSet *)_elements {
150 ASSIGNCOPY(self->attributeElements, _elements);
152 - (NSSet *)attributeElements {
153 return self->attributeElements;
156 - (void)setElementMapping:(NSDictionary *)_mapping {
157 ASSIGNCOPY(self->elementMapping, _mapping);
159 - (NSDictionary *)elementMapping {
160 return self->elementMapping;
163 - (void)setAttributeMapping:(NSDictionary *)_mapping {
164 [self setAttributeMapping:_mapping forElement:@""];
167 - (void)setAttributeMapping:(NSDictionary *)_mapping
168 forElement:(NSString *)_element
172 [attributeMapping setObject:_mapping forKey:_element];
175 - (void)setSubItemMapping:(NSArray *)_mapping
176 forElement:(NSString *)_element
178 [subItemMapping setObject:_mapping forKey:_element];
185 - (NSString *)_mapTagName:(NSString *)_tagName {
189 if ((ret = [self->elementMapping objectForKey:_tagName]) == nil) {
190 //NSLog(@"Unknown Key: %@ in %@",_tagName,self->elementMapping);
193 /* This is to allow parsing of vCards produced by Apple
194 Addressbook. AFAIK the .dot notation is a non-standard
196 r = [_tagName rangeOfCharacterFromSet:dotCharSet];
198 ret = [self _mapTagName:[_tagName substringFromIndex:(r.location + 1)]];
204 - (void)_addAttribute:(NSString *)_attribute
205 value:(NSString *)_value
206 toAttrs:(SaxAttributes *)_attrs
208 [_attrs addAttribute:_attribute
215 - (void)_addAttribute:(NSString *)_attribute value:(NSString *)_value {
216 NSArray *element = [cardStack lastObject];
217 SaxAttributes *attrs = [element objectAtIndex:2];
218 [self _addAttribute:_attribute value:_value toAttrs:attrs];
221 - (NSString *)_mapAttrName:(NSString *)_attrName forTag:(NSString *)_tagName {
222 NSString *mappedName;
224 mappedName = [[self->attributeMapping objectForKey:_tagName]
225 objectForKey:_attrName];
227 mappedName = [[self->attributeMapping objectForKey:
228 [self _mapTagName:_tagName]]
229 objectForKey:_attrName];
232 mappedName = [[self->attributeMapping objectForKey:@""]
233 objectForKey:_attrName];
236 mappedName = _attrName;
241 - (void)_parseAttr:(NSString *)_attr
242 forTag:(NSString *)_tagName
243 intoAttr:(NSString **)attr_
244 intoValue:(NSString **)value_
247 NSString *attrName, *attrValue, *mappedName;
249 r = [_attr rangeOfCharacterFromSet:equalSignCharSet];
251 unsigned left, right;
253 attrName = [[_attr substringToIndex:r.location] uppercaseString];
254 left = NSMaxRange(r);
255 right = [_attr length] - 1;
257 if(([_attr characterAtIndex:left] == '"') &&
258 ([_attr characterAtIndex:right] == '"'))
261 r = NSMakeRange(left, right - left);
262 attrValue = [_attr substringWithRange:r];
265 attrValue = [_attr substringFromIndex:left];
268 else if(left == right) {
269 attrValue = [_attr substringFromIndex:left];
281 // ZNeK: what's this for?
282 r = [attrValue rangeOfCharacterFromSet:commaCharSet];
283 while (r.length > 0) {
284 [attrValue replaceCharactersInRange:r withString:@" "];
285 r = [attrValue rangeOfCharacterFromSet:commaCharSet];
289 mappedName = [self _mapAttrName:attrName forTag:_tagName];
291 *value_ = [stringFormatter stringByUnescapingRFC2445Text:attrValue];
294 - (id<NSObject,SaxAttributes>)_mapAttrs:(NSArray *)_attrs
295 forTag:(NSString *)_tagName
297 SaxAttributes *retAttrs;
298 NSEnumerator *attrEnum;
299 NSString *curAttr, *mappedAttr, *mappedValue, *oldValue;
300 NSMutableDictionary *attributes;
302 if (!_attrs || [_attrs count] == 0)
305 attributes = [[NSMutableDictionary alloc] init];
306 retAttrs = [[[SaxAttributes alloc] init] autorelease];
307 attrEnum = [_attrs objectEnumerator];
308 while ((curAttr = [attrEnum nextObject])) {
309 [self _parseAttr:curAttr
312 intoValue:&mappedValue];
313 if ((oldValue = [attributes objectForKey:mappedAttr])) {
316 val = [NSString stringWithFormat:@"%@ %@",oldValue, mappedValue];
317 [attributes setObject:val forKey:mappedAttr];
320 [attributes setObject:mappedValue forKey:mappedAttr];
323 attrEnum = [attributes keyEnumerator];
324 while ((curAttr = [attrEnum nextObject])) {
325 [self _addAttribute:curAttr
326 value:[attributes objectForKey:curAttr]
330 [attributes release];
335 - (NSArray *)_beginTag:(NSString *)_tagName
336 withAttrs:(id<NSObject,SaxAttributes>)_attrs
338 NSArray *tag = [NSArray arrayWithObjects:@"BEGIN",_tagName,_attrs,NULL];
339 [self->elementList addObject:tag];
343 - (void)_endTag:(NSString *)_tagName {
344 [self->elementList addObject:
345 [NSArray arrayWithObjects:@"END",_tagName,NULL]];
348 - (void)_addSubItems:(NSArray *)_items withData:(NSString *)_content {
349 NSEnumerator *itemEnum, *contentEnum;
351 NSString *subContent;
353 itemEnum = [_items objectEnumerator];
354 contentEnum = [[_content componentsSeparatedByString:@";"] objectEnumerator];
356 while ((subTag=[itemEnum nextObject])) {
357 subContent = [contentEnum nextObject];
359 [self _beginTag:subTag withAttrs:nil];
360 if ([subContent length]>0)
361 [self->elementList addObject:
362 [NSArray arrayWithObjects:@"DATA", subContent, nil]];
363 [self _endTag:subTag];
367 - (void)_dataTag:(NSString *)_tagName
368 withAttrs:(id<NSObject,SaxAttributes>)_attrs
369 andContent:(NSString *)_content
373 _content = [stringFormatter stringByUnescapingRFC2445Text:_content];
374 if ([self->attributeElements containsObject:_tagName]) {
375 [self _addAttribute:_tagName value:_content];
378 [self _beginTag:_tagName withAttrs:_attrs];
379 if ([_content length] > 0) {
380 if ((subItems = [self->subItemMapping objectForKey:_tagName])) {
381 [self _addSubItems:subItems withData:_content];
384 [self->elementList addObject:
385 [NSArray arrayWithObjects:@"DATA", _content, nil]];
388 [self _endTag:_tagName];
392 - (void)_eventsForElements {
398 id<NSObject,SaxAttributes> attrs;
400 enu = [elementList objectEnumerator];
401 while ((obj = [enu nextObject])) {
402 type = [obj objectAtIndex:0];
403 name = [obj objectAtIndex:1];
406 attrs = [obj objectAtIndex:2];
410 if ([type isEqualToString:@"BEGIN"]) {
411 [self->contentHandler startElement:name
412 namespace:self->prefixURI
416 else if ([type isEqualToString:@"END"]) {
417 [self->contentHandler endElement:name
418 namespace:self->prefixURI
422 unsigned len = [name length];
423 chardata = malloc(len * sizeof(unichar));
424 [name getCharacters:chardata range:NSMakeRange(0, len)];
425 [self->contentHandler characters:chardata length:len];
430 [elementList removeAllObjects];
433 - (void)_parseLine:(NSString *)_line {
434 NSString *tagName, *tagValue;
435 NSMutableArray *tagAttributes;
436 NSRange r, todoRange;
439 length = [_line length];
440 todoRange = NSMakeRange(0, length);
441 r = [_line rangeOfCharacterFromSet:colonAndSemicolonCharSet
444 /* is line well-formed? */
447 NSLog(@"%s got an improper content line! ->\n%@",
454 tagName = [[_line substringToIndex:r.location] uppercaseString];
455 tagAttributes = [[NSMutableArray alloc] init];
457 /* possible shortcut: if we spotted a ':', we don't have to do "expensive"
458 argument scanning/processing.
460 if([_line characterAtIndex:r.location] != ':') {
461 BOOL isAtEnd = NO, isInDquote = NO;
462 unsigned start = NSMaxRange(r);
464 todoRange = NSMakeRange(start, length - start);
468 /* scan for parameters */
469 r = [_line rangeOfCharacterFromSet:colonSemicolonAndDquoteCharSet
472 /* is line well-formed? */
473 if(r.length == 0 || r.location == 0) {
475 NSLog(@"%s got an improper content line! ->\n%@",
479 [tagAttributes release];
482 /* first check if delimiter candidate is escaped */
483 if([_line characterAtIndex:(r.location - 1)] != '\\') {
487 delimiter = [_line characterAtIndex:r.location];
488 if(delimiter == '\"') {
489 /* not a real delimiter - toggle isInDquote for proper escaping */
490 isInDquote = !isInDquote;
494 /* is a delimiter, which one? */
496 if(delimiter == ':') {
499 copyRange = NSMakeRange(start, r.location - start);
500 [tagAttributes addObject:[_line substringWithRange:copyRange]];
502 /* adjust start, todoRange */
503 start = NSMaxRange(r);
504 todoRange = NSMakeRange(start, length - start);
510 /* adjust todoRange */
511 unsigned offset = NSMaxRange(r);
512 todoRange = NSMakeRange(offset, length - offset);
516 tagValue = [_line substringFromIndex:NSMaxRange(r)];
518 if ([tagName isEqualToString:@"BEGIN"]) {
521 tag = [self _beginTag:[self _mapTagName:tagValue]
522 withAttrs:[[[SaxAttributes alloc] init] autorelease]];
523 [self->cardStack addObject:tag];
525 else if ([tagName isEqualToString:@"END"]) {
528 mtName = [self _mapTagName:tagValue];
529 if([self->cardStack count] > 0) {
530 NSString *expectedName;
532 expectedName = [[self->cardStack lastObject] objectAtIndex:1];
533 if(![expectedName isEqualToString:mtName]) {
535 NSLog(@"%s found end tag '%@' which doesn't match expected name "
536 @"'%@'! Tag '%@' hasn't been closed properly. Given iCal "
537 @"document contains errors!",
543 /* probably futile attempt to parse anyways */
545 NSLog(@"%s trying to fix previous error by inserting bogus end "
547 __PRETTY_FUNCTION__);
549 [self _endTag:expectedName];
550 [self->cardStack removeLastObject];
555 NSLog(@"%s found end tag '%@' without any open tags left?!",
560 [self _endTag:mtName];
561 [self->cardStack removeLastObject];
562 if ([self->cardStack count] == 0)
563 [self _eventsForElements];
566 [self _dataTag:[self _mapTagName:tagName]
567 withAttrs:[self _mapAttrs:tagAttributes forTag:tagName]
568 andContent:tagValue];
570 [tagAttributes release];
573 - (void)_parseString:(NSString *)_rawString {
574 unsigned pos, length;
575 NSMutableString *line;
578 [self->contentHandler startDocument];
579 [self->contentHandler startPrefixMapping:@"" uri:self->prefixURI];
581 length = [_rawString length];
583 contentline = name *(";" param ) ":" value CRLF
584 ; When parsing a content line, folded lines MUST first
587 r = NSMakeRange(0, 0);
588 /* probably too optimistic */
589 line = [[NSMutableString alloc] initWithCapacity:75 + 2];
591 for(pos = 0; pos < length; pos++) {
592 unichar c = [_rawString characterAtIndex:pos];
595 if(((length - 1) - pos) >= 1) {
596 if([_rawString characterAtIndex:pos + 1] == '\n') {
597 BOOL isAtEndOfLine = YES;
598 /* test for folding first */
599 if(((length - 1) - pos) >= 2) {
600 unichar ws = [_rawString characterAtIndex:pos + 2];
601 isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO
604 /* assemble part of line up to pos */
606 [line appendString:[_rawString substringWithRange:r]];
610 r = NSMakeRange(pos + 1, 0); /* begin new range */
614 /* assemble part of line up to pos */
616 [line appendString:[_rawString substringWithRange:r]];
618 [self _parseLine:line];
620 [line deleteCharactersInRange:NSMakeRange(0, [line length])];
622 r = NSMakeRange(pos + 1, 0); /* begin new range */
627 /* garbled last line! */
629 NSLog(@"%s Last line is truncated, trying to parse anyways!",
630 __PRETTY_FUNCTION__);
634 else if(c == '\n') { /* broken, non-standard */
635 BOOL isAtEndOfLine = YES;
636 /* test for folding first */
637 if(((length - 1) - pos) >= 1) {
638 unichar ws = [_rawString characterAtIndex:pos + 1];
639 isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO
642 /* assemble part of line up to pos */
644 [line appendString:[_rawString substringWithRange:r]];
648 r = NSMakeRange(pos + 1, 0); /* begin new range */
652 /* assemble part of line up to pos */
654 [line appendString:[_rawString substringWithRange:r]];
656 [self _parseLine:line];
658 [line deleteCharactersInRange:NSMakeRange(0, [line length])];
659 r = NSMakeRange(pos + 1, 0); /* begin new range */
668 NSLog(@"%s Last line of iCal string is not properly terminated!",
669 __PRETTY_FUNCTION__);
671 [line appendString:[_rawString substringWithRange:r]];
672 [self _parseLine:line];
675 if([self->cardStack count] != 0) {
677 NSLog(@"%s found elements on cardStack. This indicates an improper "
678 @"iCal structure! Not all required events will have been "
679 @"generated, leading to unpredictable results!",
680 __PRETTY_FUNCTION__);
685 [self->contentHandler endPrefixMapping:@""];
686 [self->contentHandler endDocument];
689 - (void)parseFromSource:(id)_source {
691 NSLog(@"%s: parse: %@", __PRETTY_FUNCTION__, _source);
693 if ([_source isKindOfClass:[NSURL class]]) {
695 NSLog(@"%s: trying to load URL...",__PRETTY_FUNCTION__);
696 _source = [_source resourceDataUsingCache:NO];
699 if ([_source isKindOfClass:[NSData class]]) {
700 // FIXME: Data is not always utf-8.....
702 NSLog(@"%s: trying to decode data...",__PRETTY_FUNCTION__);
703 _source = [[[NSString alloc]
704 initWithData:_source encoding:NSUTF8StringEncoding]
708 if ([_source isKindOfClass:[NSString class]]) {
710 NSLog(@"%s: trying to parse string...",__PRETTY_FUNCTION__);
711 [self _parseString:_source];
715 NSLog(@"%s: unrecognizable source: %@", __PRETTY_FUNCTION__,_source);
716 // FIXME: Return Error
720 - (void)parseFromSource:(id)_source systemId:(NSString *)_sysId {
721 [self parseFromSource:_source];
724 - (void)parseFromSystemId:(NSString *)_sysId {
727 if ((url = [NSURL URLWithString:_sysId]))
728 [self parseFromSource:url systemId:_sysId];
733 - (BOOL)isDebuggingEnabled {
737 @end /* VersitSaxDriver */