diff --git a/SOPE/NGCards/ChangeLog b/SOPE/NGCards/ChangeLog index 20ec67b1f..01a0a820c 100644 --- a/SOPE/NGCards/ChangeLog +++ b/SOPE/NGCards/ChangeLog @@ -1,3 +1,11 @@ +2010-06-04 Wolfgang Sourdeau + + * iCalXMLRenderer.m: new class module for exporting iCalCalendar + objects as XML files. + + * iCalUTCOffset.m: new class module for handling of the + "tzoffsetfrom" and "tzoffsetto" elements. + 2010-05-05 Wolfgang Sourdeau * iCalPerson.m (-setParticipationStatus:, -participationStatus): diff --git a/SOPE/NGCards/GNUmakefile b/SOPE/NGCards/GNUmakefile index e35b4f71a..3691f5fff 100644 --- a/SOPE/NGCards/GNUmakefile +++ b/SOPE/NGCards/GNUmakefile @@ -26,7 +26,9 @@ libNGCards_HEADER_FILES = \ \ CardElement.h \ CardGroup.h \ + \ CardVersitRenderer.h \ + iCalXMLRenderer.h \ \ NGCards.h \ iCalAlarm.h \ @@ -49,6 +51,7 @@ libNGCards_HEADER_FILES = \ iCalTimeZonePeriod.h \ iCalToDo.h \ iCalTrigger.h \ + iCalUTCOffset.h \ \ NSCalendarDate+ICal.h \ \ @@ -75,7 +78,9 @@ libNGCards_OBJC_FILES = \ \ CardElement.m \ CardGroup.m \ + \ CardVersitRenderer.m \ + iCalXMLRenderer.m \ \ iCalAlarm.m \ iCalAttachment.m \ @@ -99,6 +104,7 @@ libNGCards_OBJC_FILES = \ iCalTimeZonePeriod.m \ iCalToDo.m \ iCalTrigger.m \ + iCalUTCOffset.m \ iCalWeeklyRecurrenceCalculator.m\ iCalYearlyRecurrenceCalculator.m\ \ diff --git a/SOPE/NGCards/iCalTimeZonePeriod.m b/SOPE/NGCards/iCalTimeZonePeriod.m index f36ef8efb..ad46e3545 100644 --- a/SOPE/NGCards/iCalTimeZonePeriod.m +++ b/SOPE/NGCards/iCalTimeZonePeriod.m @@ -27,6 +27,7 @@ #import "iCalDateTime.h" #import "iCalRecurrenceRule.h" #import "iCalByDayMask.h" +#import "iCalUTCOffset.h" #import "iCalTimeZonePeriod.h" @@ -40,9 +41,10 @@ tagClass = [iCalRecurrenceRule class]; else if ([classTag isEqualToString: @"DTSTART"]) tagClass = [iCalDateTime class]; - else if ([classTag isEqualToString: @"TZNAME"] - || [classTag isEqualToString: @"TZOFFSETFROM"] + else if ([classTag isEqualToString: @"TZOFFSETFROM"] || [classTag isEqualToString: @"TZOFFSETTO"]) + tagClass = [iCalUTCOffset class]; + else if ([classTag isEqualToString: @"TZNAME"]) tagClass = [CardElement class]; else tagClass = [super classForTag: classTag]; diff --git a/SOPE/NGCards/iCalUTCOffset.h b/SOPE/NGCards/iCalUTCOffset.h new file mode 100644 index 000000000..d6d4c4fc8 --- /dev/null +++ b/SOPE/NGCards/iCalUTCOffset.h @@ -0,0 +1,31 @@ +/* iCalUTCOffset.h - this file is part of SOPE + * + * Copyright (C) 2010 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef ICALUTCOFFSET_H +#define ICALUTCOFFSET_H + +#import "CardElement.h" + +@interface iCalUTCOffset : CardElement +@end + +#endif /* ICALUTCOFFSET_H */ diff --git a/SOPE/NGCards/iCalUTCOffset.m b/SOPE/NGCards/iCalUTCOffset.m new file mode 100644 index 000000000..703e3e396 --- /dev/null +++ b/SOPE/NGCards/iCalUTCOffset.m @@ -0,0 +1,27 @@ +/* iCalUTCOffset.m - this file is part of $PROJECT_NAME_HERE$ + * + * Copyright (C) 2010 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import "iCalUTCOffset.h" + +@implementation iCalUTCOffset + +@end diff --git a/SOPE/NGCards/iCalXMLRenderer.h b/SOPE/NGCards/iCalXMLRenderer.h new file mode 100644 index 000000000..c19ac4f29 --- /dev/null +++ b/SOPE/NGCards/iCalXMLRenderer.h @@ -0,0 +1,41 @@ +/* iCalXMLRenderer.h - this file is part of SOPE + * + * Copyright (C) 2010 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef ICALXMLRENDERER_H +#define ICALXMLRENDERER_H + +#import + +@class NSMutableString; +@class NSString; + +@class iCalCalendar; + +@interface iCalXMLRenderer : NSObject + ++ (iCalXMLRenderer *) sharedXMLRenderer; + +- (NSString *) render: (iCalCalendar *) calendar; + +@end + +#endif /* ICALXMLRENDERER_H */ diff --git a/SOPE/NGCards/iCalXMLRenderer.m b/SOPE/NGCards/iCalXMLRenderer.m new file mode 100644 index 000000000..50fa9436a --- /dev/null +++ b/SOPE/NGCards/iCalXMLRenderer.m @@ -0,0 +1,438 @@ +/* iCalXMLRenderer.m - this file is part of SOPE + * + * Copyright (C) 2006 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* This class implements most of the XML iCalendar spec as defined here: + http://tools.ietf.org/html/draft-daboo-et-al-icalendar-in-xml-04 */ + +#import +#import +#import +#import + +#import +#import + +#import "CardElement.h" +#import "CardGroup.h" +#import "iCalDateTime.h" +#import "iCalPerson.h" +#import "iCalRecurrenceRule.h" +#import "iCalUTCOffset.h" + +#import "iCalXMLRenderer.h" + +@interface CardElement (iCalXMLExtension) + +- (NSString *) xmlRender; + +@end + +@interface iCalXMLRenderer (PrivateAPI) + +- (NSString *) renderElement: (CardElement *) anElement; +- (NSString *) renderGroup: (CardGroup *) aGroup; + +@end + +@implementation iCalXMLRenderer + ++ (iCalXMLRenderer *) sharedXMLRenderer +{ + static iCalXMLRenderer *sharedXMLRenderer = nil; + + if (!sharedXMLRenderer) + sharedXMLRenderer = [self new]; + + return sharedXMLRenderer; +} + +- (NSString *) render: (iCalCalendar *) calendar +{ + return [NSString stringWithFormat: + @"\n" + @"" + @"%@" + @"", + [calendar xmlRender]]; +} + +@end + +@implementation CardElement (iCalXMLExtension) + +- (NSString *) xmlValueTag +{ + return @"text"; +} + +- (NSString *) xmlParameterTag: (NSString *) paramName +{ + return nil; +} + +- (void) _appendPaddingValues: (int) max + withTag: (NSString *) valueTag + intoString: (NSMutableString *) rendering +{ + int count; + + for (count = 0; count < max; count++) + [rendering appendFormat: @"<%@/>"]; +} + +- (NSString *) _xmlRenderParameter: (NSString *) paramName +{ + NSMutableString *rendering; + NSArray *paramValues; + NSString *lowerName, *paramTypeTag, *escapedValue; + int count, max; + + paramValues = [attributes objectForKey: paramName]; + max = [paramValues count]; + if (max > 0) + { + lowerName = [paramName lowercaseString]; + rendering = [NSMutableString stringWithCapacity: 32]; + paramTypeTag = [self xmlParameterTag: [paramName lowercaseString]]; + for (count = 0; count < max; count++) + { + [rendering appendFormat: @"<%@>", lowerName]; + if (paramTypeTag) + [rendering appendFormat: @"<%@>", paramTypeTag]; + escapedValue = [[paramValues objectAtIndex: count] + stringByEscapingXMLString]; + [rendering appendFormat: @"%@", escapedValue]; + if (paramTypeTag) + [rendering appendFormat: @"", paramTypeTag]; + [rendering appendFormat: @"", lowerName]; + } + } + else + rendering = nil; + + return rendering; +} + +- (NSString *) _xmlRenderParameters +{ + NSArray *keys; + NSMutableArray *renderedValues; + NSMutableString *rendering; + NSString *currentValue; + int count, max; + BOOL displayed; + + keys = [attributes allKeys]; + max = [keys count]; + if (max > 0) + { + rendering = [NSMutableString stringWithCapacity: 64]; + renderedValues = [NSMutableArray arrayWithCapacity: max]; + displayed = NO; + for (count = 0; count < max; count++) + { + currentValue + = [self _xmlRenderParameter: [keys objectAtIndex: count]]; + if ([currentValue length] > 0) + [rendering appendString: currentValue]; + } + } + else + rendering = nil; + + return rendering; +} + +- (NSString *) _xmlRenderValue +{ + NSMutableString *rendering; + NSString *valueTag, *currentValue; + int count, max; + BOOL displayed; + + valueTag = [self xmlValueTag]; + rendering = [NSMutableString stringWithCapacity: 64]; + max = [values count]; + displayed = NO; + for (count = 0; count < max; count++) + { + currentValue = [[values objectAtIndex: count] + stringByEscapingXMLString]; + if ([currentValue length] > 0) + { + if (!displayed) + { + [self _appendPaddingValues: count withTag: valueTag + intoString: rendering]; + displayed = YES; + } + [rendering appendFormat: @"<%@>%@", + valueTag, currentValue, valueTag]; + } + } + + return rendering; +} + +- (NSString *) xmlRender +{ + NSMutableString *rendering; + NSString *lowerTag, *rParameters, *value; + + rParameters = [self _xmlRenderParameters]; + value = [self _xmlRenderValue]; + if ([value length]) + { + rendering = [NSMutableString stringWithCapacity: 128]; + lowerTag = [tag lowercaseString]; + [rendering appendFormat: @"<%@>", lowerTag]; + if ([rParameters length] > 0) + [rendering appendFormat: @"%@", + rParameters]; + [rendering appendString: value]; + [rendering appendFormat: @"", lowerTag]; + } + else + rendering = nil; + + return rendering; +} + +@end + +@implementation iCalDateTime (iCalXMLExtension) + +- (NSString *) xmlValueTag +{ + return ([self isAllDay] ? @"date" : @"date-time"); +} + +@end + +@implementation iCalPerson (iCalXMLExtension) + +- (NSString *) xmlParameterTag: (NSString *) paramName +{ + NSString *paramTag; + + if ([paramName isEqualToString: @"delegated-from"] + || [paramName isEqualToString: @"delegated-to"] + || [paramName isEqualToString: @"sent-by"]) + paramTag = @"cal-address"; + else + paramTag = [super xmlParameterTag: paramName]; + + return paramTag; +} + +- (NSString *) xmlValueTag +{ + return @"cal-address"; +} + +@end + +@implementation iCalRecurrenceRule (iCalXMLExtension) + +- (NSString *) _xmlRenderValue +{ + NSMutableString *rendering; + NSArray *valueParts; + NSString *valueTag, *currentValue; + int count, max; + + max = [values count]; + rendering = [NSMutableString stringWithCapacity: 64]; + for (count = 0; count < max; count++) + { + currentValue = [[values objectAtIndex: count] + stringByEscapingXMLString]; + if ([currentValue length] > 0) + { + valueParts = [currentValue componentsSeparatedByString: @"="]; + if ([valueParts count] == 2) + { + valueTag = [[valueParts objectAtIndex: 0] lowercaseString]; + [rendering appendFormat: @"<%@>%@", + valueTag, + [valueParts objectAtIndex: 1], + valueTag]; + } + } + } + + return rendering; +} + +@end + +@implementation iCalUTCOffset (iCalXMLExtension) + +- (NSString *) xmlValueTag +{ + return @"utc-offset"; +} + +@end + +@implementation CardGroup (iCalXMLExtension) + +- (NSString *) xmlRender +{ + NSString *lowerTag; + int count, max; + NSMutableString *rendering; + NSMutableArray *properties, *components; + CardElement *currentChild; + + rendering = [NSMutableString stringWithCapacity: 4096]; + max = [children count]; + if (max > 0) + { + properties = [[NSMutableArray alloc] initWithCapacity: max]; + components = [[NSMutableArray alloc] initWithCapacity: max]; + for (count = 0; count < max; count++) + { + currentChild = [children objectAtIndex: count]; + if ([currentChild isKindOfClass: [CardGroup class]]) + [components addObject: [currentChild xmlRender]]; + else + [properties addObject: [currentChild xmlRender]]; + } + + lowerTag = [tag lowercaseString]; + [rendering appendFormat: @"<%@>", lowerTag]; + if ([properties count] > 0) + [rendering appendFormat: @"%@", + [properties componentsJoinedByString: @""]]; + if ([components count] > 0) + [rendering appendFormat: @"%@", + [components componentsJoinedByString: @""]]; + [rendering appendFormat: @"", lowerTag]; + } + + return rendering; +} + +@end + +// - (NSString *) renderElement: (CardElement *) anElement +// { +// NSMutableString *rendering; +// NSDictionary *attributes; +// NSEnumerator *keys; +// NSArray *values, *renderedAttrs; +// NSString *key, *finalRendering, *tag; + +// if (![anElement isVoid]) +// { +// rendering = [NSMutableString string]; +// if ([anElement group]) +// [rendering appendFormat: @"%@.", [anElement group]]; +// tag = [anElement tag]; +// if (!(tag && [tag length])) +// { +// tag = @""; +// [self warnWithFormat: @"card element of class '%@' has an empty tag", +// NSStringFromClass([anElement class])]; +// } + +// [rendering appendString: [tag uppercaseString]]; +// attributes = [anElement attributes]; +// keys = [[attributes allKeys] objectEnumerator]; +// while ((key = [keys nextObject])) +// { +// NSString *s; +// int i, c; + +// renderedAttrs = [[attributes objectForKey: key] renderedForCards]; +// c = [renderedAttrs count]; +// if (c > 0) +// { +// [rendering appendFormat: @";%@=", [key uppercaseString]]; + +// for (i = 0; i < c; i++) +// { +// s = [renderedAttrs objectAtIndex: i]; + +// /* We MUST quote attribute values that have a ":" in them +// and that not already quoted */ +// if ([s length] > 2 && [s rangeOfString: @":"].length && +// [s characterAtIndex: 0] != '"' && ![s hasSuffix: @"\""]) +// s = [NSString stringWithFormat: @"\"%@\"", s]; + +// [rendering appendFormat: @"%@", s]; + +// if (i+1 < c) +// [rendering appendString: @","]; +// } +// } +// } + +// values = [anElement values]; +// if ([values count] > 0) +// [rendering appendFormat: @":%@", +// [[values renderedForCards] componentsJoinedByString: @";"]]; + +// if ([rendering length] > 0) +// [rendering appendString: @"\r\n"]; + +// finalRendering = [rendering foldedForVersitCards]; +// } +// else +// finalRendering = @""; + +// return finalRendering; +// } + +// - (NSString *) renderGroup: (CardGroup *) aGroup +// { +// NSEnumerator *children; +// CardElement *currentChild; +// NSMutableString *rendering; +// NSString *groupTag; + +// rendering = [NSMutableString string]; + +// groupTag = [aGroup tag]; +// if (!(groupTag && [groupTag length])) +// { +// groupTag = @""; +// [self warnWithFormat: @"card group of class '%@' has an empty tag", +// NSStringFromClass([aGroup class])]; +// } + +// groupTag = [groupTag uppercaseString]; +// [rendering appendFormat: @"BEGIN:%@\r\n", groupTag]; +// children = [[aGroup children] objectEnumerator]; +// currentChild = [children nextObject]; +// while (currentChild) +// { +// [rendering appendString: [self render: currentChild]]; +// currentChild = [children nextObject]; +// } +// [rendering appendFormat: @"END:%@\r\n", groupTag]; + +// return rendering; +// } + +// @end