Repository navigation
Expand file tree
/
Copy pathMDBufferedInputStream.m
More file actions
260 lines (231 loc) · 7.87 KB
/
Copy pathMDBufferedInputStream.m
File metadata and controls
260 lines (231 loc) · 7.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
//
// MDBufferedInputStream.m
// A NSInputStream decorator that adds buffering and text parsing functionality
// Text is returned line by line through the readLine method
//
// Created by Federico Mestrone on 07/04/2010.
// Copyright 2010 Moodsdesign Ltd. All rights reserved.
//
/*
TODO
read the first kilobyte or so of a file, and scan for \r. If you find
one, look at the next character and see if it's a \n. If so, it's a
DOS-format (\r\n) file. If not, it's an old-style Mac (\r) text
file. If you don't see a \r in the beginning of the file at all,
assume it's a new-style Mac or Linux/Unix file (\n)
*/
#import "MDBufferedInputStream.h"
@interface MDBufferedInputStream (PrivateMethods)
- (NSString *) csvReadToken:(NSString *)line fromIndex:(int *)pos;
@end
@implementation MDBufferedInputStream
@synthesize bytesProcessed;
@synthesize wantsEmptyLines;
@synthesize trimLines;
@synthesize csvTitles;
#pragma mark -
#pragma mark Initialisation
- (id) initWithInputStream:(NSInputStream *)_stream bufferSize:(NSUInteger)_bufSize encoding:(NSStringEncoding)_encoding {
if ( (self = [super init]) ) {
stream = [_stream retain];
bufSize = _bufSize;
encoding = _encoding;
wantsEmptyLines = NO;
quote = '"';
separator = ',';
}
return self;
}
#pragma mark -
#pragma mark MDBufferedInputStream methods
/*
Breaks on \r\n and \n. Does not support \r-only (old Mac) line breaks.
This method is not re-entrant.
*/
- (NSString *) readLine {
// emptying out the line buffer - this method will keep on reading
// from the byte buffer and the stream until it can return a string,
// either because it found a new line, or because it reached EOF
[lineBuffer setLength:0];
// make sure you start from where we left off with the previous call
NSUInteger offset = pos;
// this specifies where in the byte buffer the new line element was
// found, but also serves as flag (if -1, no new line was found)
NSInteger found = -1;
do {
if ( pos >= read ) {
// we have reached the end of the byte buffer, read another chunk
read = [stream read:dataBuffer maxLength:bufSize];
// reset position in buffer and offset
pos = offset = 0;
if ( read <= 0 ) {
// nothing was read, we have reached EOF
if ( [lineBuffer length] ) {
// if there are bytes in the line buffer, make sure whatever is there
// is returned as the last line of the file
break;
} else {
// no byte in the byte buffer, so signal that we have finished parsing
return nil;
}
}
}
// this loop looks for line termination markers
for ( ; pos < read; ++pos ) {
// don't break on \r, it will be removed later
if ( dataBuffer[pos] == '\n' ) {
// found one, save its position
found = pos;
// move the pointer forward to the character at the beginning of the new line
++pos;
break;
}
}
// add the processed bytes to the line buffer
[lineBuffer appendBytes:&dataBuffer[offset] length:(found < 0 ? read : found) - offset];
// should not return empty lines?
if ( found >= 0 && !wantsEmptyLines && ![lineBuffer length] ) {
// if so, cannot exit now
found = -1;
// move to next character of new line (???)
++pos;
// make sure you start from the new line
offset = pos;
}
// if a new line was not found, read more from the stream and continue
} while ( found < 0 /* && read */ );
// TODO not very good multithread support - if bytesProcessed is read by another thread in the middle
// of the execution of this method it will return inconsistent values - but it's ok for now
// Also note we increase bytesProcessed in readLine as it doesn't invoke [self read:maxLength:],
// but rather [stream read:maxLength:] directly, otherwise increasing in [MDBufferedInputStream read:maxLength:]
// would be enough
NSUInteger length = [lineBuffer length];
bytesProcessed += length;
// create a new string with the line buffer
// TODO check for leak issues with this code
const uint8_t *bytes = [lineBuffer bytes];
if (length > 0 && bytes[length - 1] == '\r') {
length -= 1; // remove last \r to interpret \r\n as line break
}
NSString *line = [[[NSString alloc] initWithBytes:bytes length:length encoding:encoding] autorelease];
if ( trimLines ) {
line = [line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
return line;
}
#pragma mark -
#pragma mark Reading CSV Files
- (NSString *) csvReadToken:(NSString *)line fromIndex:(int *)p {
if ( *p >= [line length] ) {
return nil;
}
NSMutableString *buffer = [[NSMutableString alloc] init];
BOOL atStart = YES;
BOOL inQuote = NO;
BOOL afterQuote = NO;
for ( ; *p < [line length]; ++*p ) {
unichar c = [line characterAtIndex:*p];
if ( inQuote ) {
if ( c == quote ) {
inQuote = NO;
afterQuote = YES;
} else {
[buffer appendString:[NSString stringWithFormat:@"%C", c]];
}
continue;
} else if ( atStart && c == quote ) {
inQuote = YES;
atStart = NO;
continue;
}
if ( c == separator ) {
++*p;
break;
}
if ( (atStart || afterQuote) && [[NSCharacterSet whitespaceCharacterSet] characterIsMember:c] ) {
continue;
}
atStart = NO;
[buffer appendString:[NSString stringWithFormat:@"%C", c]];
}
return buffer;
}
// Read the line of CSV header titles from the underlying stream
- (NSArray *) csvReadHeader {
NSString *headline = [self readLine];
if ( !headline ) return nil;
NSMutableArray *titles = [[NSMutableArray alloc] init];
int p = 0;
NSString *token;
while ( (token = [self csvReadToken:headline fromIndex:&p]) ) {
[titles addObject:token];
}
csvTitles = [titles retain];
return titles;
}
// Read a new line of CSV text data from the underlying stream
- (NSDictionary *) csvReadData {
NSString *dataline = [self readLine];
if ( !dataline ) return nil;
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
int p = 0, f = 0;
NSString *token;
while ( (token = [self csvReadToken:dataline fromIndex:&p]) ) {
NSString *fieldName;
if ( csvTitles && f < [csvTitles count] ) {
fieldName = [csvTitles objectAtIndex:f];
} else {
fieldName = [NSString stringWithFormat:@"field%d", f];
if ( csvTitles ) {
NSLog(@"Warning: possible error in following line\n%@\n", dataline);
}
}
[dict setObject:token forKey:fieldName];
++f;
}
return dict;
}
#pragma mark -
#pragma mark NSInputStream methods
- (void) open {
if ( (shouldCloseStream = ([stream streamStatus] == kCFStreamStatusNotOpen)) ) {
// If the underlying stream is not already open, we open it ourselves
// and we will close it when the decorator is closed
[stream open];
}
lineBuffer = [[NSMutableData alloc] init];
dataBuffer = calloc(bufSize, sizeof(uint8_t));
pos = 0;
read = 0;
bytesProcessed = 0;
}
- (BOOL) hasBytesAvailable {
return [stream hasBytesAvailable];
}
// TODO define the semantics of this method within the decorator
- (NSInteger) read:(uint8_t *)buffer maxLength:(NSUInteger)len {
return (bytesProcessed += [stream read:buffer maxLength:len]);
}
- (BOOL) getBuffer:(uint8_t **)buffer length:(NSUInteger *)len {
return [stream getBuffer:buffer length:len];
}
- (void) close {
[csvTitles release];
[lineBuffer release];
free(dataBuffer);
csvTitles = nil;
lineBuffer = nil;
dataBuffer = nil;
if ( shouldCloseStream ) {
// Close the underlying stream if is was opened by this decorator
[stream close];
}
}
#pragma mark -
#pragma mark Deallocation
- (void) dealloc {
[self close];
[stream release];
[super dealloc];
}
@end