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:@"OGoDebugVersitSaxDriver"];
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 attrName = [[_attr substringToIndex:r.location] uppercaseString];
252 attrValue = [_attr substringFromIndex:(r.location + 1)];
260 // ZNeK: what's this for?
261 r = [attrValue rangeOfCharacterFromSet:commaCharSet];
262 while (r.length > 0) {
263 [attrValue replaceCharactersInRange:r withString:@" "];
264 r = [attrValue rangeOfCharacterFromSet:commaCharSet];
268 mappedName = [self _mapAttrName:attrName forTag:_tagName];
270 *value_ = [stringFormatter stringByUnescapingRFC2445Text:attrValue];
273 - (id<NSObject,SaxAttributes>)_mapAttrs:(NSArray *)_attrs
274 forTag:(NSString *)_tagName
276 SaxAttributes *retAttrs;
277 NSEnumerator *attrEnum;
278 NSString *curAttr, *mappedAttr, *mappedValue, *oldValue;
279 NSMutableDictionary *attributes;
281 if (!_attrs || [_attrs count] == 0)
284 attributes = [[NSMutableDictionary alloc] init];
285 retAttrs = [[[SaxAttributes alloc] init] autorelease];
286 attrEnum = [_attrs objectEnumerator];
287 while ((curAttr = [attrEnum nextObject])) {
288 [self _parseAttr:curAttr
291 intoValue:&mappedValue];
292 if ((oldValue = [attributes objectForKey:mappedAttr])) {
295 val = [NSString stringWithFormat:@"%@ %@",oldValue, mappedValue];
296 [attributes setObject:val forKey:mappedAttr];
299 [attributes setObject:mappedValue forKey:mappedAttr];
302 attrEnum = [attributes keyEnumerator];
303 while ((curAttr = [attrEnum nextObject])) {
304 [self _addAttribute:curAttr
305 value:[attributes objectForKey:curAttr]
309 [attributes release];
314 - (NSArray *)_beginTag:(NSString *)_tagName
315 withAttrs:(id<NSObject,SaxAttributes>)_attrs
317 NSArray *tag = [NSArray arrayWithObjects:@"BEGIN",_tagName,_attrs,NULL];
318 [self->elementList addObject:tag];
322 - (void)_endTag:(NSString *)_tagName {
323 [self->elementList addObject:
324 [NSArray arrayWithObjects:@"END",_tagName,NULL]];
327 - (void)_addSubItems:(NSArray *)_items withData:(NSString *)_content {
328 NSEnumerator *itemEnum, *contentEnum;
330 NSString *subContent;
332 itemEnum = [_items objectEnumerator];
333 contentEnum = [[_content componentsSeparatedByString:@";"] objectEnumerator];
335 while ((subTag=[itemEnum nextObject])) {
336 subContent = [contentEnum nextObject];
338 [self _beginTag:subTag withAttrs:nil];
339 if ([subContent length]>0)
340 [self->elementList addObject:
341 [NSArray arrayWithObjects:@"DATA", subContent, nil]];
342 [self _endTag:subTag];
346 - (void)_dataTag:(NSString *)_tagName
347 withAttrs:(id<NSObject,SaxAttributes>)_attrs
348 andContent:(NSString *)_content
352 _content = [stringFormatter stringByUnescapingRFC2445Text:_content];
353 if ([self->attributeElements containsObject:_tagName]) {
354 [self _addAttribute:_tagName value:_content];
357 [self _beginTag:_tagName withAttrs:_attrs];
358 if ([_content length] > 0) {
359 if ((subItems = [self->subItemMapping objectForKey:_tagName])) {
360 [self _addSubItems:subItems withData:_content];
363 [self->elementList addObject:
364 [NSArray arrayWithObjects:@"DATA", _content, nil]];
367 [self _endTag:_tagName];
371 - (void)_eventsForElements {
377 id<NSObject,SaxAttributes> attrs;
379 enu = [elementList objectEnumerator];
380 while ((obj = [enu nextObject])) {
381 type = [obj objectAtIndex:0];
382 name = [obj objectAtIndex:1];
385 attrs = [obj objectAtIndex:2];
389 if ([type isEqualToString:@"BEGIN"]) {
390 [self->contentHandler startElement:name
391 namespace:self->prefixURI
395 else if ([type isEqualToString:@"END"]) {
396 [self->contentHandler endElement:name
397 namespace:self->prefixURI
401 unsigned len = [name length];
402 chardata = malloc(len * sizeof(unichar));
403 [name getCharacters:chardata range:NSMakeRange(0, len)];
404 [self->contentHandler characters:chardata length:len];
409 [elementList removeAllObjects];
412 - (void)_parseLine:(NSString *)_line {
413 NSString *tagName, *tagValue;
414 NSMutableArray *tagAttributes;
415 NSRange r, todoRange;
418 length = [_line length];
419 tagAttributes = [[NSMutableArray alloc] init];
420 todoRange = NSMakeRange(0, length);
421 r = [_line rangeOfCharacterFromSet:colonAndSemicolonCharSet
424 tagName = [[_line substringToIndex:r.location] uppercaseString];
425 if([_line characterAtIndex:r.location] != ':') {
426 BOOL isAtEnd = NO, isInDquote = NO;
427 unsigned start = NSMaxRange(r);
429 todoRange = NSMakeRange(start, length - start);
433 /* scan for parameters */
434 r = [_line rangeOfCharacterFromSet:colonSemicolonAndDquoteCharSet
437 /* first check if delimiter candidate is escaped */
438 if([_line characterAtIndex:(r.location - 1)] != '\\') {
442 delimiter = [_line characterAtIndex:r.location];
443 if(delimiter == '\"') {
444 /* not a real delimiter - toggle isInDquote for proper escaping */
445 isInDquote = !isInDquote;
449 /* is a delimiter, which one? */
451 if(delimiter == ':') {
454 copyRange = NSMakeRange(start, r.location - start);
455 [tagAttributes addObject:[_line substringWithRange:copyRange]];
457 /* adjust start, todoRange */
458 start = NSMaxRange(r);
459 todoRange = NSMakeRange(start, length - start);
465 /* adjust todoRange */
466 unsigned offset = NSMaxRange(r);
467 todoRange = NSMakeRange(offset, length - offset);
471 tagValue = [_line substringFromIndex:NSMaxRange(r)];
473 if ([tagName isEqualToString:@"BEGIN"]) {
476 tag = [self _beginTag:[self _mapTagName:tagValue]
477 withAttrs:[[[SaxAttributes alloc] init] autorelease]];
478 [self->cardStack addObject:tag];
480 else if ([tagName isEqualToString:@"END"]) {
481 [self _endTag:[self _mapTagName:tagValue]];
482 [self->cardStack removeLastObject];
483 if ([self->cardStack count] == 0)
484 [self _eventsForElements];
487 [self _dataTag:[self _mapTagName:tagName]
488 withAttrs:[self _mapAttrs:tagAttributes forTag:tagName]
489 andContent:tagValue];
491 [tagAttributes release];
494 - (void)_parseString:(NSString *)_rawString {
495 unsigned pos, length;
496 NSMutableString *line;
499 [self->contentHandler startDocument];
500 [self->contentHandler startPrefixMapping:@"" uri:self->prefixURI];
502 length = [_rawString length];
504 contentline = name *(";" param ) ":" value CRLF
505 ; When parsing a content line, folded lines MUST first
508 r = NSMakeRange(0, 0);
509 /* probably too optimistic */
510 line = [[NSMutableString alloc] initWithCapacity:75 + 2];
512 for(pos = 0; pos < length; pos++) {
513 unichar c = [_rawString characterAtIndex:pos];
516 if(((length - 1) - pos) >= 1) {
517 if([_rawString characterAtIndex:pos + 1] == '\n') {
518 BOOL isAtEndOfLine = YES;
519 /* test for folding first */
520 if(((length - 1) - pos) >= 2) {
521 unichar ws = [_rawString characterAtIndex:pos + 2];
522 isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO
525 /* assemble part of line up to pos */
527 [line appendString:[_rawString substringWithRange:r]];
531 r = NSMakeRange(pos + 1, 0); /* begin new range */
535 /* assemble part of line up to pos */
537 [line appendString:[_rawString substringWithRange:r]];
539 [self _parseLine:line];
541 [line deleteCharactersInRange:NSMakeRange(0, [line length])];
543 r = NSMakeRange(pos + 1, 0); /* begin new range */
548 /* garbled last line! */
550 NSLog(@"%s Last line is truncated, trying to parse anyways!",
551 __PRETTY_FUNCTION__);
555 else if(c == '\n') { /* broken, non-standard */
556 BOOL isAtEndOfLine = YES;
557 /* test for folding first */
558 if(((length - 1) - pos) >= 1) {
559 unichar ws = [_rawString characterAtIndex:pos + 1];
560 isAtEndOfLine = [whitespaceCharSet characterIsMember:ws] ? NO
563 /* assemble part of line up to pos */
565 [line appendString:[_rawString substringWithRange:r]];
569 r = NSMakeRange(pos + 1, 0); /* begin new range */
573 /* assemble part of line up to pos */
575 [line appendString:[_rawString substringWithRange:r]];
577 [self _parseLine:line];
579 [line deleteCharactersInRange:NSMakeRange(0, [line length])];
580 r = NSMakeRange(pos + 1, 0); /* begin new range */
589 NSLog(@"%s Last line of iCal string is not properly terminated!",
590 __PRETTY_FUNCTION__);
592 [line appendString:[_rawString substringWithRange:r]];
593 [self _parseLine:line];
597 [self->contentHandler endPrefixMapping:@""];
598 [self->contentHandler endDocument];
601 - (void)parseFromSource:(id)_source {
603 NSLog(@"%s: parse: %@", __PRETTY_FUNCTION__, _source);
605 if ([_source isKindOfClass:[NSURL class]]) {
607 NSLog(@"%s: trying to load URL...",__PRETTY_FUNCTION__);
608 _source = [_source resourceDataUsingCache:NO];
611 if ([_source isKindOfClass:[NSData class]]) {
612 // FIXME: Data is not always utf-8.....
614 NSLog(@"%s: trying to decode data...",__PRETTY_FUNCTION__);
615 _source = [[[NSString alloc]
616 initWithData:_source encoding:NSUTF8StringEncoding]
620 if ([_source isKindOfClass:[NSString class]]) {
622 NSLog(@"%s: trying to parse string...",__PRETTY_FUNCTION__);
623 [self _parseString:_source];
627 NSLog(@"%s: unrecognizable source: %@", __PRETTY_FUNCTION__,_source);
628 // FIXME: Return Error
632 - (void)parseFromSource:(id)_source systemId:(NSString *)_sysId {
633 [self parseFromSource:_source];
636 - (void)parseFromSystemId:(NSString *)_sysId {
639 if ((url = [NSURL URLWithString:_sysId]))
640 [self parseFromSource:url systemId:_sysId];
645 - (BOOL)isDebuggingEnabled {
649 @end /* VersitSaxDriver */