From b7685686a41013d9ed2242b76e3b49835cbd8066 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Tue, 30 Oct 2007 14:16:51 +0000 Subject: [PATCH 1/6] Monotone-Parent: 8ef0ab24c4c10895d5e911f82260ba93fedbb853 Monotone-Revision: 42b9d80ac08dee2bf98006223a87c3a37a8ace44 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2007-10-30T14:16:51 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 5 +++++ UI/MailerUI/UIxMailActions.m | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 3d8206ef9..446f3fdc1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2007-10-30 Wolfgang Sourdeau + + * UI/MailerUI/UIxMailActions.m ([-replyToAllAction]): invoke + "replyToAll:" with YES as parameter instead of NO. + 2007-10-29 Wolfgang Sourdeau * SoObjects/Mailer/SOGoMailBodyPart.m ([SOGoMailBodyPart diff --git a/UI/MailerUI/UIxMailActions.m b/UI/MailerUI/UIxMailActions.m index 4e962e432..4ab7843c3 100644 --- a/UI/MailerUI/UIxMailActions.m +++ b/UI/MailerUI/UIxMailActions.m @@ -66,7 +66,7 @@ - (WOResponse *) replyToAllAction { - return [self replyToAll: NO]; + return [self replyToAll: YES]; } - (WOResponse *) forwardAction From de698e6a1a3de11b0e27a8767bc3239019328d3f Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Tue, 30 Oct 2007 17:07:39 +0000 Subject: [PATCH 2/6] Monotone-Parent: 42b9d80ac08dee2bf98006223a87c3a37a8ace44 Monotone-Revision: ba9e6391ee9c1e0fe0680835dcc1e917cea277fb Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2007-10-30T17:07:39 Monotone-Branch: ca.inverse.sogo --- SoObjects/Mailer/SOGoMailObject.m | 43 ++++++++++++++++++------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index e126222f3..c86781f59 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -398,11 +398,11 @@ static BOOL debugSoParts = NO; NSData *content; id result, fullResult; - fullResult = [self fetchParts:[NSArray arrayWithObject: @"RFC822"]]; + fullResult = [self fetchParts: [NSArray arrayWithObject: @"RFC822"]]; if (fullResult == nil) return nil; - if ([fullResult isKindOfClass:[NSException class]]) + if ([fullResult isKindOfClass: [NSException class]]) return fullResult; /* extract fetch result */ @@ -440,23 +440,30 @@ static BOOL debugSoParts = NO; - (NSString *) contentAsString { - NSString *s; + id s; NSData *content; - - if ((content = [self content]) == nil) - return nil; - if ([content isKindOfClass:[NSException class]]) - return (id)content; - - s = [[NSString alloc] initWithData: content - encoding: NSISOLatin1StringEncoding]; - if (s == nil) { - [self logWithFormat: - @"ERROR: could not convert data of length %d to string", - [content length]]; - return nil; - } - return [s autorelease]; + + content = [self content]; + if (content) + { + if ([content isKindOfClass: [NSData class]]) + { + s = [[NSString alloc] initWithData: content + encoding: NSISOLatin1StringEncoding]; + if (s) + [s autorelease]; + else + [self logWithFormat: + @"ERROR: could not convert data of length %d to string", + [content length]]; + } + else + s = content; + } + else + s = nil; + + return s; } /* bulk fetching of plain/text content */ From 040e7dda383a79466e254555c1854efd147fac40 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Tue, 30 Oct 2007 17:24:59 +0000 Subject: [PATCH 3/6] Monotone-Parent: ba9e6391ee9c1e0fe0680835dcc1e917cea277fb Monotone-Revision: 0f7a7062f9e521fc44b55467166775907a3e3668 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2007-10-30T17:24:59 Monotone-Branch: ca.inverse.sogo --- .../UIxMailPartAlternativeViewer.m | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/UI/MailPartViewers/UIxMailPartAlternativeViewer.m b/UI/MailPartViewers/UIxMailPartAlternativeViewer.m index 182894f89..5dc10f575 100644 --- a/UI/MailPartViewers/UIxMailPartAlternativeViewer.m +++ b/UI/MailPartViewers/UIxMailPartAlternativeViewer.m @@ -50,15 +50,15 @@ @implementation UIxMailPartAlternativeViewer - (void)dealloc { - [self->childInfo release]; + [childInfo release]; [super dealloc]; } /* caches */ - (void)resetBodyInfoCaches { - [self->childInfo release]; self->childInfo = nil; - self->childIndex = 0; + [childInfo release]; childInfo = nil; + childIndex = 0; [super resetBodyInfoCaches]; } @@ -110,8 +110,8 @@ - (void)selectChildInfo { unsigned idx; - [self->childInfo release]; self->childInfo = nil; - self->childIndex = 0; + [childInfo release]; childInfo = nil; + childIndex = 0; idx = [self selectPartIndexFromTypes:[self childPartTypes]]; if (idx == NSNotFound) { @@ -120,25 +120,25 @@ return; } - self->childIndex = idx + 1; - self->childInfo = + childIndex = idx + 1; + childInfo = [[[[self bodyInfo] valueForKey:@"parts"] objectAtIndex:idx] retain]; } /* accessors */ - (id)childInfo { - if (self->childInfo == nil) + if (childInfo == nil) [self selectChildInfo]; - return self->childInfo; + return childInfo; } - (unsigned int)childIndex { - if (self->childIndex == 0) + if (childIndex == 0) [self selectChildInfo]; - return self->childIndex - 1; + return childIndex - 1; } - (NSString *)childPartName { From af740904e22d00a37d85f0c62c563c566508f469 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Tue, 30 Oct 2007 17:43:34 +0000 Subject: [PATCH 4/6] Monotone-Parent: 0f7a7062f9e521fc44b55467166775907a3e3668 Monotone-Revision: 3afd5e21d96b09fb0b8e7c1fd319dcce3cd06d3b Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2007-10-30T17:43:34 Monotone-Branch: ca.inverse.sogo --- .../UIxMailPartAlternativeViewer.m | 154 +++++++++++------- 1 file changed, 91 insertions(+), 63 deletions(-) diff --git a/UI/MailPartViewers/UIxMailPartAlternativeViewer.m b/UI/MailPartViewers/UIxMailPartAlternativeViewer.m index 5dc10f575..a2cff584b 100644 --- a/UI/MailPartViewers/UIxMailPartAlternativeViewer.m +++ b/UI/MailPartViewers/UIxMailPartAlternativeViewer.m @@ -41,7 +41,7 @@ @interface UIxMailPartAlternativeViewer : UIxMailPartViewer { - id childInfo; + id childInfo; unsigned int childIndex; } @@ -49,120 +49,148 @@ @implementation UIxMailPartAlternativeViewer -- (void)dealloc { +- (void) dealloc +{ [childInfo release]; [super dealloc]; } /* caches */ -- (void)resetBodyInfoCaches { - [childInfo release]; childInfo = nil; +- (void) resetBodyInfoCaches +{ + [childInfo release]; + childInfo = nil; childIndex = 0; + [super resetBodyInfoCaches]; } /* part selection */ -- (NSArray *)childPartTypes { +- (NSArray *) childPartTypes +{ NSMutableArray *types; - unsigned i, count; - NSArray *childParts; + NSArray *childParts; + NSEnumerator *allParts; + NSString *mt, *st; + NSDictionary *currentPart; - childParts = [[self bodyInfo] valueForKey:@"parts"]; - count = [childParts count]; - types = [NSMutableArray arrayWithCapacity:count]; - - for (i = 0; i < count; i++) { - NSString *mt, *st; + types = [NSMutableArray array]; + + childParts = [[self bodyInfo] valueForKey: @"parts"]; + allParts = [childParts objectEnumerator]; + + while ((currentPart = [allParts nextObject])) + { + mt = [[currentPart valueForKey:@"type"] lowercaseString]; + st = [[currentPart valueForKey:@"subtype"] lowercaseString]; + [types addObject: [NSString stringWithFormat: @"%@/%@", mt, st]]; + } - mt = [[[childParts objectAtIndex:i] valueForKey:@"type"] lowercaseString]; - st = [[[childParts objectAtIndex:i] valueForKey:@"subtype"] - lowercaseString]; - mt = [[mt stringByAppendingString:@"/"] stringByAppendingString:st]; - [types addObject:mt ? mt : (id)[NSNull null]]; - } return types; } -- (int)selectPartIndexFromTypes:(NSArray *)_types { +- (int) selectPartIndexFromTypes: (NSArray *) types +{ /* returns the index of the selected part or NSNotFound */ - unsigned i, count; + unsigned int count, max; + int index; - if ((count = [_types count]) == 0) - return NSNotFound; - - if ((i = [_types indexOfObject:@"text/html"]) != NSNotFound) - return i; - if ((i = [_types indexOfObject:@"text/plain"]) != NSNotFound) - return i; + index = -1; - /* then we scan for other text types and choose the first one found */ - for (i = 0; i < count; i++) { - if ([(NSString *)[_types objectAtIndex:i] hasPrefix:@"text/"]) - return i; - } - - /* as a fallback, we select the first available part */ - return 0; + max = [types count]; + if (max > 0) + { + index = [types indexOfObject: @"text/html"]; + if (index == NSNotFound) + { + index = [types indexOfObject: @"text/plain"]; + if (index == NSNotFound) + { + count = 0; + while (index == -1 + && count < max) + if ([[types objectAtIndex: count] hasPrefix: @"text/"]) + index = count; + else + count++; + if (index == -1) + index = 0; + } + } + else + index = count; + } + else + index = NSNotFound; + + return index; } -- (void)selectChildInfo { - unsigned idx; +- (void) selectChildInfo +{ + int idx; - [childInfo release]; childInfo = nil; + [childInfo release]; + childInfo = nil; childIndex = 0; - idx = [self selectPartIndexFromTypes:[self childPartTypes]]; - if (idx == NSNotFound) { - [self errorWithFormat:@"could not select a part of types: %@", - [self childPartTypes]]; - return; - } - - childIndex = idx + 1; - childInfo = - [[[[self bodyInfo] valueForKey:@"parts"] objectAtIndex:idx] retain]; + idx = [self selectPartIndexFromTypes: [self childPartTypes]]; + if (idx == NSNotFound) + [self errorWithFormat: @"could not select a part of types: %@", + [self childPartTypes]]; + else + { + childIndex = idx + 1; + childInfo = [[[self bodyInfo] valueForKey:@"parts"] objectAtIndex: idx]; + [childInfos retain]; + } } /* accessors */ -- (id)childInfo { - if (childInfo == nil) +- (id) childInfo +{ + if (!childInfo) [self selectChildInfo]; return childInfo; } -- (unsigned int)childIndex { - if (childIndex == 0) +- (unsigned int) childIndex +{ + if (!childIndex) [self selectChildInfo]; - + return childIndex - 1; } -- (NSString *)childPartName { - char buf[8]; - sprintf(buf, "%d", [self childIndex] + 1); - return [NSString stringWithCString:buf]; +- (NSString *) childPartName +{ + return [NSString stringWithFormat: @"%d", ([self childIndex] + 1)]; } -- (id)childPartPath { +- (id) childPartPath +{ NSArray *pp; pp = [self partPath]; + return [pp count] > 0 - ? [pp arrayByAddingObject:[self childPartName]] - : [NSArray arrayWithObject:[self childPartName]]; + ? [pp arrayByAddingObject: [self childPartName]] + : [NSArray arrayWithObject: [self childPartName]]; } /* nested viewers */ -- (id)contentViewerComponent { +- (id) contentViewerComponent +{ id info; info = [self childInfo]; - return [[[self context] mailRenderingContext] viewerForBodyInfo:info]; + + return [[context mailRenderingContext] viewerForBodyInfo:info]; } @end /* UIxMailPartAlternativeViewer */ From cb099612754e7a64db4d2e5ba8f835d138626a79 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Tue, 30 Oct 2007 19:31:35 +0000 Subject: [PATCH 5/6] Monotone-Parent: 3afd5e21d96b09fb0b8e7c1fd319dcce3cd06d3b Monotone-Revision: 3291dd3b0291e6b09f19d1879b5f7a56fd0f5045 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2007-10-30T19:31:35 Monotone-Branch: ca.inverse.sogo --- .../UIxMailPartAlternativeViewer.m | 154 +++++++----------- 1 file changed, 63 insertions(+), 91 deletions(-) diff --git a/UI/MailPartViewers/UIxMailPartAlternativeViewer.m b/UI/MailPartViewers/UIxMailPartAlternativeViewer.m index a2cff584b..f93a18f7e 100644 --- a/UI/MailPartViewers/UIxMailPartAlternativeViewer.m +++ b/UI/MailPartViewers/UIxMailPartAlternativeViewer.m @@ -41,7 +41,7 @@ @interface UIxMailPartAlternativeViewer : UIxMailPartViewer { - id childInfo; + id childInfo; unsigned int childIndex; } @@ -49,148 +49,120 @@ @implementation UIxMailPartAlternativeViewer -- (void) dealloc -{ +- (void)dealloc { [childInfo release]; [super dealloc]; } /* caches */ -- (void) resetBodyInfoCaches -{ - [childInfo release]; - childInfo = nil; +- (void)resetBodyInfoCaches { + [childInfo release]; childInfo = nil; childIndex = 0; - [super resetBodyInfoCaches]; } /* part selection */ -- (NSArray *) childPartTypes -{ +- (NSArray *)childPartTypes { NSMutableArray *types; - NSArray *childParts; - NSEnumerator *allParts; - NSString *mt, *st; - NSDictionary *currentPart; + unsigned i, count; + NSArray *childParts; - types = [NSMutableArray array]; - - childParts = [[self bodyInfo] valueForKey: @"parts"]; - allParts = [childParts objectEnumerator]; - - while ((currentPart = [allParts nextObject])) - { - mt = [[currentPart valueForKey:@"type"] lowercaseString]; - st = [[currentPart valueForKey:@"subtype"] lowercaseString]; - [types addObject: [NSString stringWithFormat: @"%@/%@", mt, st]]; - } + childParts = [[self bodyInfo] valueForKey:@"parts"]; + count = [childParts count]; + types = [NSMutableArray arrayWithCapacity:count]; + + for (i = 0; i < count; i++) { + NSString *mt, *st; + mt = [[[childParts objectAtIndex:i] valueForKey:@"type"] lowercaseString]; + st = [[[childParts objectAtIndex:i] valueForKey:@"subtype"] + lowercaseString]; + mt = [[mt stringByAppendingString:@"/"] stringByAppendingString:st]; + [types addObject:mt ? mt : (id)[NSNull null]]; + } return types; } -- (int) selectPartIndexFromTypes: (NSArray *) types -{ +- (int)selectPartIndexFromTypes:(NSArray *)_types { /* returns the index of the selected part or NSNotFound */ - unsigned int count, max; - int index; + unsigned i, count; - index = -1; + if ((count = [_types count]) == 0) + return NSNotFound; + + if ((i = [_types indexOfObject:@"text/html"]) != NSNotFound) + return i; + if ((i = [_types indexOfObject:@"text/plain"]) != NSNotFound) + return i; - max = [types count]; - if (max > 0) - { - index = [types indexOfObject: @"text/html"]; - if (index == NSNotFound) - { - index = [types indexOfObject: @"text/plain"]; - if (index == NSNotFound) - { - count = 0; - while (index == -1 - && count < max) - if ([[types objectAtIndex: count] hasPrefix: @"text/"]) - index = count; - else - count++; - if (index == -1) - index = 0; - } - } - else - index = count; - } - else - index = NSNotFound; - - return index; + /* then we scan for other text types and choose the first one found */ + for (i = 0; i < count; i++) { + if ([(NSString *)[_types objectAtIndex:i] hasPrefix:@"text/"]) + return i; + } + + /* as a fallback, we select the first available part */ + return 0; } -- (void) selectChildInfo -{ - int idx; +- (void)selectChildInfo { + unsigned idx; - [childInfo release]; - childInfo = nil; + [childInfo release]; childInfo = nil; childIndex = 0; - idx = [self selectPartIndexFromTypes: [self childPartTypes]]; - if (idx == NSNotFound) - [self errorWithFormat: @"could not select a part of types: %@", - [self childPartTypes]]; - else - { - childIndex = idx + 1; - childInfo = [[[self bodyInfo] valueForKey:@"parts"] objectAtIndex: idx]; - [childInfos retain]; - } + idx = [self selectPartIndexFromTypes:[self childPartTypes]]; + if (idx == NSNotFound) { + [self errorWithFormat:@"could not select a part of types: %@", + [self childPartTypes]]; + return; + } + + childIndex = idx + 1; + childInfo = + [[[[self bodyInfo] valueForKey:@"parts"] objectAtIndex:idx] retain]; } /* accessors */ -- (id) childInfo -{ - if (!childInfo) +- (id)childInfo { + if (childInfo == nil) [self selectChildInfo]; return childInfo; } -- (unsigned int) childIndex -{ - if (!childIndex) +- (unsigned int) childIndex { + if (childIndex == 0) [self selectChildInfo]; - + return childIndex - 1; } -- (NSString *) childPartName -{ - return [NSString stringWithFormat: @"%d", ([self childIndex] + 1)]; +- (NSString *)childPartName { + char buf[8]; + sprintf(buf, "%d", [self childIndex] + 1); + return [NSString stringWithCString:buf]; } -- (id) childPartPath -{ +- (id)childPartPath { NSArray *pp; pp = [self partPath]; - return [pp count] > 0 - ? [pp arrayByAddingObject: [self childPartName]] - : [NSArray arrayWithObject: [self childPartName]]; + ? [pp arrayByAddingObject:[self childPartName]] + : [NSArray arrayWithObject:[self childPartName]]; } /* nested viewers */ -- (id) contentViewerComponent -{ +- (id)contentViewerComponent { id info; info = [self childInfo]; - - return [[context mailRenderingContext] viewerForBodyInfo:info]; + return [[[self context] mailRenderingContext] viewerForBodyInfo:info]; } @end /* UIxMailPartAlternativeViewer */ From 0986ba4ba1d0f8f8120e1be537b9e5018254c965 Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Tue, 30 Oct 2007 19:46:48 +0000 Subject: [PATCH 6/6] Monotone-Parent: 3291dd3b0291e6b09f19d1879b5f7a56fd0f5045 Monotone-Revision: 529806b76290054de1e3af14d027b6f92e169941 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2007-10-30T19:46:48 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 21 ++ SoObjects/Mailer/GNUmakefile | 3 +- SoObjects/Mailer/NSString+Mail.h | 34 +++ SoObjects/Mailer/NSString+Mail.m | 351 ++++++++++++++++++++++++ SoObjects/Mailer/SOGoMailObject+Draft.m | 124 +++------ SoObjects/Mailer/SOGoMailObject.m | 163 +++++------ 6 files changed, 533 insertions(+), 163 deletions(-) create mode 100644 SoObjects/Mailer/NSString+Mail.h create mode 100644 SoObjects/Mailer/NSString+Mail.m diff --git a/ChangeLog b/ChangeLog index 446f3fdc1..1c2dcc2e3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,26 @@ 2007-10-30 Wolfgang Sourdeau + * SoObjects/Mailer/NSString+Mail.m ([NSString -htmlToText]): new + method converting html content to plain text. + + * SoObjects/Mailer/NSString+Mail.[hm]: new category module + enhancing NSString with utility methods pertaining to mail handling. + + * SoObjects/Mailer/SOGoMailObject.m + ([-shouldFetchPartOfType:_typesubtype:_subtype]): removed obsolete method. + ([SOGoMailObject + -addRequiredKeysOfStructure:infopath:ptoArray:keysacceptedTypes:types]): + modified method to be always recursive and to take an array of the + accepted mime-types as parameter. The returned array now contains + the mime-type as well as the part keys. + + * SoObjects/Mailer/SOGoMailObject+Draft.m ([SOGoMailObject + -contentForEditingOnParts:_prtskeys:_k]): removed obsolete method. + ([SOGoMailObject -contentForEditing]): rewrote method to take into + account the first text/plain part or the first text/html part + converted to text/plain with our new -[NSString htmlToText] + category method. + * UI/MailerUI/UIxMailActions.m ([-replyToAllAction]): invoke "replyToAll:" with YES as parameter instead of NO. diff --git a/SoObjects/Mailer/GNUmakefile b/SoObjects/Mailer/GNUmakefile index 381e3fbdd..bd94d2e0e 100644 --- a/SoObjects/Mailer/GNUmakefile +++ b/SoObjects/Mailer/GNUmakefile @@ -32,7 +32,8 @@ Mailer_OBJC_FILES += \ \ SOGoMailForward.m \ \ - NSData+Mail.m + NSData+Mail.m \ + NSString+Mail.m Mailer_RESOURCE_FILES += \ Version \ diff --git a/SoObjects/Mailer/NSString+Mail.h b/SoObjects/Mailer/NSString+Mail.h new file mode 100644 index 000000000..254711015 --- /dev/null +++ b/SoObjects/Mailer/NSString+Mail.h @@ -0,0 +1,34 @@ +/* NSString+Mail.h - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * 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 NSSTRING_MAIL_H +#define NSSTRING_MAIL_H + +#import + +@interface NSString (SOGoExtension) + +- (NSString *) htmlToText; + +@end + +#endif /* NSSTRING_MAIL_H */ diff --git a/SoObjects/Mailer/NSString+Mail.m b/SoObjects/Mailer/NSString+Mail.m new file mode 100644 index 000000000..494becdc0 --- /dev/null +++ b/SoObjects/Mailer/NSString+Mail.m @@ -0,0 +1,351 @@ +/* NSString+Mail.m - this file is part of SOGo + * + * Copyright (C) 2007 Inverse groupe conseil + * + * 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 +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import "NSString+Mail.h" + +#if 1 +#define showWhoWeAre() \ + [self logWithFormat: @"invoked '%@'", NSStringFromSelector (_cmd)] +#else +#define showWhoWeAre() {} +#endif + +@interface _SOGoHTMLToTextContentHandler : NSObject +{ + NSArray *ignoreContentTags; + NSArray *specialTreatmentTags; + + BOOL ignoreContent; + BOOL orderedList; + BOOL unorderedList; + unsigned int listCount; + + NSMutableString *result; +} + ++ (id) htmlToTextContentHandler; + +- (NSString *) result; + +@end + +@implementation _SOGoHTMLToTextContentHandler + ++ (id) htmlToTextContentHandler +{ + static id htmlToTextContentHandler; + + if (!htmlToTextContentHandler) + htmlToTextContentHandler = [self new]; + + return htmlToTextContentHandler; +} + +- (id) init +{ + if ((self = [super init])) + { + ignoreContentTags = [NSArray arrayWithObjects: @"head", @"script", + @"style", nil]; + specialTreatmentTags = [NSArray arrayWithObjects: @"body", @"p", @"ul", + @"li", @"table", @"tr", @"td", @"th", + @"br", @"hr", @"dt", @"dd", nil]; + [ignoreContentTags retain]; + [specialTreatmentTags retain]; + + ignoreContent = NO; + result = nil; + + orderedList = NO; + unorderedList = NO; + listCount = 0; + } + + return self; +} + +- (void) dealloc +{ + [ignoreContentTags release]; + [specialTreatmentTags release]; + [result release]; + [super dealloc]; +} + +- (NSString *) result +{ + NSString *newResult; + + newResult = [NSString stringWithString: result]; + [result release]; + result = nil; + + return newResult; +} + +/* SaxContentHandler */ +- (void) startDocument +{ + showWhoWeAre(); + + [result release]; + result = [NSMutableString new]; +} + +- (void) endDocument +{ + showWhoWeAre(); + + ignoreContent = NO; +} + +- (void) startPrefixMapping: (NSString *) prefix + uri: (NSString *) uri +{ + showWhoWeAre(); +} + +- (void) endPrefixMapping: (NSString *) prefix +{ + showWhoWeAre(); +} + +- (void) _startSpecialTreatment: (NSString *) tagName +{ + if ([tagName isEqualToString: @"br"] + || [tagName isEqualToString: @"p"]) + [result appendString: @"\n"]; + else if ([tagName isEqualToString: @"hr"]) + [result appendString: @"______________________________________________________________________________\n"]; + else if ([tagName isEqualToString: @"ul"]) + { + [result appendString: @"\n"]; + unorderedList = YES; + } + else if ([tagName isEqualToString: @"ol"]) + { + [result appendString: @"\n"]; + orderedList = YES; + listCount = 0; + } + else if ([tagName isEqualToString: @"li"]) + { + if (orderedList) + { + listCount++; + [result appendFormat: @" %d. ", listCount]; + } + else + [result appendString: @" * "]; + } + else if ([tagName isEqualToString: @"dd"]) + [result appendString: @" "]; +} + +- (void) _endSpecialTreatment: (NSString *) tagName +{ + if ([tagName isEqualToString: @"ul"]) + { + [result appendString: @"\n"]; + unorderedList = NO; + } + else if ([tagName isEqualToString: @"ol"]) + { + [result appendString: @"\n"]; + orderedList = NO; + } + else if ([tagName isEqualToString: @"dt"]) + { + [result appendString: @":\n"]; + } + else if ([tagName isEqualToString: @"li"] + || [tagName isEqualToString: @"dd"]) + [result appendString: @"\n"]; +} + +- (void) startElement: (NSString *) element + namespace: (NSString *) namespace + rawName: (NSString *) rawName + attributes: (id ) attributes +{ + NSString *tagName; + + showWhoWeAre(); + + if (!ignoreContent) + { + tagName = [rawName lowercaseString]; + if ([ignoreContentTags containsObject: tagName]) + ignoreContent = YES; + else if ([specialTreatmentTags containsObject: tagName]) + [self _startSpecialTreatment: tagName]; + } +} + +- (void) endElement: (NSString *) element + namespace: (NSString *) namespace + rawName: (NSString *) rawName +{ + NSString *tagName; + + showWhoWeAre(); + + if (ignoreContent) + { + tagName = [rawName lowercaseString]; + if ([ignoreContentTags containsObject: tagName]) + ignoreContent = NO; + else if ([specialTreatmentTags containsObject: tagName]) + [self _endSpecialTreatment: tagName]; + } +} + +- (void) characters: (unichar *) characters + length: (int) length +{ + if (!ignoreContent) + [result appendString: [NSString stringWithCharacters: characters + length: length]]; +} + +- (void) ignorableWhitespace: (unichar *) whitespaces + length: (int) length +{ + showWhoWeAre(); +} + +- (void) processingInstruction: (NSString *) pi + data: (NSString *) data +{ + showWhoWeAre(); +} + +- (void) setDocumentLocator: (id ) locator +{ + showWhoWeAre(); +} + +- (void) skippedEntity: (NSString *) entity +{ + showWhoWeAre(); +} + +/* SaxLexicalHandler */ +- (void) comment: (unichar *) chars + length: (int) len +{ + showWhoWeAre(); +} + +- (void) startDTD: (NSString *) name + publicId: (NSString *) pub + systemId: (NSString *) sys +{ + showWhoWeAre(); +} + +- (void) endDTD +{ + showWhoWeAre(); +} + +- (void) startEntity: (NSString *) entity +{ + showWhoWeAre(); +} + +- (void) endEntity: (NSString *) entity +{ + showWhoWeAre(); +} + +- (void) startCDATA +{ + showWhoWeAre(); +} + +- (void) endCDATA +{ + showWhoWeAre(); +} + +@end + +// @interface NSDictionary (SOGoDebug) + +// - (void) dump; + +// @end + +// @implementation NSDictionary (SOGoDebug) + +// - (void) dump +// { +// NSEnumerator *keys; +// NSString *key; +// NSMutableString *dump; + +// dump = [NSMutableString new]; +// [dump appendFormat: @"\nNSDictionary dump (%@):\n", self]; +// keys = [[self allKeys] objectEnumerator]; +// key = [keys nextObject]; +// while (key) +// { +// [dump appendFormat: @"%@: %@\n", key, [self objectForKey: key]]; +// key = [keys nextObject]; +// } +// [dump appendFormat: @"--- end ---\n"]; + +// NSLog (dump); +// [dump release]; +// } + +// @end + +@implementation NSString (SOGoExtension) + +- (NSString *) htmlToText +{ + id parser; + _SOGoHTMLToTextContentHandler *handler; + + parser = [[SaxXMLReaderFactory standardXMLReaderFactory] + createXMLReaderForMimeType: @"text/html"]; + handler = [_SOGoHTMLToTextContentHandler htmlToTextContentHandler]; + [parser setContentHandler: handler]; + [parser parseFromSource: self]; + + return [handler result]; +} + +@end diff --git a/SoObjects/Mailer/SOGoMailObject+Draft.m b/SoObjects/Mailer/SOGoMailObject+Draft.m index ebe7e04a7..66ffa4447 100644 --- a/SoObjects/Mailer/SOGoMailObject+Draft.m +++ b/SoObjects/Mailer/SOGoMailObject+Draft.m @@ -27,8 +27,10 @@ #import #import +#import #import +#import "NSString+Mail.h" #import "SOGoMailForward.h" #import "SOGoMailObject+Draft.h" @@ -67,98 +69,54 @@ return newSubject; } -- (NSString *) contentForEditingOnParts: (NSDictionary *) _prts - keys: (NSArray *) _k + +- (NSString *) _contentForEditingFromKeys: (NSArray *) keys { - static NSString *textPartSeparator = @"\n---\n"; - NSMutableString *ms; - unsigned int count, max; - NSString *k, *v; - - ms = [NSMutableString stringWithCapacity: 16000]; - - max = [_k count]; - for (count = 0; count < max; count++) - { - k = [_k objectAtIndex: count]; - - // TODO: this is DUP code to SOGoMailObject - if ([k isEqualToString: @"body[text]"]) - k = @""; - else if ([k hasPrefix: @"body["]) { - k = [k substringFromIndex: 5]; - if ([k length] > 0) - k = [k substringToIndex: ([k length] - 1)]; - } - - v = [_prts objectForKey: k]; - if ([v isKindOfClass: [NSString class]] - && [v length] > 0) - { - if (count > 0) - [ms appendString: textPartSeparator]; - [ms appendString: v]; - } - else - [self logWithFormat:@"Note: cannot show part %@", k]; - } - - return ms; -} - -#warning this method should be fixed to return the first available text/plain \ - part, and otherwise the first text/html part converted to text -- (NSString *) contentForEditing -{ - NSArray *keys; + NSArray *types; NSDictionary *parts; - NSMutableArray *topLevelKeys = nil; - unsigned int count, max; - NSRange r; - NSString *contentForEditing; + NSString *rawPart, *content; + int index; + BOOL htmlContent; -// SOGoMailObject *co; - -// co = self; -// keys = [co plainTextContentFetchKeys]; -// infos = [co fetchCoreInfos]; -// partInfos = [infos objectForKey: keys]; -// NSLog (@"infos: '%@'", infos); - - keys = [self plainTextContentFetchKeys]; - max = [keys count]; - if (max > 0) + if ([keys count]) { - if (max > 1) + types = [keys objectsForKey: @"mimeType"]; + index = [types indexOfObject: @"text/plain"]; + if (index == NSNotFound) { - /* filter keys, only include top-level, or if none, the first */ - for (count = 0; count < max; count++) - { - r = [[keys objectAtIndex: count] rangeOfString: @"."]; - if (!r.length) - { - if (!topLevelKeys) - topLevelKeys = [NSMutableArray arrayWithCapacity: 4]; - [topLevelKeys addObject: [keys objectAtIndex: count]]; - } - } - - if ([topLevelKeys count] > 0) - /* use top-level keys if we have some */ - keys = topLevelKeys; - else - /* just take the first part */ - keys = [NSArray arrayWithObject: [keys objectAtIndex: 0]]; + index = [types indexOfObject: @"text/html"]; + htmlContent = YES; + } + if (index == NSNotFound) + content = @""; + else + { + parts = [self fetchPlainTextStrings: keys]; + rawPart = [[parts allValues] objectAtIndex: 0]; + if (htmlContent) + content = [rawPart htmlToText]; + else + content = rawPart; } - - parts = [self fetchPlainTextStrings: keys]; - contentForEditing = [self contentForEditingOnParts: parts - keys: keys]; } else - contentForEditing = nil; + content = @""; - return contentForEditing; + return content; +} + +- (NSString *) contentForEditing +{ + NSMutableArray *keys; + NSArray *acceptedTypes; + + acceptedTypes + = [NSArray arrayWithObjects: @"text/plain", @"text/html", nil]; + keys = [NSMutableArray new]; + [self addRequiredKeysOfStructure: [self bodyStructure] + path: @"" toArray: keys acceptedTypes: acceptedTypes]; + + return [self _contentForEditingFromKeys: keys]; } - (NSString *) contentForReply diff --git a/SoObjects/Mailer/SOGoMailObject.m b/SoObjects/Mailer/SOGoMailObject.m index c86781f59..15d531375 100644 --- a/SoObjects/Mailer/SOGoMailObject.m +++ b/SoObjects/Mailer/SOGoMailObject.m @@ -40,6 +40,7 @@ #import #import +#import #import #import @@ -181,10 +182,13 @@ static BOOL debugSoParts = NO; return ma; } -- (NSArray *)toOneRelationshipKeys { +- (NSArray *) toOneRelationshipKeys +{ return [self relationshipKeysWithParts:NO]; } -- (NSArray *)toManyRelationshipKeys { + +- (NSArray *) toManyRelationshipKeys +{ return [self relationshipKeysWithParts:YES]; } @@ -468,29 +472,29 @@ static BOOL debugSoParts = NO; /* bulk fetching of plain/text content */ -- (BOOL) shouldFetchPartOfType: (NSString *) _type - subtype: (NSString *) _subtype -{ - /* - This method decides which parts are 'prefetched' for display. Those are - usually text parts (the set is currently hardcoded in this method ...). - */ - _type = [_type lowercaseString]; - _subtype = [_subtype lowercaseString]; +// - (BOOL) shouldFetchPartOfType: (NSString *) _type +// subtype: (NSString *) _subtype +// { +// /* +// This method decides which parts are 'prefetched' for display. Those are +// usually text parts (the set is currently hardcoded in this method ...). +// */ +// _type = [_type lowercaseString]; +// _subtype = [_subtype lowercaseString]; - return (([_type isEqualToString: @"text"] - && ([_subtype isEqualToString: @"plain"] - || [_subtype isEqualToString: @"html"] - || [_subtype isEqualToString: @"calendar"])) - || ([_type isEqualToString: @"application"] - && ([_subtype isEqualToString: @"pgp-signature"] - || [_subtype hasPrefix: @"x-vnd.kolab."]))); -} +// return (([_type isEqualToString: @"text"] +// && ([_subtype isEqualToString: @"plain"] +// || [_subtype isEqualToString: @"html"] +// || [_subtype isEqualToString: @"calendar"])) +// || ([_type isEqualToString: @"application"] +// && ([_subtype isEqualToString: @"pgp-signature"] +// || [_subtype hasPrefix: @"x-vnd.kolab."]))); +// } -- (void) addRequiredKeysOfStructure: (id) _info - path: (NSString *) _p - toArray: (NSMutableArray *) _keys - recurse: (BOOL) _recurse +- (void) addRequiredKeysOfStructure: (NSDictionary *) info + path: (NSString *) p + toArray: (NSMutableArray *) keys + acceptedTypes: (NSArray *) types { /* This is used to collect the set of IMAP4 fetch-keys required to fetch @@ -501,19 +505,19 @@ static BOOL debugSoParts = NO; */ NSArray *parts; unsigned i, count; - BOOL fetchPart; NSString *k; id body; - NSString *sp; + NSString *sp, *mimeType; id childInfo; - - /* Note: if the part itself doesn't qualify, we still check subparts */ - fetchPart = [self shouldFetchPartOfType: [_info valueForKey: @"type"] - subtype: [_info valueForKey: @"subtype"]]; - if (fetchPart) + + mimeType = [[NSString stringWithFormat: @"%@/%@", + [info valueForKey: @"type"], + [info valueForKey: @"subtype"]] + lowercaseString]; + if ([types containsObject: mimeType]) { - if ([_p length] > 0) - k = [NSString stringWithFormat: @"body[%@]", _p]; + if ([p length] > 0) + k = [NSString stringWithFormat: @"body[%@]", p]; else { /* @@ -523,40 +527,37 @@ static BOOL debugSoParts = NO; */ k = @"body[text]"; } - [_keys addObject: k]; + [keys addObject: [NSDictionary dictionaryWithObjectsAndKeys: k, @"key", + mimeType, @"mimeType", nil]]; } - if (_recurse) + parts = [info objectForKey: @"parts"]; + count = [parts count]; + for (i = 0; i < count; i++) { - /* recurse */ - parts = [(NSDictionary *)_info objectForKey: @"parts"]; - count = [parts count]; - for (i = 0; i < count; i++) - { - sp = (([_p length] > 0) - ? [_p stringByAppendingFormat: @".%d", i + 1] - : [NSString stringWithFormat: @"%d", i + 1]); - - childInfo = [parts objectAtIndex: i]; - - [self addRequiredKeysOfStructure: childInfo - path: sp toArray: _keys - recurse: YES]; - } + sp = (([p length] > 0) + ? [p stringByAppendingFormat: @".%d", i + 1] + : [NSString stringWithFormat: @"%d", i + 1]); - /* check body */ - body = [(NSDictionary *)_info objectForKey: @"body"]; - if (body) - { - sp = [[body valueForKey: @"type"] lowercaseString]; - if ([sp isEqualToString: @"multipart"]) - sp = _p; - else - sp = [_p length] > 0 ? [_p stringByAppendingString: @".1"] : @"1"; - [self addRequiredKeysOfStructure: body - path: sp toArray: _keys - recurse: YES]; - } + childInfo = [parts objectAtIndex: i]; + + [self addRequiredKeysOfStructure: childInfo + path: sp toArray: keys + acceptedTypes: types]; + } + + /* check body */ + body = [info objectForKey: @"body"]; + if (body) + { + sp = [[body valueForKey: @"type"] lowercaseString]; + if ([sp isEqualToString: @"multipart"]) + sp = p; + else + sp = [p length] > 0 ? [p stringByAppendingString: @".1"] : @"1"; + [self addRequiredKeysOfStructure: body + path: sp toArray: keys + acceptedTypes: types]; } } @@ -567,10 +568,13 @@ static BOOL debugSoParts = NO; keys which are marked by the -shouldFetchPartOfType:subtype: method. */ NSMutableArray *ma; - - ma = [NSMutableArray arrayWithCapacity:4]; + NSArray *types; + + types = [NSArray arrayWithObjects: @"text/plain", @"text/html", + @"text/calendar", @"application/pgp-signature", nil]; + ma = [NSMutableArray arrayWithCapacity: 4]; [self addRequiredKeysOfStructure: [self bodyStructure] - path: @"" toArray: ma recurse: YES]; + path: @"" toArray: ma acceptedTypes: types]; return ma; } @@ -584,7 +588,7 @@ static BOOL debugSoParts = NO; [self debugWithFormat: @"fetch keys: %@", _fetchKeys]; - result = [self fetchParts:_fetchKeys]; + result = [self fetchParts: [_fetchKeys objectsForKey: @"key"]]; result = [result valueForKey: @"RawResponse"]; // hackish // Note: -valueForKey: doesn't work! @@ -596,7 +600,7 @@ static BOOL debugSoParts = NO; NSString *key; NSData *data; - key = [_fetchKeys objectAtIndex:i]; + key = [[_fetchKeys objectAtIndex:i] objectForKey: @"key"]; data = [(NSDictionary *)[(NSDictionary *)result objectForKey:key] objectForKey: @"data"]; @@ -622,7 +626,7 @@ static BOOL debugSoParts = NO; - (NSDictionary *) fetchPlainTextParts { - return [self fetchPlainTextParts:[self plainTextContentFetchKeys]]; + return [self fetchPlainTextParts: [self plainTextContentFetchKeys]]; } /* convert parts to strings */ @@ -656,19 +660,20 @@ static BOOL debugSoParts = NO; - (NSDictionary *) stringifyTextParts: (NSDictionary *) _datas { NSMutableDictionary *md; + NSDictionary *info; NSEnumerator *keys; - NSString *key; - - md = [NSMutableDictionary dictionaryWithCapacity:4]; + NSString *key, *s; + + md = [NSMutableDictionary dictionaryWithCapacity:4]; keys = [_datas keyEnumerator]; - while ((key = [keys nextObject]) != nil) { - NSDictionary *info; - NSString *s; - - info = [self lookupInfoForBodyPart:key]; - if ((s = [self stringForData:[_datas objectForKey:key] partInfo:info])) - [md setObject:s forKey:key]; - } + while ((key = [keys nextObject])) + { + info = [self lookupInfoForBodyPart: key]; + s = [self stringForData: [_datas objectForKey:key] partInfo: info]; + if (s) + [md setObject: s forKey: key]; + } + return md; }