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