|
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 |
|
|
colin@3271
|
34 |
@interface NSMutableString (XMLMethods) |
|
colin@3271
|
35 |
- (void)stripInvalidCharacters; |
|
colin@3271
|
36 |
@end |
|
colin@3271
|
37 |
|
|
colin@3271
|
38 |
@implementation NSMutableString (XMLMethods) |
|
colin@3271
|
39 |
|
|
colin@3271
|
40 |
//Strip invalid XML characters |
|
colin@3271
|
41 |
- (void)stripInvalidCharacters |
|
colin@3271
|
42 |
{ |
|
colin@3271
|
43 |
static NSCharacterSet *invalidXMLCharacterSet; |
|
colin@3271
|
44 |
|
|
colin@3271
|
45 |
if (invalidXMLCharacterSet == nil) |
|
colin@3271
|
46 |
{ |
|
colin@3271
|
47 |
// First, create a character set containing all valid UTF8 characters. |
|
colin@3271
|
48 |
NSMutableCharacterSet *xmlCharacterSet = [[NSMutableCharacterSet alloc] init]; |
|
colin@3271
|
49 |
[xmlCharacterSet addCharactersInRange:NSMakeRange(0x9, 1)]; |
|
colin@3271
|
50 |
[xmlCharacterSet addCharactersInRange:NSMakeRange(0xA, 1)]; |
|
colin@3271
|
51 |
[xmlCharacterSet addCharactersInRange:NSMakeRange(0xD, 1)]; |
|
colin@3271
|
52 |
[xmlCharacterSet addCharactersInRange:NSMakeRange(0x20, 0xD7FF - 0x20)]; |
|
colin@3271
|
53 |
[xmlCharacterSet addCharactersInRange:NSMakeRange(0xE000, 0xFFFD - 0xE000)]; |
|
colin@3271
|
54 |
[xmlCharacterSet addCharactersInRange:NSMakeRange(0x10000, 0x10FFFF - 0x10000)]; |
|
colin@3271
|
55 |
// Then create and retain an inverted set, which will thus contain all invalid XML characters. |
|
colin@3271
|
56 |
invalidXMLCharacterSet = [[xmlCharacterSet invertedSet] retain]; |
|
colin@3271
|
57 |
[xmlCharacterSet release]; |
|
colin@3271
|
58 |
} |
|
colin@3271
|
59 |
|
|
colin@3271
|
60 |
// Are there any invalid characters in this string? |
|
colin@3271
|
61 |
NSRange range = [self rangeOfCharacterFromSet:invalidXMLCharacterSet]; |
|
colin@3271
|
62 |
|
|
colin@3271
|
63 |
// Otherwise go through and remove any illegal XML characters from a copy of the string. |
|
colin@3271
|
64 |
while (range.length > 0) |
|
colin@3271
|
65 |
{ |
|
colin@3271
|
66 |
[self deleteCharactersInRange:range]; |
|
colin@3271
|
67 |
range = [self rangeOfCharacterFromSet:invalidXMLCharacterSet |
|
colin@3271
|
68 |
options:0 |
|
colin@3271
|
69 |
range:NSMakeRange(range.location,[self length]-range.location)]; |
|
colin@3271
|
70 |
} |
|
colin@3271
|
71 |
} |
|
colin@3271
|
72 |
|
|
colin@3271
|
73 |
@end |
|
colin@3271
|
74 |
|
|
colin@3271
|
75 |
@interface NSXMLElement (AIAttributeDict) |
|
colin@3271
|
76 |
- (NSDictionary *)AIAttributesAsDictionary; |
|
colin@3271
|
77 |
@end |
|
colin@3271
|
78 |
|
|
colin@3271
|
79 |
@implementation NSXMLElement (AIAttributeDict) |
|
colin@3271
|
80 |
|
|
colin@3271
|
81 |
- (NSDictionary *)AIAttributesAsDictionary |
|
colin@3271
|
82 |
{ |
|
colin@3271
|
83 |
NSArray *attrArray = [self attributes]; |
|
colin@3271
|
84 |
NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; |
|
colin@3271
|
85 |
for (NSXMLNode *attr in attrArray) { |
|
colin@3271
|
86 |
[attributes setObject:attr forKey:[attr name]]; |
|
colin@3271
|
87 |
} |
|
colin@3271
|
88 |
return attributes; |
|
colin@3271
|
89 |
} |
|
colin@3271
|
90 |
|
|
colin@3271
|
91 |
@end |
|
colin@3271
|
92 |
|
|
colin@3271
|
93 |
@interface AIXMLChatlogConverter() |
|
colin@3271
|
94 |
- (NSAttributedString *)readData:(NSData *)xmlData withOptions:(NSDictionary *)options retrying:(BOOL)reentrancyFlag; |
|
colin@3271
|
95 |
@end |
|
David@0
|
96 |
|
|
David@0
|
97 |
@implementation AIXMLChatlogConverter |
|
David@0
|
98 |
|
|
David@0
|
99 |
+ (NSAttributedString *)readFile:(NSString *)filePath withOptions:(NSDictionary *)options |
|
David@0
|
100 |
{ |
|
colin@3271
|
101 |
static AIXMLChatlogConverter *converter; |
|
colin@3271
|
102 |
if (!converter) { |
|
colin@3271
|
103 |
converter = [[AIXMLChatlogConverter alloc] init]; |
|
colin@3271
|
104 |
} |
|
colin@3271
|
105 |
NSData *xmlData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:filePath]]; |
|
colin@3271
|
106 |
[converter->htmlDecoder setBaseURL:[filePath stringByDeletingLastPathComponent]]; |
|
colin@3271
|
107 |
NSAttributedString *result = nil; |
|
colin@3271
|
108 |
@try { |
|
colin@3271
|
109 |
result = [converter readData:xmlData withOptions:options retrying:NO]; |
|
colin@3271
|
110 |
} @catch (NSException *e) { |
|
colin@3271
|
111 |
NSLog(@"Error \"%@\" parsing log file at %@.", e, filePath); |
|
colin@3271
|
112 |
return [[[NSAttributedString alloc] initWithString:@"Sorry, there was an error parsing this transcript. It may be corrupt."] autorelease]; |
|
colin@3271
|
113 |
} |
|
colin@3271
|
114 |
return result; |
|
David@0
|
115 |
} |
|
David@0
|
116 |
|
|
David@0
|
117 |
- (id)init |
|
David@0
|
118 |
{ |
|
David@487
|
119 |
if ((self = [super init])) { |
|
colin@3271
|
120 |
if (!newlineAttributedString) { |
|
colin@3271
|
121 |
newlineAttributedString = [[NSAttributedString alloc] initWithString:@"\n" attributes:nil]; |
|
colin@3271
|
122 |
} |
|
colin@3271
|
123 |
|
|
colin@3271
|
124 |
htmlDecoder = [[AIHTMLDecoder alloc] init]; |
|
David@625
|
125 |
dateFormatter = [[NSDateFormatter localizedDateFormatterShowingSeconds:YES showingAMorPM:YES] retain]; |
|
David@0
|
126 |
|
|
David@487
|
127 |
statusLookup = [[NSDictionary alloc] initWithObjectsAndKeys: |
|
David@487
|
128 |
AILocalizedString(@"Online", nil), @"online", |
|
David@487
|
129 |
AILocalizedString(@"Idle", nil), @"idle", |
|
David@487
|
130 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_OFFLINE], @"offline", |
|
David@487
|
131 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AWAY], @"away", |
|
David@487
|
132 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AVAILABLE], @"available", |
|
David@487
|
133 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_BUSY], @"busy", |
|
David@487
|
134 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_AT_HOME], @"notAtHome", |
|
David@487
|
135 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_PHONE], @"onThePhone", |
|
David@487
|
136 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_VACATION], @"onVacation", |
|
David@487
|
137 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_DND], @"doNotDisturb", |
|
David@487
|
138 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_EXTENDED_AWAY], @"extendedAway", |
|
David@487
|
139 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_BRB], @"beRightBack", |
|
David@487
|
140 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_AVAILABLE], @"notAvailable", |
|
David@487
|
141 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_AT_DESK], @"notAtMyDesk", |
|
David@487
|
142 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_IN_OFFICE], @"notInTheOffice", |
|
David@487
|
143 |
[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_STEPPED_OUT], @"steppedOut", |
|
colin@3271
|
144 |
nil]; |
|
David@0
|
145 |
} |
|
David@0
|
146 |
|
|
David@0
|
147 |
return self; |
|
David@0
|
148 |
} |
|
David@0
|
149 |
|
|
David@0
|
150 |
- (void)dealloc |
|
David@0
|
151 |
{ |
|
David@625
|
152 |
[dateFormatter release]; |
|
David@0
|
153 |
[statusLookup release]; |
|
David@0
|
154 |
[htmlDecoder release]; |
|
David@0
|
155 |
[super dealloc]; |
|
David@0
|
156 |
} |
|
David@0
|
157 |
|
|
colin@3271
|
158 |
- (NSAttributedString *)readData:(NSData *)xmlData withOptions:(NSDictionary *)options retrying:(BOOL)reentrancyFlag |
|
David@0
|
159 |
{ |
|
colin@3271
|
160 |
if (!xmlData) { |
|
colin@3271
|
161 |
return [[[NSAttributedString alloc] initWithString:@""] autorelease]; |
|
colin@3271
|
162 |
} |
|
colin@3271
|
163 |
AINameFormat nameFormat; |
|
colin@3271
|
164 |
if ([[adium.preferenceController preferenceForKey:KEY_WEBKIT_USE_NAME_FORMAT |
|
colin@3271
|
165 |
group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] boolValue]) { |
|
colin@3271
|
166 |
nameFormat = [[adium.preferenceController preferenceForKey:KEY_WEBKIT_NAME_FORMAT |
|
colin@3271
|
167 |
group:PREF_GROUP_WEBKIT_MESSAGE_DISPLAY] intValue]; |
|
colin@3271
|
168 |
} else { |
|
colin@3271
|
169 |
nameFormat = AIDefaultName; |
|
colin@3271
|
170 |
} |
|
colin@3271
|
171 |
NSMutableAttributedString *output = [[[NSMutableAttributedString alloc] init] autorelease]; |
|
colin@3271
|
172 |
|
|
colin@3271
|
173 |
NSError *err=nil; |
|
colin@3271
|
174 |
NSXMLDocument *xmlDoc = [[[NSXMLDocument alloc] initWithData:xmlData |
|
colin@3271
|
175 |
options:NSXMLNodePreserveCDATA |
|
colin@3271
|
176 |
error:&err] autorelease]; |
|
David@0
|
177 |
|
|
colin@3271
|
178 |
if (!xmlDoc) |
|
colin@3271
|
179 |
{ |
|
colin@3271
|
180 |
goto ohno; |
|
colin@3271
|
181 |
} |
|
colin@3271
|
182 |
|
|
colin@3271
|
183 |
BOOL showTimestamps = [[options objectForKey:@"showTimestamps"] boolValue]; |
|
colin@3271
|
184 |
BOOL showEmoticons = [[options objectForKey:@"showEmoticons"] boolValue]; |
|
colin@3271
|
185 |
|
|
colin@3271
|
186 |
NSXMLElement *chatElement = [[xmlDoc nodesForXPath:@"//chat" error:&err] lastObject]; |
|
colin@3271
|
187 |
|
|
colin@3271
|
188 |
NSDictionary *chatAttributes = [chatElement AIAttributesAsDictionary]; |
|
colin@3271
|
189 |
NSString *mySN = [[chatAttributes objectForKey:@"account"] stringValue]; |
|
colin@3271
|
190 |
NSString *service = [[chatAttributes objectForKey:@"service"] stringValue]; |
|
colin@3271
|
191 |
|
|
colin@3271
|
192 |
NSString *myDisplayName = nil; |
|
colin@3271
|
193 |
|
|
colin@3271
|
194 |
for (AIAccount *account in adium.accountController.accounts) { |
|
colin@3271
|
195 |
if ([[account.UID compactedString] isEqualToString:[mySN compactedString]] && |
|
colin@3271
|
196 |
[account.service.serviceID isEqualToString:service]) { |
|
colin@3271
|
197 |
myDisplayName = [account.displayName retain]; |
|
colin@3271
|
198 |
break; |
|
colin@3271
|
199 |
} |
|
colin@3271
|
200 |
} |
|
colin@3271
|
201 |
|
|
colin@3271
|
202 |
NSArray *elements = [xmlDoc nodesForXPath:@"//message | //status" error:&err]; |
|
colin@3271
|
203 |
if (!elements) { |
|
colin@3271
|
204 |
goto ohno; |
|
colin@3271
|
205 |
} |
|
colin@3271
|
206 |
|
|
colin@3271
|
207 |
for (NSXMLElement *element in elements) { |
|
colin@3271
|
208 |
NSString *type = [element name]; |
|
colin@3271
|
209 |
|
|
colin@3271
|
210 |
NSDictionary *attributes = [element AIAttributesAsDictionary]; |
|
colin@3271
|
211 |
|
|
colin@3271
|
212 |
if ([type isEqualToString:@"message"]) { |
|
colin@3271
|
213 |
NSString *senderAlias = [[attributes objectForKey:@"alias"] stringValue]; |
|
colin@3271
|
214 |
NSString *dateStr = [[attributes objectForKey:@"time"] stringValue]; |
|
colin@3271
|
215 |
NSDate *date = dateStr ? [NSCalendarDate calendarDateWithString:dateStr] : nil; |
|
colin@3271
|
216 |
NSString *sender = [[attributes objectForKey:@"sender"] stringValue]; |
|
colin@3271
|
217 |
NSString *shownSender = (senderAlias ? senderAlias : sender); |
|
colin@3271
|
218 |
BOOL autoResponse = [[[attributes objectForKey:@"auto"] stringValue] isEqualToString:@"true"]; |
|
David@0
|
219 |
|
|
colin@3271
|
220 |
NSMutableString *messageXML = [NSMutableString string]; |
|
colin@3271
|
221 |
for (NSXMLNode *node in [element children]) { |
|
colin@3271
|
222 |
[messageXML appendString:[node XMLString]]; |
|
colin@3271
|
223 |
} |
|
colin@3271
|
224 |
|
|
colin@3271
|
225 |
NSString *displayName = nil, *longDisplayName = nil; |
|
colin@3271
|
226 |
|
|
colin@3271
|
227 |
BOOL sentMessage = [mySN isEqualToString:sender]; |
|
David@0
|
228 |
|
|
colin@3271
|
229 |
|
|
colin@3271
|
230 |
if (sentMessage) { |
|
colin@3271
|
231 |
//Find an account if one exists, and use its name |
|
colin@3271
|
232 |
displayName = (myDisplayName ? myDisplayName : sender); |
|
colin@3271
|
233 |
} else { |
|
colin@3271
|
234 |
AIListObject *listObject = [adium.contactController existingListObjectWithUniqueID:[AIListObject internalObjectIDForServiceID:service UID:sender]]; |
|
colin@3271
|
235 |
|
|
colin@3271
|
236 |
displayName = listObject.displayName; |
|
colin@3271
|
237 |
longDisplayName = [listObject longDisplayName]; |
|
colin@3271
|
238 |
} |
|
colin@3271
|
239 |
|
|
colin@3271
|
240 |
if (displayName && !sentMessage) { |
|
colin@3271
|
241 |
switch (nameFormat) { |
|
colin@3271
|
242 |
case AIDefaultName: |
|
colin@3271
|
243 |
shownSender = (longDisplayName ? longDisplayName : displayName); |
|
colin@3271
|
244 |
break; |
|
colin@3271
|
245 |
|
|
colin@3271
|
246 |
case AIDisplayName: |
|
colin@3271
|
247 |
shownSender = displayName; |
|
colin@3271
|
248 |
break; |
|
colin@3271
|
249 |
|
|
colin@3271
|
250 |
case AIDisplayName_ScreenName: |
|
colin@3271
|
251 |
shownSender = [NSString stringWithFormat:@"%@ (%@)",displayName,sender]; |
|
colin@3271
|
252 |
break; |
|
colin@3271
|
253 |
|
|
colin@3271
|
254 |
case AIScreenName_DisplayName: |
|
colin@3271
|
255 |
shownSender = [NSString stringWithFormat:@"%@ (%@)",sender,displayName]; |
|
colin@3271
|
256 |
break; |
|
colin@3271
|
257 |
|
|
colin@3271
|
258 |
case AIScreenName: |
|
colin@3271
|
259 |
shownSender = sender; |
|
colin@3271
|
260 |
break; |
|
colin@3271
|
261 |
} |
|
colin@3271
|
262 |
} |
|
David@0
|
263 |
|
|
colin@3271
|
264 |
NSString *timestampStr = [dateFormatter stringFromDate:date]; |
|
David@0
|
265 |
|
|
colin@3271
|
266 |
[output appendAttributedString:[htmlDecoder decodeHTML:[NSString stringWithFormat: |
|
colin@3271
|
267 |
@"<div class=\"%@\">%@<span class=\"sender\">%@%@:</span></div> ", |
|
colin@3271
|
268 |
(sentMessage ? @"send" : @"receive"), |
|
colin@3271
|
269 |
(showTimestamps ? [NSString stringWithFormat:@"<span class=\"timestamp\">%@</span> ", timestampStr] : @""), |
|
colin@3271
|
270 |
shownSender, (autoResponse ? AILocalizedString(@" (Autoreply)", nil) : @"")]]]; |
|
David@0
|
271 |
|
|
colin@3271
|
272 |
NSAttributedString *attributedMessage = [htmlDecoder decodeHTML:messageXML]; |
|
colin@3271
|
273 |
if (showEmoticons) { |
|
colin@3271
|
274 |
attributedMessage = [adium.contentController filterAttributedString:attributedMessage |
|
colin@3271
|
275 |
usingFilterType:AIFilterMessageDisplay |
|
colin@3271
|
276 |
direction:(sentMessage ? AIFilterOutgoing : AIFilterIncoming) |
|
colin@3271
|
277 |
context:nil]; |
|
colin@3271
|
278 |
} |
|
colin@3271
|
279 |
[output appendAttributedString:attributedMessage]; |
|
colin@3271
|
280 |
[output appendAttributedString:newlineAttributedString]; |
|
colin@3271
|
281 |
} else if ([type isEqualToString:@"status"]) { |
|
colin@3271
|
282 |
NSString *dateStr = [[attributes objectForKey:@"time"] stringValue]; |
|
colin@3271
|
283 |
NSDate *date = dateStr ? [NSCalendarDate calendarDateWithString:dateStr] : nil; |
|
colin@3271
|
284 |
NSString *status = [[attributes objectForKey:@"type"] stringValue]; |
|
colin@3271
|
285 |
|
|
colin@3271
|
286 |
NSMutableString *messageXML = [NSMutableString string]; |
|
colin@3271
|
287 |
for (NSXMLNode *node in [element children]) { |
|
colin@3271
|
288 |
[messageXML appendString:[node XMLString]]; |
|
colin@3271
|
289 |
} |
|
colin@3271
|
290 |
|
|
colin@3271
|
291 |
NSString *displayMessage = nil; |
|
colin@3271
|
292 |
//Note: I am diverging from what the AILoggerPlugin logs in this case. It can't handle every case we can have here |
|
colin@3271
|
293 |
if([messageXML length]) { |
|
colin@3271
|
294 |
if([statusLookup objectForKey:status]) { |
|
colin@3271
|
295 |
displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@: %@", nil), [statusLookup objectForKey:status], messageXML]; |
|
colin@3271
|
296 |
} else { |
|
colin@3271
|
297 |
displayMessage = [NSString stringWithFormat:AILocalizedString(@"%@", nil), messageXML]; |
|
colin@3271
|
298 |
} |
|
colin@3271
|
299 |
} else if([status length] && [statusLookup objectForKey:status]) { |
|
colin@3271
|
300 |
displayMessage = [NSString stringWithFormat:AILocalizedString(@"Changed status to %@", nil), [statusLookup objectForKey:status]]; |
|
colin@3271
|
301 |
} |
|
colin@3271
|
302 |
|
|
colin@3271
|
303 |
if([displayMessage length]) { |
|
colin@3271
|
304 |
[output appendAttributedString:[htmlDecoder decodeHTML:[NSString stringWithFormat:@"<div class=\"status\">%@ (%@)</div>\n", |
|
colin@3271
|
305 |
displayMessage, |
|
colin@3271
|
306 |
[dateFormatter stringFromDate:date]]]]; |
|
colin@3271
|
307 |
} |
|
colin@3271
|
308 |
} |
|
colin@3271
|
309 |
} |
|
colin@3271
|
310 |
|
|
colin@3271
|
311 |
return output; |
|
colin@3271
|
312 |
|
|
colin@3271
|
313 |
ohno: |
|
colin@3271
|
314 |
if (!reentrancyFlag) { |
|
colin@3271
|
315 |
NSMutableString *xmlString = [NSMutableString stringWithUTF8String:[xmlData bytes]]; |
|
colin@3271
|
316 |
[xmlString stripInvalidCharacters]; |
|
colin@3271
|
317 |
return [self readData:[xmlString dataUsingEncoding:NSUTF8StringEncoding] withOptions:options retrying:YES]; |
|
colin@3271
|
318 |
} |
|
colin@3271
|
319 |
@throw [NSException exceptionWithName:@"Log File Parsing Error" reason:[err description] userInfo:nil]; |
|
David@0
|
320 |
} |
|
David@0
|
321 |
|
|
David@0
|
322 |
@end |