| OLD | NEW |
| 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 Loading... |
| 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 } |
| OLD | NEW |