]> err.no Git - scalable-opengroupware.org/blob - UI/MailPartViewers/UIxMailPartHTMLViewer.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1100 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / UI / MailPartViewers / UIxMailPartHTMLViewer.m
1 /* UIxMailPartHTMLViewer.m - this file is part of SOGo
2  *
3  * Copyright (C) 2007 Inverse groupe conseil
4  *
5  * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
6  *
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)
10  * any later version.
11  *
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.
16  *
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.
21  */
22
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>
32
33 #import "UIxMailPartHTMLViewer.h"
34
35 #define showWhoWeAre() NSLog(@"invoked '%@'", NSStringFromSelector(_cmd))
36
37 @interface _UIxHTMLMailContentHandler : NSObject <SaxContentHandler, SaxLexicalHandler>
38 {
39   NSMutableString *result;
40   NSMutableString *css;
41   NSDictionary *attachmentIds;
42   BOOL inBody;
43   BOOL inStyle;
44   BOOL inScript;
45   BOOL inCSSDeclaration;
46   BOOL hasEmbeddedCSS;
47   NSMutableArray *crumb;
48 }
49
50 - (NSString *) result;
51
52 @end
53
54 @implementation _UIxHTMLMailContentHandler
55
56 - (id) init
57 {
58   if ((self = [super init]))
59     {
60       crumb = nil;
61       css = nil;
62       result = nil;
63       attachmentIds = nil;
64     }
65
66   return self;
67 }
68
69 - (void) dealloc
70 {
71   if (crumb)
72     [crumb release];
73   if (result)
74     [result release];
75   if (css)
76     [css release];
77   [super dealloc];
78 }
79
80 - (void) setAttachmentIds: (NSDictionary *) newAttachmentIds
81 {
82   attachmentIds = newAttachmentIds;
83 }
84
85 - (NSString *) css
86 {
87   return [[css copy] autorelease];
88 }
89
90 - (NSString *) result
91 {
92   return [[result copy] autorelease];
93 }
94
95 /* SaxContentHandler */
96 - (void) startDocument
97 {
98   if (crumb)
99     [crumb release];
100   if (result)
101     [result release];
102   if (css)
103     [css release];
104
105   result = [NSMutableString new];
106   css = [NSMutableString new];
107   crumb = [NSMutableArray new];
108   inBody = NO;
109   inStyle = NO;
110   inScript = NO;
111   inCSSDeclaration = NO;
112   hasEmbeddedCSS = NO;
113 }
114
115 - (void) endDocument
116 {
117   unsigned int count, max;
118
119   max = [crumb count];
120   if (max > 0)
121     for (count = max - 1; count > -1; count--)
122       {
123         [result appendFormat: @"</%@>", [crumb objectAtIndex: count]];
124         [crumb removeObjectAtIndex: count];
125       }
126 }
127
128 - (void) startPrefixMapping: (NSString *)_prefix
129                         uri: (NSString *)_uri
130 {
131   showWhoWeAre();
132 }
133
134 - (void) endPrefixMapping: (NSString *)_prefix
135 {
136   showWhoWeAre();
137 }
138
139 - (void) _appendStyle: (unichar *) _chars
140                length: (int) _len
141 {
142   unsigned int count;
143   unichar *start, *currentChar;
144
145   start = _chars;
146   currentChar = start;
147   for (count = 0; count < _len; count++)
148     {
149       currentChar = _chars + count;
150       if (inCSSDeclaration)
151         {
152           if (*(char *) currentChar == '}')
153             {
154               inCSSDeclaration = NO;
155               hasEmbeddedCSS = NO;
156             }
157         }
158       else
159         {
160           if (*(char *) currentChar == '{')
161             inCSSDeclaration = YES;
162           if (*(char *) currentChar == ',')
163             hasEmbeddedCSS = NO;
164           else if (!hasEmbeddedCSS)
165             {
166               if (*(char *) currentChar == '@')
167                 hasEmbeddedCSS = YES;
168               else
169                 if (*(char *) currentChar > 32)
170                   {
171                     [css appendString: [NSString stringWithCharacters: start
172                                                  length: (currentChar - start)]];
173                     [css appendString: @".SOGoHTMLMail-CSS-Delimiter "];
174                     hasEmbeddedCSS = YES;
175                     start = currentChar;
176                   }
177             }
178         }
179     }
180   [css appendString: [NSString stringWithCharacters: start
181                                length: (currentChar - start)]];
182 }
183
184 - (void) startElement: (NSString *) _localName
185             namespace: (NSString *) _ns
186               rawName: (NSString *) _rawName
187            attributes: (id <SaxAttributes>) _attributes
188 {
189   unsigned int count, max;
190   NSString *name, *value;
191   NSMutableString *resultPart;
192   BOOL skipAttribute;
193
194   if (inStyle || inScript)
195     ;
196   else if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame)
197     inBody = YES;
198   else if ([_localName caseInsensitiveCompare: @"script"] == NSOrderedSame)
199     inScript = YES;
200   else if ([_localName caseInsensitiveCompare: @"style"] == NSOrderedSame)
201     inStyle = YES;
202   else if (inBody)
203     {
204       resultPart = [NSMutableString new];
205       [resultPart appendFormat: @"<%@", _rawName];
206       
207       max = [_attributes count];
208       for (count = 0; count < max; count++)
209         {
210           skipAttribute = NO;
211           name = [_attributes nameAtIndex: count];
212           if ([[name lowercaseString] hasPrefix: @"on"])
213             skipAttribute = YES;
214           else if ([name caseInsensitiveCompare: @"src"] == NSOrderedSame)
215             {
216               value = [_attributes valueAtIndex: count];
217               if ([value hasPrefix: @"cid:"])
218                 {
219                   value = [attachmentIds
220                             objectForKey: [value substringFromIndex: 4]];
221                   skipAttribute = (value == nil);
222                 }
223               else
224                 skipAttribute = YES;
225             }
226           else
227             value = [_attributes valueAtIndex: count];
228           if (!skipAttribute)
229             [resultPart appendFormat: @" %@=\"%@\"",
230                         name, [value stringByReplacingString: @"\""
231                                      withString: @"\\\""]];
232         }
233
234       [resultPart appendString: @">"];
235       [result appendString: resultPart];
236     }
237 }
238
239 - (void) endElement: (NSString *) _localName
240           namespace: (NSString *) _ns
241             rawName: (NSString *) _rawName
242 {
243   if (inStyle)
244     {
245      if ([_localName caseInsensitiveCompare: @"style"] == NSOrderedSame)
246        {
247          inStyle = NO;
248          inCSSDeclaration = NO;
249        }
250     }
251   else if (inScript)
252     inScript = ([_localName caseInsensitiveCompare: @"script"] != NSOrderedSame);
253   else if (inBody)
254     {
255       if ([_localName caseInsensitiveCompare: @"body"] == NSOrderedSame)
256         inBody = NO;
257       else
258         [result appendFormat: @"</%@>", _localName];
259     }
260 }
261
262 - (void) characters: (unichar *) _chars
263              length: (int) _len
264 {
265   NSString *tmpString;
266
267   if (!inScript)
268     {
269       if (inStyle)
270         [self _appendStyle: _chars length: _len];
271       if (inBody)
272         {
273           tmpString = [NSString stringWithCharacters: _chars length: _len];
274           [result appendString: [tmpString stringByEscapingHTMLString]];
275         }
276     }
277 }
278
279 - (void) ignorableWhitespace: (unichar *) _chars
280                       length: (int) _len
281 {
282   showWhoWeAre();
283 }
284
285 - (void) processingInstruction: (NSString *) _pi
286                           data: (NSString *) _data
287 {
288   showWhoWeAre();
289 }
290
291 - (void) setDocumentLocator: (id <NSObject, SaxLocator>) _locator
292 {
293   showWhoWeAre();
294 }
295
296 - (void) skippedEntity: (NSString *) _entityName
297 {
298   showWhoWeAre();
299 }
300
301 /* SaxLexicalHandler */
302 - (void) comment: (unichar *) _chars
303           length: (int) _len
304 {
305   if (inStyle)
306     [self _appendStyle: _chars length: _len];
307 }
308
309 - (void) startDTD: (NSString *) _name
310          publicId: (NSString *) _pub
311          systemId: (NSString *) _sys
312 {
313   showWhoWeAre();
314 }
315
316 - (void) endDTD
317 {
318   showWhoWeAre();
319 }
320
321 - (void) startEntity: (NSString *) _name
322 {
323   showWhoWeAre();
324 }
325
326 - (void) endEntity: (NSString *) _name
327 {
328   showWhoWeAre();
329 }
330
331 - (void) startCDATA
332 {
333   showWhoWeAre();
334 }
335
336 - (void) endCDATA
337 {
338   showWhoWeAre();
339 }
340
341 @end
342
343 @interface NSDictionary (SOGoDebug)
344
345 - (void) dump;
346
347 @end
348
349 @implementation NSDictionary (SOGoDebug)
350
351 - (void) dump
352 {
353   NSEnumerator *keys;
354   NSString *key;
355   NSMutableString *dump;
356
357   dump = [NSMutableString new];
358   [dump appendFormat: @"\nNSDictionary dump (%@):\n", self];
359   keys = [[self allKeys] objectEnumerator];
360   key = [keys nextObject];
361   while (key)
362     {
363       [dump appendFormat: @"%@: %@\n", key, [self objectForKey: key]];
364       key = [keys nextObject];
365     }
366   [dump appendFormat: @"--- end ---\n"];
367
368   NSLog (dump);
369   [dump release];
370 }
371
372 @end
373
374 @implementation UIxMailPartHTMLViewer
375
376 - (void) _convertReferencesForPart: (NSDictionary *) part
377                          withCount: (unsigned int) count
378                         andBaseURL: (NSString *) url
379                     intoDictionary: (NSMutableDictionary *) attachmentIds
380 {
381   NSString *bodyId;
382
383   bodyId = [part objectForKey: @"bodyId"];
384   if ([bodyId length] > 0)
385     {
386       NSLog(@"%@", part);
387       if ([bodyId hasPrefix: @"<"])
388         bodyId = [bodyId substringFromIndex: 1];
389       if ([bodyId hasSuffix: @">"])
390         bodyId = [bodyId substringToIndex: [bodyId length] - 1];
391       [attachmentIds setObject: [url stringByAppendingFormat: @"/%d", count]
392                      forKey: bodyId];
393     }
394 }
395
396 - (NSDictionary *) _attachmentIds
397 {
398   NSMutableDictionary *attachmentIds;
399   UIxMailPartViewer *parent;
400   unsigned int count, max;
401   NSMutableString *url;
402   NSString *baseURL;
403   NSArray *parts;
404
405   attachmentIds = [NSMutableDictionary new];
406   [attachmentIds autorelease];
407   
408   parent = [self parent];
409   if ([NSStringFromClass ([parent class])
410                          isEqualToString: @"UIxMailPartAlternativeViewer"])
411     {
412       baseURL = [[self clientObject] baseURLInContext: context];
413       url = [NSMutableString new];
414       [url appendString: baseURL];
415       [url appendFormat: @"/%@", [partPath componentsJoinedByString: @"/"]];
416       [url deleteCharactersInRange: NSMakeRange([url length] - 3, 2)];
417       parts = [[parent bodyInfo] objectForKey: @"parts"];
418       max = [parts count];
419       for (count = 0; count < max; count++)
420         [self _convertReferencesForPart: [parts objectAtIndex: count]
421               withCount: count + 1
422               andBaseURL: url
423               intoDictionary: attachmentIds];
424       [url release];
425     }
426
427   return attachmentIds;
428 }
429
430 - (NSString *) flatContentAsString
431 {
432   id <NSObject, SaxXMLReader> parser;
433   _UIxHTMLMailContentHandler *handler;
434   NSString *preparsedContent, *content, *css;
435
436   preparsedContent = [super flatContentAsString];
437   parser = [[SaxXMLReaderFactory standardXMLReaderFactory]
438              createXMLReaderForMimeType: @"text/html"];
439
440   handler = [_UIxHTMLMailContentHandler new];
441   [handler setAttachmentIds: [self _attachmentIds]];
442   [parser setContentHandler: handler];
443   [parser setProperty: @"http://xml.org/sax/properties/lexical-handler"
444           to: handler];
445   [parser parseFromSource: preparsedContent];
446
447   css = [handler css];
448   if ([css length])
449     content
450       = [NSString stringWithFormat: @"<style type=\"text/css\">%@</style>%@",
451                   css, [handler result]];
452   else
453     content = [handler result];
454   [handler release];
455
456   return content;
457 }
458
459 @end