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