Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(265)

Side by Side Diff: runtime/bin/string_stream.dart

Issue 8772008: Add line handler to string stream (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « runtime/bin/input_stream.dart ('k') | tests/standalone/src/StringStreamTest.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 // Interface for decoders decoding binary data into objects of type T. 5 // Interface for decoders decoding binary data into string data. The
6 interface _Decoder<T> { 6 // decoder keeps track of line breaks during decoding.
7 interface _StringDecoder {
7 // Add more binary data to be decoded. The ownership of the buffer 8 // Add more binary data to be decoded. The ownership of the buffer
8 // is transfered to the decoder and the caller most not modify it any more. 9 // is transfered to the decoder and the caller most not modify it any more.
9 int write(List<int> buffer); 10 int write(List<int> buffer);
10 11
11 // Returns whether any decoded data is available. 12 // Returns whether any decoded data is available.
12 bool isEmpty(); 13 bool isEmpty();
13 14
14 // Get the data decoded since the last call to decode. 15 // Get the number of line breaks present in the current decoded
15 T get decoded(); 16 // data.
17 int lineBreaks();
18
19 // Get the string data decoded since the last call to [decode] or
20 // [decodeLine]. Returns null if no decoded data is available.
21 String get decoded();
22
23 // Get the string data decoded since the last call to [decode] or
24 // [decodeLine] up to the next line break present. Returns null if
25 // no line break is present. The line break character sequence is
26 // discarded.
27 String get decodedLine();
16 } 28 }
17 29
18 30
19 class DecoderException implements Exception { 31 class DecoderException implements Exception {
20 const DecoderException([String this.message]); 32 const DecoderException([String this.message]);
21 String toString() => "DecoderException: $message"; 33 String toString() => "DecoderException: $message";
22 final String message; 34 final String message;
23 } 35 }
24 36
25 37
26 // Utility class for decoding UTF-8 from data delivered as a stream of 38 // Utility class for decoding UTF-8 from data delivered as a stream of
27 // bytes. 39 // bytes.
28 class _StringDecoderBase implements _Decoder<String> { 40 class _StringDecoderBase implements _StringDecoder {
29 _StringDecoderBase() 41 _StringDecoderBase()
30 : _bufferList = new _BufferList(), 42 : _bufferList = new _BufferList(),
31 _result = new List<int>(); 43 _result = new List<int>(),
44 _lineBreakEnds = new Queue<int>();
32 45
33 // Add UTF-8 encoded data.
34 int write(List<int> buffer) { 46 int write(List<int> buffer) {
35 _bufferList.add(buffer); 47 _bufferList.add(buffer);
36 // Decode as many bytes into characters as possible. 48 // Decode as many bytes into characters as possible.
37 while (_bufferList.length > 0) { 49 while (_bufferList.length > 0) {
38 if (!_processNext()) { 50 if (!_processNext()) {
39 break; 51 break;
40 } 52 }
41 } 53 }
42 return buffer.length; 54 return buffer.length;
43 } 55 }
44 56
45 // Check if any characters have been decoded since the last call to decode.
46 bool isEmpty() { 57 bool isEmpty() {
47 return _result.isEmpty(); 58 return _result.isEmpty();
48 } 59 }
49 60
50 // Return the string decoded since the last call to decode. 61 int get lineBreaks() => _lineBreaks;
62
51 String get decoded() { 63 String get decoded() {
52 if (isEmpty()) { 64 if (isEmpty()) return null;
53 return null; 65
54 } else { 66 String result = new String.fromCharCodes(_result);
55 String result = new String.fromCharCodes(_result); 67 _charOffset += result.length;
56 _result = new List<int>(); 68 while (!_lineBreakEnds.isEmpty() && _lineBreakEnds.first() < _charOffset) {
57 return result; 69 _lineBreakEnds.removeFirst();
70 _lineBreaks--;
58 } 71 }
72 _result = new List<int>();
73 return result;
74 }
75
76 String get decodedLine() {
77 if (_lineBreakEnds.isEmpty()) return null;
78 int lineEnd = _lineBreakEnds.removeFirst();
79 int terminationSequenceLength = 1;
80 if (_result[lineEnd - _charOffset] == LF &&
81 lineEnd > _charOffset &&
82 _result[lineEnd - _charOffset - 1] == CR) {
83 terminationSequenceLength = 2;
84 }
85 var lineLength = lineEnd - _charOffset - terminationSequenceLength + 1;
86 String result = new String.fromCharCodes(_result.getRange(0, lineLength));
87 _lineBreaks--;
88 int removeCount = lineLength + terminationSequenceLength;
89 _result = _result.getRange(removeCount, _result.length - removeCount);
90 _charOffset = lineEnd + 1;
91 return result;
92 }
93
94 // Add another decoded character.
95 void addChar(int charCode) {
96 _result.add(charCode);
97 _charCount++;
98 // Check for line ends (\r, \n and \r\n).
99 if (charCode == LF) {
100 _recordLineBreakEnd(_charCount - 1);
101 } else if (_lastCharCode == CR) {
102 _recordLineBreakEnd(_charCount - 2);
103 }
104 _lastCharCode = charCode;
105 }
106
107 void _recordLineBreakEnd(int charPos) {
108 _lineBreakEnds.add(charPos);
109 _lineBreaks++;
59 } 110 }
60 111
61 abstract bool _processNext(); 112 abstract bool _processNext();
62 113
63 _BufferList _bufferList; 114 _BufferList _bufferList;
64 List<int> _result; 115 List<int> _result;
116 int _lineBreaks = 0; // Number of line breaks in the current list.
117 // The positions of the line breaks are tracked in terms of absolute
118 // character positions from the begining of the decoded data.
119 Queue<int> _lineBreakEnds; // Character position of known line breaks.
120 int _charOffset = 0; // Character number of the first character in the list.
121 int _charCount = 0; // Total number of characters decodes.
122 int _lastCharCode = -1;
123
124 final int LF = 10;
125 final int CR = 13;
65 } 126 }
66 127
67 128
68 // Utility class for decoding ascii data delivered as a stream of 129 // Utility class for decoding ascii data delivered as a stream of
69 // bytes. 130 // bytes.
70 class _AsciiDecoder extends _StringDecoderBase { 131 class _AsciiDecoder extends _StringDecoderBase {
71 // Process the next ascii encoded character. 132 // Process the next ascii encoded character.
72 bool _processNext() { 133 bool _processNext() {
73 while (_bufferList.length > 0) { 134 while (_bufferList.length > 0) {
74 int byte = _bufferList.next(); 135 int byte = _bufferList.next();
75 if (byte > 127) { 136 if (byte > 127) {
76 throw new DecoderException("Illegal ASCII character $byte"); 137 throw new DecoderException("Illegal ASCII character $byte");
77 } 138 }
78 _result.add(byte); 139 addChar(byte);
79 } 140 }
80 return true; 141 return true;
81 } 142 }
82 } 143 }
83 144
84 145
85 // Utility class for decoding Latin-1 data delivered as a stream of 146 // Utility class for decoding Latin-1 data delivered as a stream of
86 // bytes. 147 // bytes.
87 class _Latin1Decoder extends _StringDecoderBase { 148 class _Latin1Decoder extends _StringDecoderBase {
88 // Process the next Latin-1 encoded character. 149 // Process the next Latin-1 encoded character.
89 bool _processNext() { 150 bool _processNext() {
90 while (_bufferList.length > 0) { 151 while (_bufferList.length > 0) {
91 int byte = _bufferList.next(); 152 int byte = _bufferList.next();
92 _result.add(byte); 153 addChar(byte);
93 } 154 }
94 return true; 155 return true;
95 } 156 }
96 } 157 }
97 158
98 159
99 // Utility class for decoding UTF-8 from data delivered as a stream of 160 // Utility class for decoding UTF-8 from data delivered as a stream of
100 // bytes. 161 // bytes.
101 class _UTF8Decoder extends _StringDecoderBase { 162 class _UTF8Decoder extends _StringDecoderBase {
102 // Process the next UTF-8 encoded character. 163 // Process the next UTF-8 encoded character.
(...skipping 21 matching lines...) Expand all
124 // Remove the value peeked from the buffer list. 185 // Remove the value peeked from the buffer list.
125 _bufferList.next(); 186 _bufferList.next();
126 for (int i = 0; i < additionalBytes; i++) { 187 for (int i = 0; i < additionalBytes; i++) {
127 int byte = _bufferList.next(); 188 int byte = _bufferList.next();
128 value = value << 6 | (byte & 0x3F); 189 value = value << 6 | (byte & 0x3F);
129 } 190 }
130 } else { 191 } else {
131 // Remove the value peeked from the buffer list. 192 // Remove the value peeked from the buffer list.
132 _bufferList.next(); 193 _bufferList.next();
133 } 194 }
134 _result.add(value); 195 addChar(value);
135 return true; 196 return true;
136 } 197 }
137 } 198 }
138 199
139 200
140 class _StringInputStream implements StringInputStream { 201 class _StringInputStream implements StringInputStream {
141 _StringInputStream(InputStream this._input, [String encoding]) 202 _StringInputStream(InputStream this._input, [String encoding])
142 : _encoding = encoding { 203 : _encoding = encoding {
143 if (_encoding === null) { 204 if (_encoding === null) {
144 _encoding = "UTF-8"; 205 _encoding = "UTF-8";
145 } 206 }
146 if (_encoding == "UTF-8") { 207 if (_encoding == "UTF-8") {
147 _decoder = new _UTF8Decoder(); 208 _decoder = new _UTF8Decoder();
148 } else if (_encoding == "ISO-8859-1") { 209 } else if (_encoding == "ISO-8859-1") {
149 _decoder = new _Latin1Decoder(); 210 _decoder = new _Latin1Decoder();
150 } else if (_encoding == "ASCII") { 211 } else if (_encoding == "ASCII") {
151 _decoder = new _AsciiDecoder(); 212 _decoder = new _AsciiDecoder();
152 } else { 213 } else {
153 throw new StreamException("Unsupported encoding $_encoding"); 214 throw new StreamException("Unsupported encoding $_encoding");
154 } 215 }
155 _input.dataHandler = _dataHandler; 216 _input.dataHandler = _dataHandler;
156 _input.closeHandler = _closeHandler; 217 _input.closeHandler = _closeHandler;
157 } 218 }
158 219
159 String read() { 220 String read() {
160 // If there is buffered data return that first. 221 // If there is buffered data return that first.
161 var decodedString = _decoder.decoded; 222 var decodedString = _decoder.decoded;
162 if (_buffer !== null) { 223 if (decodedString !== null) {
163 var result = _buffer; 224 if (_inputClosed && _decoder.isEmpty()) {
164 _resetBuffer(); 225 _streamClosed();
165 if (decodedString !== null) result += decodedString; 226 }
166 return result; 227 return decodedString;
228 } else if (_inputClosed) {
229 _streamClosed();
230 return null;
167 } else { 231 } else {
168 if (decodedString !== null) { 232 _readData();
169 return decodedString; 233 return _decoder.decoded;
170 } else if (_inputClosed) {
171 _streamClosed();
172 return null;
173 } else {
174 _readData();
175 return _decoder.decoded;
176 }
177 } 234 }
178 } 235 }
179 236
180 String readLine() { 237 String readLine() {
181 if (_closed) return null; 238 if (_closed) return null;
182 // Get line from the buffer if possible. 239
183 if (_buffer !== null) { 240 if (_decoder.lineBreaks == 0) {
184 var result = _readLineFromBuffer(); 241 _readData();
185 if (result !== null) return result;
186 } 242 }
187 // Try to fill more data into the buffer and read a line. 243 var decodedLine = _decoder.decodedLine;
188 if (_fillBuffer()) { 244 if (decodedLine !== null) {
189 if (_eof && _buffer === null) { 245 if (_inputClosed && _decoder.isEmpty()) {
190 _streamClosed(); 246 _streamClosed();
191 return null;
192 } 247 }
193 return _readLineFromBuffer(); 248 return decodedLine;
249 }
250 if (_inputClosed) {
251 decodedLine = _decoder.decoded;
252 if (decodedLine[decodedLine.length - 1] == '\r') {
253 decodedLine = decodedLine.substring(0, decodedLine.length - 1);
254 }
255 _streamClosed();
256 return decodedLine;
194 } 257 }
195 return null; 258 return null;
196 } 259 }
197 260
198 String get encoding() => _encoding; 261 String get encoding() => _encoding;
199 262
200 bool get closed() => _closed; 263 bool get closed() => _closed;
201 264
202 void set dataHandler(void callback()) { 265 void set dataHandler(void callback()) {
203 _clientDataHandler = callback; 266 _clientDataHandler = callback;
267 _clientLineHandler = null;
268 }
269
270 void set lineHandler(void callback()) {
271 _clientLineHandler = callback;
272 _clientDataHandler = null;
204 } 273 }
205 274
206 void set closeHandler(void callback()) { 275 void set closeHandler(void callback()) {
207 _clientCloseHandler = callback; 276 _clientCloseHandler = callback;
208 } 277 }
209 278
210 void _dataHandler() { 279 void _dataHandler() {
211 _readData(); 280 _readData();
212 if (!_decoder.isEmpty() && _clientDataHandler !== null) { 281 if (!_decoder.isEmpty() && _clientDataHandler !== null) {
213 _clientDataHandler(); 282 _clientDataHandler();
214 } 283 }
284 if (_decoder.lineBreaks > 0 && _clientLineHandler !== null) {
285 _clientLineHandler();
286 }
215 } 287 }
216 288
217 void _closeHandler() { 289 void _closeHandler() {
218 _inputClosed = true; 290 _inputClosed = true;
219 if (_buffer !== null || !_decoder.isEmpty()) { 291 if (!_decoder.isEmpty()) {
220 // If there is still data buffered in either the buffer or the 292 // If there is still data buffered call the data handler.
221 // decoder call the data handler.
222 if (_clientDataHandler !== null) _clientDataHandler(); 293 if (_clientDataHandler !== null) _clientDataHandler();
294 if (_clientLineHandler !== null) _clientLineHandler();
223 } else { 295 } else {
224 _closed = true; 296 _closed = true;
225 if (_clientCloseHandler !== null) _clientCloseHandler(); 297 if (_clientCloseHandler !== null) _clientCloseHandler();
226 } 298 }
227 } 299 }
228 300
229 void _readData() { 301 void _readData() {
230 List<int> data = _input.read(); 302 List<int> data = _input.read();
231 if (data !== null) { 303 if (data !== null) {
232 _decoder.write(data); 304 _decoder.write(data);
233 } 305 }
234 } 306 }
235 307
236 String _readLineFromBuffer() {
237 // Both \n or \r indicates a new line. If \r is followed by \n the
238 // \n is part of the line breaking character.
239 for (int i = _bufferLineStart; i < _buffer.length; i++) {
240 String char = _buffer[i];
241 if (char == '\r') {
242 if (i == _buffer.length - 1) {
243 if (_eof) {
244 var result = _buffer.substring(_bufferLineStart, i);
245 _resetBuffer();
246 _streamClosed();
247 return result;
248 } else {
249 return null;
250 }
251 }
252 var result = _buffer.substring(_bufferLineStart, i);
253 _bufferLineStart = i + 1;
254 if (_buffer[_bufferLineStart] == '\n') _bufferLineStart++;
255 if (_bufferLineStart == _buffer.length) _resetBuffer();
256 return result;
257 } else if (char == '\n') {
258 var result = _buffer.substring(_bufferLineStart, i);
259 _bufferLineStart = i + 1;
260 if (_bufferLineStart == _buffer.length) _resetBuffer();
261 return result;
262 }
263 }
264 if (_eof) {
265 var result = _buffer;
266 _resetBuffer();
267 _streamClosed();
268 return result;
269 }
270 return null;
271 }
272
273 void _resetBuffer() {
274 _buffer = null;
275 _bufferLineStart = null;
276 }
277
278 // Fill decoded data into the buffer. Returns true if more data was
279 // added or end of file was reached.
280 bool _fillBuffer() {
281 if (_eof) return false;
282 if (!_inputClosed) _readData();
283 var decodedString = _decoder.decoded;
284 if (decodedString === null && _inputClosed) {
285 _eof = true;
286 return true;
287 }
288 if (_buffer === null) {
289 _buffer = decodedString;
290 if (_buffer !== null) {
291 _bufferLineStart = 0;
292 return true;
293 }
294 } else if (decodedString !== null) {
295 _buffer = _buffer.substring(_bufferLineStart) + decodedString;
296 _bufferLineStart = 0;
297 return true;
298 }
299 return false;
300 }
301
302 void _streamClosed() { 308 void _streamClosed() {
303 _closed = true; 309 _closed = true;
304 310
305 // TODO(sgjesse): Find a better way of scheduling callbacks from 311 // TODO(sgjesse): Find a better way of scheduling callbacks from
306 // the event loop. 312 // the event loop.
307 void issueCloseCallback(Timer timer) { 313 void issueCloseCallback(Timer timer) {
308 if (_clientCloseHandler !== null) _clientCloseHandler(); 314 if (_clientCloseHandler !== null) _clientCloseHandler();
309 } 315 }
310 new Timer(issueCloseCallback, 0, false); 316 new Timer(issueCloseCallback, 0, false);
311 } 317 }
312 318
313 InputStream _input; 319 InputStream _input;
314 String _encoding; 320 String _encoding;
315 _Decoder _decoder; 321 _StringDecoder _decoder;
316 String _buffer; // String can be buffered here if readLine is used.
317 int _bufferLineStart; // Current offset into _buffer if any.
318 bool _inputClosed = false; // Is the underlying input stream closed? 322 bool _inputClosed = false; // Is the underlying input stream closed?
319 bool _closed = false; // Is this stream closed. 323 bool _closed = false; // Is this stream closed.
320 bool _eof = false; // Has all data been read from the decoder? 324 bool _eof = false; // Has all data been read from the decoder?
321 var _clientDataHandler; 325 var _clientDataHandler;
326 var _clientLineHandler;
322 var _clientCloseHandler; 327 var _clientCloseHandler;
323 } 328 }
OLDNEW
« no previous file with comments | « runtime/bin/input_stream.dart ('k') | tests/standalone/src/StringStreamTest.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698