|
David@0
|
1 |
/* |
|
David@0
|
2 |
* Adium is the legal property of its developers, whose names are listed in the copyright file included |
|
David@0
|
3 |
* with this source distribution. |
|
David@0
|
4 |
* |
|
David@0
|
5 |
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU |
|
David@0
|
6 |
* General Public License as published by the Free Software Foundation; either version 2 of the License, |
|
David@0
|
7 |
* or (at your option) any later version. |
|
David@0
|
8 |
* |
|
David@0
|
9 |
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
|
David@0
|
10 |
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
|
David@0
|
11 |
* Public License for more details. |
|
David@0
|
12 |
* |
|
David@0
|
13 |
* You should have received a copy of the GNU General Public License along with this program; if not, |
|
David@0
|
14 |
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
David@0
|
15 |
*/ |
|
David@0
|
16 |
|
|
David@0
|
17 |
#import "AIXMLChatlogConverter.h" |
|
David@0
|
18 |
#import "AIStandardListWindowController.h" |
|
David@0
|
19 |
#import <Adium/AIHTMLDecoder.h> |
|
David@0
|
20 |
#import <Adium/AIListContact.h> |
|
David@715
|
21 |
#import <Adium/AIService.h> |
|
David@0
|
22 |
#import <Adium/AIAccountControllerProtocol.h> |
|
David@0
|
23 |
#import <Adium/AIContactControllerProtocol.h> |
|
David@0
|
24 |
#import <Adium/AIContentControllerProtocol.h> |
|
David@0
|
25 |
#import <Adium/AIStatusControllerProtocol.h> |
|
David@0
|
26 |
#import <AIUtilities/NSCalendarDate+ISO8601Parsing.h> |
|
David@0
|
27 |
#import <AIUtilities/AIDateFormatterAdditions.h> |
|
David@0
|
28 |
#import <AIUtilities/AIStringAdditions.h> |
|
David@0
|
29 |
|
|
David@0
|
30 |
#define PREF_GROUP_WEBKIT_MESSAGE_DISPLAY @"WebKit Message Display" |
|
David@0
|
31 |
#define KEY_WEBKIT_USE_NAME_FORMAT @"Use Custom Name Format" |
|
David@0
|
32 |
#define KEY_WEBKIT_NAME_FORMAT @"Name Format" |
|
David@0
|
33 |
|
|
David@0
|
34 |
static void *createStructure(CFXMLParserRef parser, CFXMLNodeRef node, void *context); |
|
David@0
|
35 |
static void addChild(CFXMLParserRef parser, void *parent, void *child, void *context); |
|
David@0
|
36 |
static void endStructure(CFXMLParserRef parser, void *xmlType, void *context); |
|
David@0
|
37 |
|
|
David@0
|
38 |
@implementation AIXMLChatlogConverter |
|
David@0
|
39 |
|
|
David@0
|
40 |
+ (NSAttributedString *)readFile:(NSString *)filePath withOptions:(NSDictionary *)options |
|
David@0
|
41 |
{ |
|
David@0
|
42 |
AIXMLChatlogConverter *converter = [[AIXMLChatlogConverter alloc] init]; |
|
David@0
|
43 |
NSAttributedString *ret = [[converter readFile:filePath withOptions:options] retain]; |
|
David@0
|
44 |
[converter release]; |
|
David@0
|
45 |
return [ret autorelease]; |
|
David@0
|
46 |
} |
|
David@0
|
47 |
|
|
David@0
|
48 |
- (id)init |
|
David@0
|
49 |
{ |
|
David@487
|
50 |
if ((self = [super init])) { |
|
David@0
|
51 |
|
|
David@487
|
52 |
state = XML_STATE_NONE; |
|
David@487
|
53 |
|
|
David@487
|
54 |
inputFileString = nil; |
|
David@487
|
55 |
sender = nil; |
|
David@487
|
56 |
mySN = nil; |
|
David@487
|
57 |
myDisplayName = nil; |
|
David@487
|
58 |
date = nil; |
|
David@487
|
59 |
parser = NULL; |
|
David@487
|
60 |
status = nil; |
|
David@487
|
61 |
|
|
David@625
|
62 |
dateFormatter = [[NSDateFormatter localizedDateFormatterShowingSeconds:YES showingAMorPM:YES] retain]; |
|
David@625
|
63 |
|
|
David@487
|
64 |
newlineAttributedString = [[NSAttributedString alloc] initWithString:@"\n" attributes:nil]; |
|
David@0
|
65 |
|
|
David@487
|
66 |
statusLookup = [[NSDictionary alloc] initWithObjectsAndKeys: |
|
David@487
|
67 |
AILocalizedString(@"Online", nil), @"online", |
|
David@487
|
68 |
AILocalizedString(@"Idle", nil), @"idle", |
|
David@487
|
69 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_OFFLINE], @"offline", |
|
David@487
|
70 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AWAY], @"away", |
|
David@487
|
71 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AVAILABLE], @"available", |
|
David@487
|
72 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_BUSY], @"busy", |
|
David@487
|
73 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_AT_HOME], @"notAtHome", |
|
David@487
|
74 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_PHONE], @"onThePhone", |
|
David@487
|
75 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_VACATION], @"onVacation", |
|
David@487
|
76 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_DND], @"doNotDisturb", |
|
David@487
|
77 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_EXTENDED_AWAY], @"extendedAway", |
|
David@487
|
78 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_BRB], @"beRightBack", |
|
David@487
|
79 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_AVAILABLE], @"notAvailable", |
|
David@487
|
80 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_AT_DESK], @"notAtMyDesk", |
|
David@487
|
81 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_IN_OFFICE], @"notInTheOffice", |
|
David@487
|
82 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_STEPPED_OUT], @"steppedOut", |
|
David@487
|
83 |
nil]; |
|
David@487
|
84 |
|
|
David@487
|
85 |
if ([[adium.preferenceController preferenceForKey:KEY_WEBKIT_USE_NAME_FORMAT |
|
David@487
|
86 |
group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] boolValue]) { |
|
David@487
|
87 |
nameFormat = [[adium.preferenceController preferenceForKey:KEY_WEBKIT_NAME_FORMAT |
|
David@487
|
88 |
group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] integerValue]; |
|
David@487
|
89 |
} else { |
|
David@487
|
90 |
nameFormat = AIDefaultName; |
|
David@487
|
91 |
} |
|
David@0
|
92 |
} |
|
David@0
|
93 |
|
|
David@0
|
94 |
return self; |
|
David@0
|
95 |
} |
|
David@0
|
96 |
|
|
David@0
|
97 |
- (void)dealloc |
|
David@0
|
98 |
{ |
|
David@625
|
99 |
[dateFormatter release]; |
|
David@0
|
100 |
[newlineAttributedString release]; |
|
David@0
|
101 |
[inputFileString release]; |
|
David@0
|
102 |
[eventTranslate release]; |
|
David@0
|
103 |
[sender release]; |
|
David@0
|
104 |
[senderAlias release]; |
|
David@0
|
105 |
[mySN release]; |
|
David@0
|
106 |
[myDisplayName release]; |
|
David@0
|
107 |
[service release]; |
|
David@0
|
108 |
[date release]; |
|
David@0
|
109 |
[status release]; |
|
David@0
|
110 |
[output release]; |
|
David@0
|
111 |
[statusLookup release]; |
|
David@0
|
112 |
[htmlDecoder release]; |
|
David@0
|
113 |
[super dealloc]; |
|
David@0
|
114 |
} |
|
David@0
|
115 |
|
|
David@0
|
116 |
- (NSAttributedString *)readFile:(NSString *)filePath withOptions:(NSDictionary *)options |
|
David@0
|
117 |
{ |
|
David@487
|
118 |
NSData *inputData = [NSData dataWithContentsOfFile:filePath]; |
|
David@487
|
119 |
inputFileString = [[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding]; |
|
David@0
|
120 |
NSURL *url = [[NSURL alloc] initFileURLWithPath:filePath]; |
|
David@0
|
121 |
output = [[NSMutableAttributedString alloc] init]; |
|
David@0
|
122 |
|
|
David@0
|
123 |
htmlDecoder = [[AIHTMLDecoder alloc] init]; |
|
David@0
|
124 |
[htmlDecoder setBaseURL:[filePath stringByDeletingLastPathComponent]]; |
|
David@0
|
125 |
|
|
David@0
|
126 |
showTimestamps = [[options objectForKey:@"showTimestamps"] boolValue]; |
|
David@0
|
127 |
showEmoticons = [[options objectForKey:@"showEmoticons"] boolValue]; |
|
David@0
|
128 |
|
|
David@0
|
129 |
CFXMLParserCallBacks callbacks = { |
|
David@0
|
130 |
0, |
|
David@0
|
131 |
createStructure, |
|
David@0
|
132 |
addChild, |
|
David@0
|
133 |
endStructure, |
|
David@0
|
134 |
NULL, |
|
David@0
|
135 |
NULL |
|
David@0
|
136 |
}; |
|
David@0
|
137 |
CFXMLParserContext context = { |
|
David@0
|
138 |
0, |
|
David@0
|
139 |
self, |
|
David@0
|
140 |
CFRetain, |
|
David@0
|
141 |
CFRelease, |
|
David@0
|
142 |
NULL |
|
David@0
|
143 |
}; |
|
David@487
|
144 |
parser = CFXMLParserCreate(NULL, (CFDataRef)inputData, NULL, kCFXMLParserSkipMetaData | kCFXMLParserSkipWhitespace, kCFXMLNodeCurrentVersion, &callbacks, &context); |
|
David@0
|
145 |
if (!CFXMLParserParse(parser)) { |
|
David@0
|
146 |
NSLog(@"%@: Parser %@ for inputFileString %@ returned false.", |
|
David@487
|
147 |
[self class], parser, inputFileString); |
|
David@0
|
148 |
[output release]; |
|
David@0
|
149 |
output = nil; |
|
David@0
|
150 |
} |
|
David@0
|
151 |
CFRelease(parser); |
|
David@0
|
152 |
parser = nil; |
|
David@0
|
153 |
[url release]; |
|
David@0
|
154 |
return output; |
|
David@0
|
155 |
} |
|
David@0
|
156 |
|
|
David@0
|
157 |
- (void)startedElement:(NSString *)name info:(const CFXMLElementInfo *)info |
|
David@0
|
158 |
{ |
|
David@0
|
159 |
NSDictionary *attributes = (NSDictionary *)info->attributes; |
|
David@0
|
160 |
|
|
David@0
|
161 |
switch(state){ |
|
David@0
|
162 |
case XML_STATE_NONE: |
|
David@0
|
163 |
if([name isEqualToString:@"chat"]) |
|
David@0
|
164 |
{ |
|
David@0
|
165 |
[mySN release]; |
|
David@0
|
166 |
mySN = [[attributes objectForKey:@"account"] retain]; |
|
David@0
|
167 |
|
|
David@0
|
168 |
[service release]; |
|
David@0
|
169 |
service = [[attributes objectForKey:@"service"] retain]; |
|
David@0
|
170 |
|
|
David@0
|
171 |
[myDisplayName release]; |
|
David@0
|
172 |
myDisplayName = nil; |
|
David@0
|
173 |
|
|
Evan@166
|
174 |
for (AIAccount *account in adium.accountController.accounts) { |
|
David@715
|
175 |
if ([[account.UID compactedString] isEqualToString:[mySN compactedString]] && |
|
David@715
|
176 |
[account.service.serviceID isEqualToString:service]) { |
|
David@837
|
177 |
myDisplayName = [account.displayName retain]; |
|
Evan@166
|
178 |
break; |
|
David@0
|
179 |
} |
|
David@0
|
180 |
} |
|
David@0
|
181 |
|
|
David@0
|
182 |
state = XML_STATE_CHAT; |
|
David@0
|
183 |
} |
|
David@0
|
184 |
break; |
|
David@0
|
185 |
case XML_STATE_CHAT: |
|
David@0
|
186 |
if([name isEqualToString:@"message"]) |
|
David@0
|
187 |
{ |
|
David@0
|
188 |
[sender release]; |
|
David@0
|
189 |
[senderAlias release]; |
|
David@0
|
190 |
[date release]; |
|
David@0
|
191 |
|
|
David@0
|
192 |
NSString *dateStr = [attributes objectForKey:@"time"]; |
|
David@0
|
193 |
if(dateStr != nil) |
|
David@0
|
194 |
date = [[NSCalendarDate calendarDateWithString:dateStr] retain]; |
|
David@0
|
195 |
else |
|
David@0
|
196 |
date = nil; |
|
David@0
|
197 |
sender = [[attributes objectForKey:@"sender"] retain]; |
|
David@0
|
198 |
senderAlias = [[attributes objectForKey:@"alias"] retain]; |
|
David@0
|
199 |
autoResponse = [[attributes objectForKey:@"auto"] isEqualToString:@"true"]; |
|
David@0
|
200 |
|
|
David@0
|
201 |
//Mark the location of the message... We can copy it directly. Anyone know why it is off by 1? |
|
David@0
|
202 |
messageStart = CFXMLParserGetLocation(parser) - 1; |
|
David@0
|
203 |
|
|
David@0
|
204 |
state = XML_STATE_MESSAGE; |
|
David@0
|
205 |
} |
|
David@0
|
206 |
else if([name isEqualToString:@"event"]) |
|
David@0
|
207 |
{ |
|
David@0
|
208 |
//Mark the location of the message... We can copy it directly. Anyone know why it is off by 1? |
|
David@0
|
209 |
messageStart = CFXMLParserGetLocation(parser) - 1; |
|
David@0
|
210 |
|
|
David@0
|
211 |
state = XML_STATE_EVENT_MESSAGE; |
|
David@0
|
212 |
} |
|
David@0
|
213 |
else if([name isEqualToString:@"status"]) |
|
David@0
|
214 |
{ |
|
David@0
|
215 |
[status release]; |
|
David@0
|
216 |
[date release]; |
|
David@0
|
217 |
|
|
David@0
|
218 |
NSString *dateStr = [attributes objectForKey:@"time"]; |
|
David@0
|
219 |
if(dateStr != nil) |
|
David@0
|
220 |
date = [[NSCalendarDate calendarDateWithString:dateStr] retain]; |
|
David@0
|
221 |
else |
|
David@0
|
222 |
date = nil; |
|
David@0
|
223 |
|
|
David@0
|
224 |
status = [[attributes objectForKey:@"type"] retain]; |
|
David@0
|
225 |
|
|
David@0
|
226 |
//Mark the location of the message... We can copy it directly. Anyone know why it is off by 1? |
|
David@0
|
227 |
messageStart = CFXMLParserGetLocation(parser) - 1; |
|
David@0
|
228 |
|
|
David@0
|
229 |
state = XML_STATE_STATUS_MESSAGE; |
|
David@0
|
230 |
} |
|
David@0
|
231 |
break; |
|
David@0
|
232 |
case XML_STATE_MESSAGE: |
|
David@0
|
233 |
case XML_STATE_EVENT_MESSAGE: |
|
David@0
|
234 |
case XML_STATE_STATUS_MESSAGE: |
|
David@0
|
235 |
break; |
|
David@0
|
236 |
} |
|
David@0
|
237 |
} |
|
David@0
|
238 |
|
|
David@0
|
239 |
- (void)endedElement:(NSString *)name empty:(BOOL)empty |
|
David@0
|
240 |
{ |
|
David@0
|
241 |
switch(state) |
|
David@0
|
242 |
{ |
|
David@0
|
243 |
case XML_STATE_EVENT_MESSAGE: |
|
David@0
|
244 |
state = XML_STATE_CHAT; |
|
David@0
|
245 |
break; |
|
David@0
|
246 |
|
|
David@0
|
247 |
case XML_STATE_MESSAGE: |
|
David@0
|
248 |
if([name isEqualToString:@"message"]) |
|
David@0
|
249 |
{ |
|
David@0
|
250 |
CFIndex end = CFXMLParserGetLocation(parser); |
|
David@0
|
251 |
NSString *message = nil; |
|
David@0
|
252 |
if (!empty) { |
|
David@0
|
253 |
/* Need to unescape & now so that we'll do link detection properly when decoding the HTML. See #6850. |
|
David@0
|
254 |
* We'll let HTML decoding handle the other entities. |
|
David@0
|
255 |
* |
|
David@0
|
256 |
* 11 = 10 for </message> and 1 for the index being off |
|
David@0
|
257 |
*/ |
|
David@0
|
258 |
NSMutableString *mutableMessage = [[inputFileString substringWithRange:NSMakeRange(messageStart, end - messageStart - 11)] mutableCopy]; |
|
David@0
|
259 |
[mutableMessage replaceOccurrencesOfString:@"&" |
|
David@0
|
260 |
withString:@"&" |
|
David@0
|
261 |
options:NSLiteralSearch |
|
David@0
|
262 |
range:NSMakeRange(0, [mutableMessage length])]; |
|
sholt@2597
|
263 |
// Escape anchor tags |
|
sholt@2597
|
264 |
[mutableMessage replaceOccurrencesOfString:@"#" |
|
sholt@2597
|
265 |
withString:@"#" |
|
sholt@2597
|
266 |
options:NSLiteralSearch |
|
sholt@2597
|
267 |
range:NSMakeRange(0, [mutableMessage length])]; |
|
David@0
|
268 |
message = [mutableMessage autorelease]; |
|
David@0
|
269 |
} |
|
David@0
|
270 |
NSString *shownSender = (senderAlias ? senderAlias : sender); |
|
David@0
|
271 |
NSString *cssClass; |
|
David@0
|
272 |
NSString *displayName = nil, *longDisplayName = nil; |
|
David@0
|
273 |
|
|
David@0
|
274 |
if ([mySN isEqualToString:sender]) { |
|
David@0
|
275 |
//Find an account if one exists, and use its name |
|
David@0
|
276 |
displayName = (myDisplayName ? myDisplayName : sender); |
|
David@0
|
277 |
cssClass = @"send"; |
|
David@0
|
278 |
} else { |
|
David@89
|
279 |
AIListObject *listObject = [adium.contactController existingListObjectWithUniqueID:[AIListObject internalObjectIDForServiceID:service UID:sender]]; |
|
David@0
|
280 |
|
|
David@0
|
281 |
cssClass = @"receive"; |
|
David@837
|
282 |
displayName = listObject.displayName; |
|
David@0
|
283 |
longDisplayName = [listObject longDisplayName]; |
|
David@0
|
284 |
} |
|
David@0
|
285 |
|
|
David@0
|
286 |
if (displayName && ![displayName isEqualToString:sender]) { |
|
David@0
|
287 |
switch (nameFormat) { |
|
David@0
|
288 |
case AIDefaultName: |
|
David@0
|
289 |
shownSender = (longDisplayName ? longDisplayName : displayName); |
|
David@0
|
290 |
break; |
|
David@0
|
291 |
|
|
David@0
|
292 |
case AIDisplayName: |
|
David@0
|
293 |
shownSender = displayName; |
|
David@0
|
294 |
break; |
|
David@0
|
295 |
|
|
David@0
|
296 |
case AIDisplayName_ScreenName: |
|
David@0
|
297 |
shownSender = [NSString stringWithFormat:@"%@ (%@)",displayName,sender]; |
|
David@0
|
298 |
break; |
|
David@0
|
299 |
|
|
David@0
|
300 |
case AIScreenName_DisplayName: |
|
David@0
|
301 |
shownSender = [NSString stringWithFormat:@"%@ (%@)",sender,displayName]; |
|
David@0
|
302 |
break; |
|
David@0
|
303 |
|
|
David@0
|
304 |
case AIScreenName: |
|
David@0
|
305 |
shownSender = sender; |
|
David@0
|
306 |
break; |
|
David@0
|
307 |
} |
|
David@0
|
308 |
} |
|
David@0
|
309 |
|
|
David@625
|
310 |
NSString *timestampStr = [dateFormatter stringFromDate:date]; |
|
David@0
|
311 |
|
|
David@0
|
312 |
BOOL sentMessage = [mySN isEqualToString:sender]; |
|
David@0
|
313 |
[output appendAttributedString:[htmlDecoder decodeHTML:[NSString stringWithFormat: |
|
David@0
|
314 |
@"<div class=\"%@\">%@<span class=\"sender\">%@%@:</span></div> ", |
|
David@0
|
315 |
(sentMessage ? @"send" : @"receive"), |
|
David@0
|
316 |
(showTimestamps ? [NSString stringWithFormat:@"<span class=\"timestamp\">%@</span> ", timestampStr] : @""), |
|
David@0
|
317 |
shownSender, (autoResponse ? AILocalizedString(@" (Autoreply)", nil) : @"")]]]; |
|
David@0
|
318 |
|
|
David@0
|
319 |
NSAttributedString *attributedMessage = [htmlDecoder decodeHTML:message]; |
|
David@0
|
320 |
if (showEmoticons) { |
|
David@95
|
321 |
attributedMessage = [adium.contentController filterAttributedString:attributedMessage |
|
David@0
|
322 |
usingFilterType:AIFilterMessageDisplay |
|
David@0
|
323 |
direction:(sentMessage ? AIFilterOutgoing : AIFilterIncoming) |
|
David@0
|
324 |
context:nil]; |
|
David@0
|
325 |
} |
|
David@0
|
326 |
[output appendAttributedString:attributedMessage]; |
|
David@0
|
327 |
[output appendAttributedString:newlineAttributedString]; |
|
David@0
|
328 |
|
|
David@0
|
329 |
state = XML_STATE_CHAT; |
|
David@0
|
330 |
} |
|
David@0
|
331 |
break; |
|
David@0
|
332 |
case XML_STATE_STATUS_MESSAGE: |
|
David@0
|
333 |
if([name isEqualToString:@"status"]) |
|
David@0
|
334 |
{ |
|
David@0
|
335 |
CFIndex end = CFXMLParserGetLocation(parser); |
|
David@0
|
336 |
NSString *message = nil; |
|
David@0
|
337 |
if(!empty) |
|
David@0
|
338 |
message = [inputFileString substringWithRange:NSMakeRange(messageStart, end - messageStart - 10)]; // 9 for </status> and 1 for the index being off |
|
David@0
|
339 |
|
|
David@0
|
340 |
NSString *displayMessage = nil; |
|
David@0
|
341 |
//Note: I am diverging from what the AILoggerPlugin logs in this case. It can't handle every case we can have here |
|
David@0
|
342 |
if([message length]) |
|
David@0
|
343 |
{ |
|
zacw@1304
|
344 |
if([statusLookup objectForKey:status]) |
|
David@0
|
345 |
displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@: %@", nil), [statusLookup objectForKey:status], message]; |
|
David@0
|
346 |
else |
|
zacw@1304
|
347 |
displayMessage = [NSString stringWithFormat:AILocalizedString(@"%@", nil), message]; |
|
David@0
|
348 |
} |
|
zacw@1304
|
349 |
else if([status length] && [statusLookup objectForKey:status]) |
|
David@0
|
350 |
displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@", nil), [statusLookup objectForKey:status]]; |
|
David@0
|
351 |
|
|
David@0
|
352 |
if([displayMessage length]) |
|
David@0
|
353 |
[output appendAttributedString:[htmlDecoder decodeHTML:[NSString stringWithFormat:@"<div class=\"status\">%@ (%@)</div>\n", |
|
David@0
|
354 |
displayMessage, |
|
David@625
|
355 |
[dateFormatter stringFromDate:date]]]]; |
|
David@0
|
356 |
state = XML_STATE_CHAT; |
|
David@0
|
357 |
} |
|
David@0
|
358 |
case XML_STATE_CHAT: |
|
David@0
|
359 |
if([name isEqualToString:@"chat"]) |
|
David@0
|
360 |
state = XML_STATE_NONE; |
|
David@0
|
361 |
break; |
|
David@0
|
362 |
case XML_STATE_NONE: |
|
David@0
|
363 |
break; |
|
David@0
|
364 |
} |
|
David@0
|
365 |
} |
|
David@0
|
366 |
|
|
David@0
|
367 |
typedef struct{ |
|
David@0
|
368 |
NSString *name; |
|
David@0
|
369 |
BOOL empty; |
|
David@0
|
370 |
} element; |
|
David@0
|
371 |
|
|
David@0
|
372 |
void *createStructure(CFXMLParserRef parser, CFXMLNodeRef node, void *context) |
|
David@0
|
373 |
{ |
|
David@0
|
374 |
element *ret = NULL; |
|
David@0
|
375 |
|
|
David@0
|
376 |
// Use the dataTypeID to determine what to print. |
|
David@0
|
377 |
switch (CFXMLNodeGetTypeCode(node)) { |
|
David@0
|
378 |
case kCFXMLNodeTypeDocument: |
|
David@0
|
379 |
break; |
|
David@0
|
380 |
case kCFXMLNodeTypeElement: |
|
David@0
|
381 |
{ |
|
David@0
|
382 |
NSString *name = [NSString stringWithString:(NSString *)CFXMLNodeGetString(node)]; |
|
David@0
|
383 |
const CFXMLElementInfo *info = CFXMLNodeGetInfoPtr(node); |
|
David@0
|
384 |
[(AIXMLChatlogConverter *)context startedElement:name info:info]; |
|
David@0
|
385 |
ret = (element *)malloc(sizeof(element)); |
|
David@0
|
386 |
ret->name = [name retain]; |
|
David@0
|
387 |
ret->empty = info->isEmpty; |
|
David@0
|
388 |
break; |
|
David@0
|
389 |
} |
|
David@0
|
390 |
case kCFXMLNodeTypeProcessingInstruction: |
|
David@0
|
391 |
case kCFXMLNodeTypeComment: |
|
David@0
|
392 |
case kCFXMLNodeTypeText: |
|
David@0
|
393 |
case kCFXMLNodeTypeCDATASection: |
|
David@0
|
394 |
case kCFXMLNodeTypeEntityReference: |
|
David@0
|
395 |
case kCFXMLNodeTypeDocumentType: |
|
David@0
|
396 |
case kCFXMLNodeTypeWhitespace: |
|
David@0
|
397 |
default: |
|
David@0
|
398 |
break; |
|
David@0
|
399 |
} |
|
David@0
|
400 |
|
|
David@0
|
401 |
// Return the data string for use by the addChild and |
|
David@0
|
402 |
// endStructure callbacks. |
|
David@0
|
403 |
return (void *) ret; |
|
David@0
|
404 |
} |
|
David@0
|
405 |
|
|
David@0
|
406 |
void addChild(CFXMLParserRef parser, void *parent, void *child, void *context) |
|
David@0
|
407 |
{ |
|
David@0
|
408 |
} |
|
David@0
|
409 |
|
|
David@0
|
410 |
void endStructure(CFXMLParserRef parser, void *xmlType, void *context) |
|
David@0
|
411 |
{ |
|
David@0
|
412 |
NSString *name = nil; |
|
David@0
|
413 |
BOOL empty = NO; |
|
David@0
|
414 |
if(xmlType != NULL) |
|
David@0
|
415 |
{ |
|
David@0
|
416 |
name = [NSString stringWithString:((element *)xmlType)->name]; |
|
David@0
|
417 |
empty = ((element *)xmlType)->empty; |
|
David@0
|
418 |
} |
|
David@0
|
419 |
[(AIXMLChatlogConverter *)context endedElement:name empty:empty]; |
|
David@0
|
420 |
if(xmlType != NULL) |
|
David@0
|
421 |
{ |
|
David@0
|
422 |
[((element *)xmlType)->name release]; |
|
David@0
|
423 |
free(xmlType); |
|
David@0
|
424 |
} |
|
David@0
|
425 |
} |
|
David@0
|
426 |
|
|
David@0
|
427 |
@end |