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 <SaxObjC/SaxAttributes.h>
25 #import <SaxObjC/SaxContentHandler.h>
26 #import <SaxObjC/SaxLexicalHandler.h>
27 #import <SaxObjC/SaxXMLReader.h>
28 #import <SaxObjC/SaxXMLReaderFactory.h>
29 #import <NGExtensions/NSString+misc.h>
30 #import <NGObjWeb/SoObjects.h>
32 #import "UIxMailPartHTMLViewer.h"
34 #define showWhoWeAre() NSLog(@"invoked '%@'", NSStringFromSelector(_cmd))
36 @interface _UIxHTMLMailContentHandler : NSObject <SaxContentHandler, SaxLexicalHandler>
38 NSMutableString *result;
40 NSDictionary *attachmentIds;
44 BOOL inCSSDeclaration;
45 NSMutableArray *crumb;
48 - (NSString *) result;
52 @implementation _UIxHTMLMailContentHandler
56 if ((self = [super init]))
78 - (void) setAttachmentIds: (NSDictionary *) newAttachmentIds
80 attachmentIds = newAttachmentIds;
85 return [[css copy] autorelease];
90 return [[result copy] autorelease];
93 /* SaxContentHandler */
94 - (void) startDocument
103 result = [NSMutableString new];
104 css = [NSMutableString new];
105 crumb = [NSMutableArray new];
109 inCSSDeclaration = NO;
114 unsigned int count, max;
118 for (count = max - 1; count > -1; count--)
120 [result appendFormat: @"</%@>", [crumb objectAtIndex: count]];
121 [crumb removeObjectAtIndex: count];
125 - (void) startPrefixMapping: (NSString *)_prefix
126 uri: (NSString *)_uri
131 - (void) endPrefixMapping: (NSString *)_prefix
136 - (NSString *) _valueForCSSIdentifier: (NSString *) primaryValue
138 NSMutableString *value;
139 NSEnumerator *classes;
140 NSString *currentValue;
142 value = [NSMutableString new];
145 classes = [[primaryValue componentsSeparatedByString: @" "] objectEnumerator];
146 currentValue = [classes nextObject];
149 [value appendFormat: @"SOGoHTMLMail-%@ ", currentValue];
150 currentValue = [classes nextObject];
156 - (void) _appendStyle: (unichar *) _chars
160 unichar *start, *currentChar;
164 for (count = 0; count < _len; count++)
166 currentChar = _chars + count;
167 if (inCSSDeclaration)
169 if (*(char *) currentChar == '}')
170 inCSSDeclaration = NO;
174 if (*(char *) currentChar == '{')
175 inCSSDeclaration = YES;
176 else if (*(char *) currentChar == '.'
177 || *(char *) currentChar == '#')
179 [css appendString: [NSString stringWithCharacters: start
180 length: (currentChar - start + 1)]];
181 [css appendString: @"SOGoHTMLMail-"];
182 start = currentChar + 1;
186 [css appendString: [NSString stringWithCharacters: start
187 length: (currentChar - start + 1)]];
190 - (void) startElement: (NSString *) _localName
191 namespace: (NSString *) _ns
192 rawName: (NSString *) _rawName
193 attributes: (id <SaxAttributes>) _attributes
195 unsigned int count, max;
196 NSString *name, *value;
197 NSMutableString *resultPart;
200 if (inStyle || inScript)
202 else if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame)
204 else if ([_localName caseInsensitiveCompare: @"script"] == NSOrderedSame)
206 else if ([_localName caseInsensitiveCompare: @"style"] == NSOrderedSame)
210 resultPart = [NSMutableString new];
211 [resultPart appendFormat: @"<%@", _rawName];
213 max = [_attributes count];
214 for (count = 0; count < max; count++)
217 name = [_attributes nameAtIndex: count];
218 if ([name caseInsensitiveCompare: @"class"] == NSOrderedSame
219 || [name caseInsensitiveCompare: @"id"] == NSOrderedSame)
220 value = [self _valueForCSSIdentifier: [_attributes valueAtIndex: count]];
221 else if ([[name lowercaseString] hasPrefix: @"on"])
223 else if ([name caseInsensitiveCompare: @"src"] == NSOrderedSame)
225 value = [_attributes valueAtIndex: count];
226 if ([value hasPrefix: @"cid:"])
228 value = [attachmentIds
229 objectForKey: [value substringFromIndex: 4]];
230 skipAttribute = (value == nil);
236 value = [_attributes valueAtIndex: count];
238 [resultPart appendFormat: @" %@=\"%@\"",
239 name, [value stringByReplacingString: @"\""
240 withString: @"\\\""]];
243 [resultPart appendString: @">"];
244 [result appendString: resultPart];
248 - (void) endElement: (NSString *) _localName
249 namespace: (NSString *) _ns
250 rawName: (NSString *) _rawName
254 if ([_localName caseInsensitiveCompare: @"style"] == NSOrderedSame)
257 inCSSDeclaration = NO;
261 inScript = ([_localName caseInsensitiveCompare: @"script"] != NSOrderedSame);
264 if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame)
267 [result appendFormat: @"</%@>", _localName];
271 - (void) characters: (unichar *) _chars
279 [self _appendStyle: _chars length: _len];
282 tmpString = [NSString stringWithCharacters: _chars length: _len];
283 [result appendString: [tmpString stringByEscapingHTMLString]];
288 - (void) ignorableWhitespace: (unichar *) _chars
294 - (void) processingInstruction: (NSString *) _pi
295 data: (NSString *) _data
300 - (void) setDocumentLocator: (id <NSObject, SaxLocator>) _locator
305 - (void) skippedEntity: (NSString *) _entityName
310 /* SaxLexicalHandler */
311 - (void) comment: (unichar *) _chars
315 [self _appendStyle: _chars length: _len];
318 - (void) startDTD: (NSString *) _name
319 publicId: (NSString *) _pub
320 systemId: (NSString *) _sys
330 - (void) startEntity: (NSString *) _name
335 - (void) endEntity: (NSString *) _name
352 @interface NSDictionary (SOGoDebug)
358 @implementation NSDictionary (SOGoDebug)
364 NSMutableString *dump;
366 dump = [NSMutableString new];
367 [dump appendFormat: @"\nNSDictionary dump (%@):\n", self];
368 keys = [[self allKeys] objectEnumerator];
369 key = [keys nextObject];
372 [dump appendFormat: @"%@: %@\n", key, [self objectForKey: key]];
373 key = [keys nextObject];
375 [dump appendFormat: @"--- end ---\n"];
383 @implementation UIxMailPartHTMLViewer
385 - (void) _convertReferencesForPart: (NSDictionary *) part
386 withCount: (unsigned int) count
387 andBaseURL: (NSString *) url
388 intoDictionary: (NSMutableDictionary *) attachmentIds
392 bodyId = [part objectForKey: @"bodyId"];
393 if ([bodyId length] > 0)
396 if ([bodyId hasPrefix: @"<"])
397 bodyId = [bodyId substringFromIndex: 1];
398 if ([bodyId hasSuffix: @">"])
399 bodyId = [bodyId substringToIndex: [bodyId length] - 1];
400 [attachmentIds setObject: [url stringByAppendingFormat: @"/%d", count]
405 - (NSDictionary *) _attachmentIds
407 NSMutableDictionary *attachmentIds;
408 UIxMailPartViewer *parent;
409 unsigned int count, max;
413 attachmentIds = [NSMutableDictionary new];
414 [attachmentIds autorelease];
416 parent = [self parent];
417 if ([NSStringFromClass ([parent class])
418 isEqualToString: @"UIxMailPartAlternativeViewer"])
420 baseURL = [[self clientObject] baseURLInContext: context];
421 parts = [[parent bodyInfo] objectForKey: @"parts"];
423 for (count = 0; count < max; count++)
424 [self _convertReferencesForPart: [parts objectAtIndex: count]
427 intoDictionary: attachmentIds];
430 NSLog(@"attc: '%@'", attachmentIds);
431 return attachmentIds;
434 - (NSString *) flatContentAsString
436 id <NSObject, SaxXMLReader> parser;
437 _UIxHTMLMailContentHandler *handler;
438 NSString *preparsedContent, *content, *css;
440 preparsedContent = [super flatContentAsString];
441 parser = [[SaxXMLReaderFactory standardXMLReaderFactory]
442 createXMLReaderForMimeType: @"text/html"];
444 handler = [_UIxHTMLMailContentHandler new];
445 [handler setAttachmentIds: [self _attachmentIds]];
446 [parser setContentHandler: handler];
447 [parser setProperty: @"http://xml.org/sax/properties/lexical-handler"
449 [parser parseFromSource: preparsedContent];
454 = [NSString stringWithFormat: @"<style type=\"text/css\">%@</style>%@",
455 css, [handler result]];
457 content = [handler result];