1 /* UIxMailPartHTMLViewer.m - this file is part of SOGo
3 * Copyright (C) 2007 Inverse groupe conseil
5 * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
7 * This file is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
12 * This file is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; see the file COPYING. If not, write to
19 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
23 #import <Foundation/NSArray.h>
24 #import <Foundation/NSDictionary.h>
25 #import <SaxObjC/SaxAttributes.h>
26 #import <SaxObjC/SaxContentHandler.h>
27 #import <SaxObjC/SaxLexicalHandler.h>
28 #import <SaxObjC/SaxXMLReader.h>
29 #import <SaxObjC/SaxXMLReaderFactory.h>
30 #import <NGExtensions/NSString+misc.h>
31 #import <NGObjWeb/SoObjects.h>
33 #import "UIxMailPartHTMLViewer.h"
36 #define showWhoWeAre() NSLog(@"invoked '%@'", NSStringFromSelector(_cmd))
38 #define showWhoWeAre()
41 @interface _UIxHTMLMailContentHandler : NSObject <SaxContentHandler, SaxLexicalHandler>
43 NSMutableString *result;
45 NSDictionary *attachmentIds;
49 BOOL inCSSDeclaration;
51 NSMutableArray *crumb;
54 - (NSString *) result;
58 @implementation _UIxHTMLMailContentHandler
62 if ((self = [super init]))
81 - (void) setAttachmentIds: (NSDictionary *) newAttachmentIds
83 attachmentIds = newAttachmentIds;
96 /* SaxContentHandler */
97 - (void) startDocument
105 result = [NSMutableString new];
106 css = [NSMutableString new];
107 crumb = [NSMutableArray new];
112 inCSSDeclaration = NO;
118 unsigned int count, max;
124 for (count = max - 1; count > -1; count--)
126 [result appendFormat: @"</%@>", [crumb objectAtIndex: count]];
127 [crumb removeObjectAtIndex: count];
131 - (void) startPrefixMapping: (NSString *)_prefix
132 uri: (NSString *)_uri
137 - (void) endPrefixMapping: (NSString *)_prefix
142 - (void) _appendStyle: (unichar *) _chars
146 unichar *start, *currentChar;
150 for (count = 0; count < _len; count++)
152 currentChar = _chars + count;
153 if (inCSSDeclaration)
155 if (*(char *) currentChar == '}')
157 inCSSDeclaration = NO;
163 if (*(char *) currentChar == '{')
164 inCSSDeclaration = YES;
165 if (*(char *) currentChar == ',')
167 else if (!hasEmbeddedCSS)
169 if (*(char *) currentChar == '@')
170 hasEmbeddedCSS = YES;
172 if (*(char *) currentChar > 32)
174 [css appendString: [NSString stringWithCharacters: start
175 length: (currentChar - start)]];
176 [css appendString: @".SOGoHTMLMail-CSS-Delimiter "];
177 hasEmbeddedCSS = YES;
183 [css appendString: [NSString stringWithCharacters: start
184 length: (currentChar - start)]];
187 - (void) startElement: (NSString *) _localName
188 namespace: (NSString *) _ns
189 rawName: (NSString *) _rawName
190 attributes: (id <SaxAttributes>) _attributes
192 unsigned int count, max;
193 NSString *name, *value;
194 NSMutableString *resultPart;
198 if (inStyle || inScript)
200 else if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame)
202 else if ([_localName caseInsensitiveCompare: @"script"] == NSOrderedSame)
204 else if ([_localName caseInsensitiveCompare: @"style"] == NSOrderedSame)
208 resultPart = [NSMutableString new];
209 [resultPart appendFormat: @"<%@", _rawName];
211 max = [_attributes count];
212 for (count = 0; count < max; count++)
215 name = [_attributes nameAtIndex: count];
216 if ([[name lowercaseString] hasPrefix: @"on"])
218 else if ([name caseInsensitiveCompare: @"src"] == NSOrderedSame)
220 value = [_attributes valueAtIndex: count];
221 if ([value hasPrefix: @"cid:"])
223 value = [attachmentIds
224 objectForKey: [value substringFromIndex: 4]];
225 skipAttribute = (value == nil);
231 value = [_attributes valueAtIndex: count];
233 [resultPart appendFormat: @" %@=\"%@\"",
234 name, [value stringByReplacingString: @"\""
235 withString: @"\\\""]];
238 [resultPart appendString: @">"];
239 [result appendString: resultPart];
245 [css replaceString: @".SOGoHTMLMail-CSS-Delimiter body"
246 withString: @".SOGoHTMLMail-CSS-Delimiter"];
247 [css replaceString: @";" withString: @" !important;"];
248 [css replaceString: @"<!--" withString: @""];
249 [css replaceString: @"-->" withString: @""];
252 - (void) endElement: (NSString *) _localName
253 namespace: (NSString *) _ns
254 rawName: (NSString *) _rawName
260 if ([_localName caseInsensitiveCompare: @"style"] == NSOrderedSame)
263 inCSSDeclaration = NO;
267 inScript = ([_localName caseInsensitiveCompare: @"script"] != NSOrderedSame);
270 if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame)
277 [result appendFormat: @"</%@>", _localName];
281 - (void) characters: (unichar *) _chars
290 [self _appendStyle: _chars length: _len];
293 tmpString = [NSString stringWithCharacters: _chars length: _len];
294 [result appendString: [tmpString stringByEscapingHTMLString]];
299 - (void) ignorableWhitespace: (unichar *) _chars
305 - (void) processingInstruction: (NSString *) _pi
306 data: (NSString *) _data
311 - (void) setDocumentLocator: (id <NSObject, SaxLocator>) _locator
316 - (void) skippedEntity: (NSString *) _entityName
321 /* SaxLexicalHandler */
322 - (void) comment: (unichar *) _chars
327 [self _appendStyle: _chars length: _len];
330 - (void) startDTD: (NSString *) _name
331 publicId: (NSString *) _pub
332 systemId: (NSString *) _sys
342 - (void) startEntity: (NSString *) _name
347 - (void) endEntity: (NSString *) _name
364 @interface NSDictionary (SOGoDebug)
370 @implementation NSDictionary (SOGoDebug)
376 NSMutableString *dump;
378 dump = [NSMutableString new];
379 [dump appendFormat: @"\nNSDictionary dump (%@):\n", self];
380 keys = [[self allKeys] objectEnumerator];
381 key = [keys nextObject];
384 [dump appendFormat: @"%@: %@\n", key, [self objectForKey: key]];
385 key = [keys nextObject];
387 [dump appendFormat: @"--- end ---\n"];
395 @implementation UIxMailPartHTMLViewer
399 if ((self = [super init]))
413 - (void) _convertReferencesForPart: (NSDictionary *) part
414 withCount: (unsigned int) count
415 andBaseURL: (NSString *) url
416 intoDictionary: (NSMutableDictionary *) attachmentIds
420 bodyId = [part objectForKey: @"bodyId"];
421 if ([bodyId length] > 0)
423 if ([bodyId hasPrefix: @"<"])
424 bodyId = [bodyId substringFromIndex: 1];
425 if ([bodyId hasSuffix: @">"])
426 bodyId = [bodyId substringToIndex: [bodyId length] - 1];
427 [attachmentIds setObject: [url stringByAppendingFormat: @"/%d", count]
432 - (NSDictionary *) _attachmentIds
434 NSMutableDictionary *attachmentIds;
435 UIxMailPartViewer *parent;
436 unsigned int count, max;
437 NSMutableString *url;
441 attachmentIds = [NSMutableDictionary new];
442 [attachmentIds autorelease];
444 parent = [self parent];
445 if ([NSStringFromClass ([parent class])
446 isEqualToString: @"UIxMailPartAlternativeViewer"])
448 baseURL = [[self clientObject] baseURLInContext: context];
449 url = [NSMutableString new];
450 [url appendString: baseURL];
451 [url appendFormat: @"/%@", [partPath componentsJoinedByString: @"/"]];
452 [url deleteCharactersInRange: NSMakeRange([url length] - 3, 2)];
453 parts = [[parent bodyInfo] objectForKey: @"parts"];
455 for (count = 0; count < max; count++)
456 [self _convertReferencesForPart: [parts objectAtIndex: count]
459 intoDictionary: attachmentIds];
463 return attachmentIds;
466 - (void) _parseContent
468 id <NSObject, SaxXMLReader> parser;
469 NSData *preparsedContent;
471 preparsedContent = [super decodedFlatContent];
472 parser = [[SaxXMLReaderFactory standardXMLReaderFactory]
473 createXMLReaderForMimeType: @"text/html"];
475 handler = [_UIxHTMLMailContentHandler new];
476 [handler setAttachmentIds: [self _attachmentIds]];
477 [parser setContentHandler: handler];
478 [parser parseFromSource: preparsedContent];
481 - (NSString *) cssContent
483 NSString *cssContent, *css;
486 [self _parseContent];
490 cssContent = [NSString stringWithFormat: @"<style type=\"text/css\">%@</style>",
498 - (NSString *) flatContentAsString
501 [self _parseContent];
503 return [handler result];