pt_BR strings updates
[adiumx.git] / Source / GBChatlogHTMLConverter.m
blob03a37c3c95091f6749e8883d99aa90d7a2a55ad0
1 /*
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "GBChatlogHTMLConverter.h"
18 #import "AIStandardListWindowController.h"
19 #import <Adium/AIListContact.h>
20 #import <Adium/AIAccountControllerProtocol.h>
21 #import <Adium/AIContactControllerProtocol.h>
22 #import <Adium/AIPreferenceControllerProtocol.h>
23 #import <AIUtilities/NSCalendarDate+ISO8601Parsing.h>
24 #import <AIUtilities/AIDateFormatterAdditions.h>
25 #import <AIUtilities/AIStringAdditions.h>
29 #define PREF_GROUP_WEBKIT_MESSAGE_DISPLAY               @"WebKit Message Display"
30 #define KEY_WEBKIT_USE_NAME_FORMAT                              @"Use Custom Name Format"
31 #define KEY_WEBKIT_NAME_FORMAT                                  @"Name Format"
33 static void *createStructure(CFXMLParserRef parser, CFXMLNodeRef node, void *context);
34 static void addChild(CFXMLParserRef parser, void *parent, void *child, void *context);
35 static void endStructure(CFXMLParserRef parser, void *xmlType, void *context);
37 @implementation GBChatlogHTMLConverter
39 + (NSString *)readFile:(NSString *)filePath
41         GBChatlogHTMLConverter *converter = [[GBChatlogHTMLConverter alloc] init];
42         NSString *ret = [[converter readFile:filePath] retain];
43         [converter release];
44         return [ret autorelease];
47 - (id)init
49         self = [super init];
50         if(self == nil)
51                 return nil;
52         
53         state = XML_STATE_NONE;
54         
55         inputFileString = nil;
56         sender = nil;
57         mySN = nil;
58         myDisplayName = nil;
59         date = nil;
60         parser = NULL;
61         status = nil;
62         
63         statusLookup = [[NSDictionary alloc] initWithObjectsAndKeys:
64                 AILocalizedString(@"Online", nil), @"online",
65                 AILocalizedString(@"Offline", nil), @"offline",
66                 AILocalizedString(@"Away", nil), @"away",
67                 AILocalizedString(@"Idle", nil), @"idle",
68                 AILocalizedString(@"Available", nil), @"available",
69                 AILocalizedString(@"Busy", nil), @"busy",
70                 AILocalizedString(@"Not at Home", nil), @"notAtHome",
71                 AILocalizedString(@"On the Phone", nil), @"onThePhone",
72                 AILocalizedString(@"On Vacation", nil), @"onVacation",
73                 AILocalizedString(@"Do Not Disturb", nil), @"doNotDisturb",
74                 AILocalizedString(@"Extended Away", nil), @"extendedAway",
75                 AILocalizedString(@"Be Right Back", nil), @"beRightBack",
76                 AILocalizedString(@"Not Available", nil), @"notAvailable",
77                 AILocalizedString(@"Not at my Desk", nil), @"notAtMyDesk",
78                 AILocalizedString(@"Not in the Office", nil), @"notInTheOffice",
79                 AILocalizedString(@"Stepped Out", nil), @"steppedOut",
80                 nil];
81                 
82         if ([[[adium preferenceController] preferenceForKey:KEY_WEBKIT_USE_NAME_FORMAT
83                                                                                                   group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] boolValue]) {
84                 nameFormat = [[[adium preferenceController] preferenceForKey:KEY_WEBKIT_NAME_FORMAT
85                                                                                                                            group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] intValue];
86         } else {
87                 nameFormat = AIDefaultName;
88         }
90         return self;
93 - (void)dealloc
95         [inputFileString release];
96         [eventTranslate release];
97         [sender release];
98         [mySN release];
99         [myDisplayName release];
100         [service release];
101         [date release];
102         [status release];
103         [output release];
104         [statusLookup release];
105         [super dealloc];
108 - (NSString *)readFile:(NSString *)filePath
110         NSData *inputData = [NSData dataWithContentsOfFile:filePath];
111         inputFileString = [[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding];
112         NSURL *url = [[NSURL alloc] initFileURLWithPath:filePath];
113         output = [[NSMutableString alloc] init];
114         
115         CFXMLParserCallBacks callbacks = {
116                 0,
117                 createStructure,
118                 addChild,
119                 endStructure,
120                 NULL,
121                 NULL
122         };
123         CFXMLParserContext context = {
124                 0,
125                 self,
126                 CFRetain,
127                 CFRelease,
128                 NULL
129         };
130         parser = CFXMLParserCreateWithDataFromURL(NULL, (CFURLRef)url, kCFXMLParserSkipMetaData | kCFXMLParserSkipWhitespace, kCFXMLNodeCurrentVersion, &callbacks, &context);
131         if (!CFXMLParserParse(parser)) {
132                 NSLog(@"%@: Parser %@ for inputFileString %@ returned false.",
133                           [self class], parser, inputFileString);
134                 [output release];
135                 output = nil;
136         }
137         CFRelease(parser);
138         parser = nil;
139         [url release];
140         return output;
143 - (void)startedElement:(NSString *)name info:(const CFXMLElementInfo *)info
145         NSDictionary *attributes = (NSDictionary *)info->attributes;
146         
147         switch(state){
148                 case XML_STATE_NONE:
149                         if([name isEqualToString:@"chat"])
150                         {
151                                 [mySN release];
152                                 mySN = [[attributes objectForKey:@"account"] retain];
153                                 
154                                 [service release];
155                                 service = [[attributes objectForKey:@"service"] retain];
156                                 
157                                 [myDisplayName release];
158                                 myDisplayName = nil;
159                                 
160                                 NSEnumerator *enumerator = [[[adium accountController] accounts] objectEnumerator];
161                                 AIAccount        *account;
162                                 
163                                 while ((account = [enumerator nextObject])) {
164                                         if ([[[account UID] compactedString] isEqualToString:[mySN compactedString]] &&
165                                                 [[account serviceID] isEqualToString:service]) {
166                                                 myDisplayName = [[account displayName] retain];
167                                         }
168                                 }
170                                 state = XML_STATE_CHAT;
171                         }
172                         break;
173                 case XML_STATE_CHAT:
174                         if([name isEqualToString:@"message"])
175                         {
176                                 [sender release];
177                                 [date release];
178                                 
179                                 NSString *dateStr = [attributes objectForKey:@"time"];
180                                 if(dateStr != nil)
181                                         date = [[NSCalendarDate calendarDateWithString:dateStr] retain];
182                                 else
183                                         date = nil;
184                                 sender = [[attributes objectForKey:@"sender"] retain];
185                                 autoResponse = [[attributes objectForKey:@"auto"] isEqualToString:@"true"];
187                                 //Mark the location of the message...  We can copy it directly.  Anyone know why it is off by 1?
188                                 messageStart = CFXMLParserGetLocation(parser) - 1;
189                                 
190                                 state = XML_STATE_MESSAGE;
191                         }
192                         else if([name isEqualToString:@"event"])
193                         {
194                                 //Mark the location of the message...  We can copy it directly.  Anyone know why it is off by 1?
195                                 messageStart = CFXMLParserGetLocation(parser) - 1;
197                                 state = XML_STATE_EVENT_MESSAGE;
198                         }
199                         else if([name isEqualToString:@"status"])
200                         {
201                                 [status release];
202                                 [date release];
203                                 
204                                 NSString *dateStr = [attributes objectForKey:@"time"];
205                                 if(dateStr != nil)
206                                         date = [[NSCalendarDate calendarDateWithString:dateStr] retain];
207                                 else
208                                         date = nil;
209                                 
210                                 status = [[attributes objectForKey:@"type"] retain];
212                                 //Mark the location of the message...  We can copy it directly.  Anyone know why it is off by 1?
213                                 messageStart = CFXMLParserGetLocation(parser) - 1;
215                                 state = XML_STATE_STATUS_MESSAGE;
216                         }
217                         break;
218                 case XML_STATE_MESSAGE:
219                 case XML_STATE_EVENT_MESSAGE:
220                 case XML_STATE_STATUS_MESSAGE:
221                         break;
222         }
225 - (void)endedElement:(NSString *)name empty:(BOOL)empty
227         switch(state)
228         {
229                 case XML_STATE_EVENT_MESSAGE:
230                         state = XML_STATE_CHAT;
231                         break;
233                 case XML_STATE_MESSAGE:
234                         if([name isEqualToString:@"message"])
235                         {
236                                 CFIndex end = CFXMLParserGetLocation(parser);
237                                 NSString *message = nil;
238                                 if(!empty)
239                                         message = [inputFileString substringWithRange:NSMakeRange(messageStart, end - messageStart - 11)];  // 10 for </message> and 1 for the index being off
240                                 NSString *shownSender;
241                                 NSString *cssClass;
242                                 NSString *displayName = nil, *longDisplayName = nil;
243                                 
244                                 if ([mySN isEqualToString:sender]) {
245                                         //Find an account if one exists, and use its name
246                                         displayName = (myDisplayName ? myDisplayName : sender);
247                                         cssClass = @"send";
248                                 } else {
249                                         AIListObject *listObject = [[adium contactController] existingListObjectWithUniqueID:[AIListObject internalObjectIDForServiceID:service UID:sender]];
251                                         cssClass = @"receive";
252                                         displayName = [listObject displayName];
253                                         longDisplayName = [listObject longDisplayName];
254                                 }
256                                 if (displayName && ![displayName isEqualToString:sender]) {
257                                         switch (nameFormat) {
258                                                 case AIDefaultName:
259                                                         shownSender = (longDisplayName ? longDisplayName : displayName);
260                                                         break;
262                                                 case AIDisplayName:
263                                                         shownSender = displayName;
264                                                         break;
266                                                 case AIDisplayName_ScreenName:
267                                                         shownSender = [NSString stringWithFormat:@"%@ (%@)",displayName,sender];
268                                                         break;
270                                                 case AIScreenName_DisplayName:
271                                                         shownSender = [NSString stringWithFormat:@"%@ (%@)",sender,displayName];
272                                                         break;
274                                                 case AIScreenName:
275                                                         shownSender = sender;
276                                                         break;  
277                                         }
278                                 } else {
279                                         shownSender = sender;                                   
280                                 }
281                                 
282                                 [output appendFormat:@"<div class=\"%@\"><span class=\"timestamp\">%@</span> <span class=\"sender\">%@%@: </span><pre class=\"message\">%@</pre></div>\n",
283                                         ([mySN isEqualToString:sender] ? @"send" : @"receive"), 
284                                         [date descriptionWithCalendarFormat:[NSDateFormatter localizedDateFormatStringShowingSeconds:YES
285                                                                                                                                                                                                    showingAMorPM:YES]
286                                                                                            timeZone:nil
287                                                                                                  locale:nil],
288                                         shownSender, 
289                                         (autoResponse ? AILocalizedString(@" (Autoreply)",nil) : @""),
290                                         message];
291                                 state = XML_STATE_CHAT;
292                         }
293                         break;
294                 case XML_STATE_STATUS_MESSAGE:
295                         if([name isEqualToString:@"status"])
296                         {
297                                 CFIndex end = CFXMLParserGetLocation(parser);
298                                 NSString *message = nil;
299                                 if(!empty)
300                                         message = [inputFileString substringWithRange:NSMakeRange(messageStart, end - messageStart - 10)];  // 9 for </status> and 1 for the index being off
301                                                                 
302                                 NSString *displayMessage = nil;
303                                 //Note: I am diverging from what the AILoggerPlugin logs in this case.  It can't handle every case we can have here
304                                 if([message length])
305                                 {
306                                         if([status length])
307                                                 displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@: %@", nil), [statusLookup objectForKey:status], message];
308                                         else
309                                                 displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@", nil), message];
310                                 }
311                                 else if([status length])
312                                         displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@", nil), [statusLookup objectForKey:status]];
314                                 if([displayMessage length])
315                                         [output appendFormat:@"<div class=\"status\">%@ (%@)</div>\n",
316                                                 displayMessage,
317                                                 [date descriptionWithCalendarFormat:[NSDateFormatter localizedDateFormatStringShowingSeconds:YES
318                                                                                                                                                                                                            showingAMorPM:YES]
319                                                                                                    timeZone:nil
320                                                                                                          locale:nil]];
321                                 state = XML_STATE_CHAT;
322                         }                       
323                 case XML_STATE_CHAT:
324                         if([name isEqualToString:@"chat"])
325                                 state = XML_STATE_NONE;
326                         break;
327                 case XML_STATE_NONE:
328                         break;
329         }
332 typedef struct{
333         NSString        *name;
334         BOOL            empty;
335 } element;
337 void *createStructure(CFXMLParserRef parser, CFXMLNodeRef node, void *context)
339         element *ret = NULL;
340         
341     // Use the dataTypeID to determine what to print.
342     switch (CFXMLNodeGetTypeCode(node)) {
343         case kCFXMLNodeTypeDocument:
344             break;
345         case kCFXMLNodeTypeElement:
346                 {
347                         NSString *name = [NSString stringWithString:(NSString *)CFXMLNodeGetString(node)];
348                         const CFXMLElementInfo *info = CFXMLNodeGetInfoPtr(node);
349                         [(GBChatlogHTMLConverter *)context startedElement:name info:info];
350                         ret = (element *)malloc(sizeof(element));
351                         ret->name = [name retain];
352                         ret->empty = info->isEmpty;
353                         break;
354                 }
355         case kCFXMLNodeTypeProcessingInstruction:
356         case kCFXMLNodeTypeComment:
357         case kCFXMLNodeTypeText:
358         case kCFXMLNodeTypeCDATASection:
359         case kCFXMLNodeTypeEntityReference:
360         case kCFXMLNodeTypeDocumentType:
361         case kCFXMLNodeTypeWhitespace:
362         default:
363                         break;
364         }
365         
366     // Return the data string for use by the addChild and 
367     // endStructure callbacks.
368     return (void *) ret;
371 void addChild(CFXMLParserRef parser, void *parent, void *child, void *context)
375 void endStructure(CFXMLParserRef parser, void *xmlType, void *context)
377         NSString *name = nil;
378         BOOL empty = NO;
379         if(xmlType != NULL)
380         {
381                 name = [NSString stringWithString:((element *)xmlType)->name];
382                 empty = ((element *)xmlType)->empty;
383         }
384         [(GBChatlogHTMLConverter *)context endedElement:name empty:empty];
385         if(xmlType != NULL)
386         {
387                 [((element *)xmlType)->name release];
388                 free(xmlType);
389         }
392 @end