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