Index: sdk/lib/io/string_stream.dart |
diff --git a/sdk/lib/io/string_stream.dart b/sdk/lib/io/string_stream.dart |
index cd38e2cb3c14c15102c070c5ea9a7c002a8b23d7..52f2d64230c43b2dd91c8cc5c97e8deef1267b72 100644 |
--- a/sdk/lib/io/string_stream.dart |
+++ b/sdk/lib/io/string_stream.dart |
@@ -3,592 +3,3 @@ |
// BSD-style license that can be found in the LICENSE file. |
part of dart.io; |
- |
-// Interface for decoders decoding binary data into string data. The |
-// decoder keeps track of line breaks during decoding. |
-abstract class _StringDecoder { |
- // Add more binary data to be decoded. The ownership of the buffer |
- // is transfered to the decoder and the caller most not modify it any more. |
- int write(List<int> buffer); |
- |
- void done(); |
- |
- // Returns whether any decoded data is available. |
- bool get isEmpty; |
- |
- // Returns the number of available decoded characters. |
- int available(); |
- |
- // Get the number of line breaks present in the current decoded |
- // data. |
- int get lineBreaks; |
- |
- // Get up to [len] characters of string data decoded since the last |
- // call to [decode] or [decodeLine]. Returns null if no decoded data |
- // is available. If [len] is not specified all decoded characters |
- // are returned. |
- String decoded([int len]); |
- |
- // Get the string data decoded since the last call to [decode] or |
- // [decodeLine] up to the next line break present. Returns null if |
- // no line break is present. The line break character sequence is |
- // discarded. |
- String get decodedLine; |
- |
- // Set the handler that will be called if an error ocurs while decoding. |
- void set onError(Function callback); |
-} |
- |
- |
-class _StringDecoders { |
- static _StringDecoder decoder(Encoding encoding) { |
- if (encoding == Encoding.UTF_8) { |
- return new _UTF8Decoder(); |
- } else if (encoding == Encoding.ISO_8859_1) { |
- return new _Latin1Decoder(); |
- } else if (encoding == Encoding.ASCII) { |
- return new _AsciiDecoder(); |
- } else if (encoding == Encoding.SYSTEM) { |
- if (Platform.operatingSystem == 'windows') { |
- return new _WindowsCodePageDecoder(); |
- } |
- return new _UTF8Decoder(); |
- } else { |
- if (encoding is Encoding) { |
- throw new StreamException("Unsupported encoding ${encoding.name}"); |
- } else { |
- throw new StreamException("Unsupported encoding ${encoding}"); |
- } |
- } |
- } |
-} |
- |
- |
-class DecoderException implements Exception { |
- const DecoderException([String this.message]); |
- String toString() => "DecoderException: $message"; |
- final String message; |
-} |
- |
- |
-// Utility class for decoding UTF-8 from data delivered as a stream of |
-// bytes. |
-abstract class _StringDecoderBase implements _StringDecoder { |
- _StringDecoderBase() |
- : _bufferList = new _BufferList(), |
- _result = new List<int>(), |
- _lineBreakEnds = new Queue<int>(); |
- |
- int write(List<int> buffer) { |
- _bufferList.add(buffer); |
- // Decode as many bytes into characters as possible. |
- while (_bufferList.length > 0) { |
- if (!_processNext()) { |
- break; |
- } |
- } |
- return buffer.length; |
- } |
- |
- void done() { } |
- |
- bool get isEmpty => _result.isEmpty; |
- |
- int get lineBreaks => _lineBreaks; |
- |
- String decoded([int length]) { |
- if (isEmpty) return null; |
- |
- List<int> result; |
- if (length != null && length < available()) { |
- result = _result.getRange(_resultOffset, length); |
- } else if (_resultOffset == 0) { |
- result = _result; |
- } else { |
- result = _result.getRange(_resultOffset, _result.length - _resultOffset); |
- } |
- |
- _resultOffset += result.length; |
- while (!_lineBreakEnds.isEmpty && |
- _lineBreakEnds.first < _charOffset + _resultOffset) { |
- _lineBreakEnds.removeFirst(); |
- _lineBreaks--; |
- } |
- if (_result.length == _resultOffset) _resetResult(); |
- return new String.fromCharCodes(result); |
- } |
- |
- String get decodedLine { |
- if (_lineBreakEnds.isEmpty) return null; |
- int lineEnd = _lineBreakEnds.removeFirst(); |
- int terminationSequenceLength = 1; |
- if (_result[lineEnd - _charOffset] == LF && |
- lineEnd > _charOffset && |
- _resultOffset < lineEnd && |
- _result[lineEnd - _charOffset - 1] == CR) { |
- terminationSequenceLength = 2; |
- } |
- var lineLength = |
- lineEnd - _charOffset - _resultOffset - terminationSequenceLength + 1; |
- String result = |
- new String.fromCharCodes(_result.getRange(_resultOffset, lineLength)); |
- _lineBreaks--; |
- _resultOffset += (lineLength + terminationSequenceLength); |
- if (_result.length == _resultOffset) _resetResult(); |
- return result; |
- } |
- |
- // Add another decoded character. |
- void addChar(int charCode) { |
- _result.add(charCode); |
- _charCount++; |
- // Check for line ends (\r, \n and \r\n). |
- if (charCode == LF) { |
- _recordLineBreakEnd(_charCount - 1); |
- } else if (_lastCharCode == CR) { |
- _recordLineBreakEnd(_charCount - 2); |
- } |
- _lastCharCode = charCode; |
- } |
- |
- int available() => _result.length - _resultOffset; |
- |
- void _recordLineBreakEnd(int charPos) { |
- _lineBreakEnds.add(charPos); |
- _lineBreaks++; |
- } |
- |
- void _resetResult() { |
- _charOffset += _result.length; |
- _result = new List<int>(); |
- _resultOffset = 0; |
- } |
- |
- bool _processNext(); |
- |
- _BufferList _bufferList; |
- int _resultOffset = 0; |
- List<int> _result; |
- int _lineBreaks = 0; // Number of line breaks in the current list. |
- // The positions of the line breaks are tracked in terms of absolute |
- // character positions from the begining of the decoded data. |
- Queue<int> _lineBreakEnds; // Character position of known line breaks. |
- int _charOffset = 0; // Character number of the first character in the list. |
- int _charCount = 0; // Total number of characters decoded. |
- int _lastCharCode = -1; |
- Function onError; |
- |
- final int LF = 10; |
- final int CR = 13; |
-} |
- |
- |
-// Utility class for decoding UTF-8 from data delivered as a stream of |
-// bytes. |
-class _UTF8Decoder extends _StringDecoderBase { |
- static const kMaxCodePoint = 0x10FFFF; |
- static const kReplacementCodePoint = 0x3f; |
- |
- void done() { |
- if (!_bufferList.isEmpty) { |
- _reportError(new DecoderException("Illegal UTF-8")); |
- } |
- } |
- |
- bool _reportError(error) { |
- if (onError != null) { |
- onError(error); |
- return false; |
- } else { |
- throw error; |
- } |
- } |
- |
- // Process the next UTF-8 encoded character. |
- bool _processNext() { |
- // Peek the next byte to calculate the number of bytes required for |
- // the next character. |
- int value = _bufferList.peek() & 0xFF; |
- if ((value & 0x80) == 0x80) { |
- int additionalBytes; |
- if ((value & 0xe0) == 0xc0) { // 110xxxxx |
- value = value & 0x1F; |
- additionalBytes = 1; |
- } else if ((value & 0xf0) == 0xe0) { // 1110xxxx |
- value = value & 0x0F; |
- additionalBytes = 2; |
- } else if ((value & 0xf8) == 0xf0) { // 11110xxx |
- value = value & 0x07; |
- additionalBytes = 3; |
- } else if ((value & 0xfc) == 0xf8) { // 111110xx |
- value = value & 0x03; |
- additionalBytes = 4; |
- } else if ((value & 0xfe) == 0xfc) { // 1111110x |
- value = value & 0x01; |
- additionalBytes = 5; |
- } else { |
- return _reportError(new DecoderException("Illegal UTF-8")); |
- } |
- // Check if there are enough bytes to decode the character. Otherwise |
- // return false. |
- if (_bufferList.length < additionalBytes + 1) { |
- return false; |
- } |
- // Remove the value peeked from the buffer list. |
- _bufferList.next(); |
- for (int i = 0; i < additionalBytes; i++) { |
- int byte = _bufferList.next(); |
- if ((byte & 0xc0) != 0x80) { |
- return _reportError(new DecoderException("Illegal UTF-8")); |
- } |
- value = value << 6 | (byte & 0x3F); |
- } |
- } else { |
- // Remove the value peeked from the buffer list. |
- _bufferList.next(); |
- } |
- if (value > kMaxCodePoint) { |
- addChar(kReplacementCodePoint); |
- } else { |
- addChar(value); |
- } |
- return true; |
- } |
-} |
- |
- |
-// Utility class for decoding ascii data delivered as a stream of |
-// bytes. |
-class _AsciiDecoder extends _StringDecoderBase { |
- // Process the next ascii encoded character. |
- bool _processNext() { |
- while (_bufferList.length > 0) { |
- int byte = _bufferList.next(); |
- if (byte > 127) { |
- var error = new DecoderException("Illegal ASCII character $byte"); |
- if (onError != null) { |
- onError(error); |
- return false; |
- } else { |
- throw error; |
- } |
- } |
- addChar(byte); |
- } |
- return true; |
- } |
-} |
- |
- |
-// Utility class for decoding Latin-1 data delivered as a stream of |
-// bytes. |
-class _Latin1Decoder extends _StringDecoderBase { |
- // Process the next Latin-1 encoded character. |
- bool _processNext() { |
- while (_bufferList.length > 0) { |
- int byte = _bufferList.next(); |
- addChar(byte); |
- } |
- return true; |
- } |
-} |
- |
- |
-// Utility class for decoding Windows current code page data delivered |
-// as a stream of bytes. |
-class _WindowsCodePageDecoder extends _StringDecoderBase { |
- // Process the next chunk of data. |
- bool _processNext() { |
- List<int> bytes = _bufferList.readBytes(_bufferList.length); |
- for (var charCode in _decodeBytes(bytes).charCodes) { |
- addChar(charCode); |
- } |
- return true; |
- } |
- |
- external static String _decodeBytes(List<int> bytes); |
-} |
- |
- |
-// Interface for encoders encoding string data into binary data. |
-abstract class _StringEncoder { |
- List<int> encodeString(String string); |
-} |
- |
- |
-// Utility class for encoding a string into UTF-8 byte stream. |
-class _UTF8Encoder implements _StringEncoder { |
- List<int> encodeString(String string) { |
- int size = _encodingSize(string); |
- List<int> result = new Uint8List(size); |
- _encodeString(string, result); |
- return result; |
- } |
- |
- static int _encodingSize(String string) => _encodeString(string, null); |
- |
- static int _encodeString(String string, List<int> buffer) { |
- List<int> utf8CodeUnits = encodeUtf8(string); |
- if (buffer != null) { |
- for (int i = 0; i < utf8CodeUnits.length; i++) { |
- buffer[i] = utf8CodeUnits[i]; |
- } |
- } |
- return utf8CodeUnits.length; |
- } |
-} |
- |
- |
-// Utility class for encoding a string into a Latin1 byte stream. |
-class _Latin1Encoder implements _StringEncoder { |
- List<int> encodeString(String string) { |
- List<int> result = new Uint8List(string.length); |
- for (int i = 0; i < string.length; i++) { |
- int charCode = string.charCodeAt(i); |
- if (charCode > 255) { |
- throw new EncoderException( |
- "No ISO_8859_1 encoding for code point $charCode"); |
- } |
- result[i] = charCode; |
- } |
- return result; |
- } |
-} |
- |
- |
-// Utility class for encoding a string into an ASCII byte stream. |
-class _AsciiEncoder implements _StringEncoder { |
- List<int> encodeString(String string) { |
- List<int> result = new Uint8List(string.length); |
- for (int i = 0; i < string.length; i++) { |
- int charCode = string.charCodeAt(i); |
- if (charCode > 127) { |
- throw new EncoderException( |
- "No ASCII encoding for code point $charCode"); |
- } |
- result[i] = charCode; |
- } |
- return result; |
- } |
-} |
- |
- |
-// Utility class for encoding a string into a current windows |
-// code page byte list. |
-class _WindowsCodePageEncoder implements _StringEncoder { |
- List<int> encodeString(String string) { |
- return _encodeString(string); |
- } |
- |
- external static List<int> _encodeString(String string); |
-} |
- |
- |
-class _StringEncoders { |
- static _StringEncoder encoder(Encoding encoding) { |
- if (encoding == Encoding.UTF_8) { |
- return new _UTF8Encoder(); |
- } else if (encoding == Encoding.ISO_8859_1) { |
- return new _Latin1Encoder(); |
- } else if (encoding == Encoding.ASCII) { |
- return new _AsciiEncoder(); |
- } else if (encoding == Encoding.SYSTEM) { |
- if (Platform.operatingSystem == 'windows') { |
- return new _WindowsCodePageEncoder(); |
- } |
- return new _UTF8Encoder(); |
- } else { |
- throw new StreamException("Unsupported encoding ${encoding.name}"); |
- } |
- } |
-} |
- |
- |
-class EncoderException implements Exception { |
- const EncoderException([String this.message]); |
- String toString() => "EncoderException: $message"; |
- final String message; |
-} |
- |
- |
-class _StringInputStream implements StringInputStream { |
- _StringInputStream(InputStream this._input, Encoding this._encoding) { |
- _decoder = _StringDecoders.decoder(encoding); |
- _input.onData = _onData; |
- _input.onClosed = _onClosed; |
- } |
- |
- String read([int len]) { |
- String result = _decoder.decoded(len); |
- _checkInstallDataHandler(); |
- return result; |
- } |
- |
- String readLine() { |
- String decodedLine = _decoder.decodedLine; |
- if (decodedLine == null) { |
- if (_inputClosed) { |
- // Last line might not have a line separator. |
- decodedLine = _decoder.decoded(); |
- if (decodedLine != null && |
- decodedLine[decodedLine.length - 1] == '\r') { |
- decodedLine = decodedLine.substring(0, decodedLine.length - 1); |
- } |
- } |
- } |
- _checkInstallDataHandler(); |
- return decodedLine; |
- } |
- |
- int available() => _decoder.available(); |
- |
- Encoding get encoding => _encoding; |
- |
- bool get closed => _inputClosed && _decoder.isEmpty; |
- |
- void set onData(void callback()) { |
- _clientDataHandler = callback; |
- _clientLineHandler = null; |
- _checkInstallDataHandler(); |
- _checkScheduleCallback(); |
- } |
- |
- void set onLine(void callback()) { |
- _clientLineHandler = callback; |
- _clientDataHandler = null; |
- _checkInstallDataHandler(); |
- _checkScheduleCallback(); |
- } |
- |
- void set onClosed(void callback()) { |
- _clientCloseHandler = callback; |
- } |
- |
- void set onError(void callback(e)) { |
- _input.onError = callback; |
- _decoder.onError = (e) { |
- _clientCloseHandler = null; |
- _input.close(); |
- callback(e); |
- }; |
- } |
- |
- void _onData() { |
- _readData(); |
- if (!_decoder.isEmpty && _clientDataHandler != null) { |
- _clientDataHandler(); |
- } |
- if (_decoder.lineBreaks > 0 && _clientLineHandler != null) { |
- _clientLineHandler(); |
- } |
- _checkScheduleCallback(); |
- _checkInstallDataHandler(); |
- } |
- |
- void _onClosed() { |
- _inputClosed = true; |
- _decoder.done(); |
- if (_decoder.isEmpty && _clientCloseHandler != null) { |
- _clientCloseHandler(); |
- } else { |
- _checkScheduleCallback(); |
- } |
- } |
- |
- void _readData() { |
- List<int> data = _input.read(); |
- if (data != null) { |
- _decoder.write(data); |
- } |
- } |
- |
- void _checkInstallDataHandler() { |
- if (_inputClosed || |
- (_clientDataHandler == null && _clientLineHandler == null)) { |
- _input.onData = null; |
- } else if (_clientDataHandler != null) { |
- if (_decoder.isEmpty) { |
- _input.onData = _onData; |
- } else { |
- _input.onData = null; |
- } |
- } else { |
- assert(_clientLineHandler != null); |
- if (_decoder.lineBreaks == 0) { |
- _input.onData = _onData; |
- } else { |
- _input.onData = null; |
- } |
- } |
- } |
- |
- // TODO(sgjesse): Find a better way of scheduling callbacks from |
- // the event loop. |
- void _checkScheduleCallback() { |
- void issueDataCallback() { |
- _scheduledDataCallback = null; |
- if (_clientDataHandler != null) { |
- _clientDataHandler(); |
- _checkScheduleCallback(); |
- } |
- } |
- |
- void issueLineCallback() { |
- _scheduledLineCallback = null; |
- if (_clientLineHandler != null) { |
- _clientLineHandler(); |
- _checkScheduleCallback(); |
- } |
- } |
- |
- void issueCloseCallback() { |
- _scheduledCloseCallback = null; |
- if (!_closed) { |
- if (_clientCloseHandler != null) _clientCloseHandler(); |
- _closed = true; |
- } |
- } |
- |
- if (!_closed) { |
- // Schedule data callback if string data available. |
- if (_clientDataHandler != null && |
- !_decoder.isEmpty && |
- _scheduledDataCallback == null) { |
- if (_scheduledLineCallback != null) { |
- _scheduledLineCallback.cancel(); |
- } |
- _scheduledDataCallback = Timer.run(issueDataCallback); |
- } |
- |
- // Schedule line callback if a line is available. |
- if (_clientLineHandler != null && |
- (_decoder.lineBreaks > 0 || (!_decoder.isEmpty && _inputClosed)) && |
- _scheduledLineCallback == null) { |
- if (_scheduledDataCallback != null) { |
- _scheduledDataCallback.cancel(); |
- } |
- _scheduledLineCallback = Timer.run(issueLineCallback); |
- } |
- |
- // Schedule close callback if no more data and input is closed. |
- if (_decoder.isEmpty && |
- _inputClosed && |
- _scheduledCloseCallback == null) { |
- _scheduledCloseCallback = Timer.run(issueCloseCallback); |
- } |
- } |
- } |
- |
- InputStream _input; |
- Encoding _encoding; |
- _StringDecoder _decoder; |
- bool _inputClosed = false; // Is the underlying input stream closed? |
- bool _closed = false; // Is this stream closed. |
- bool _eof = false; // Has all data been read from the decoder? |
- Timer _scheduledDataCallback; |
- Timer _scheduledLineCallback; |
- Timer _scheduledCloseCallback; |
- Function _clientDataHandler; |
- Function _clientLineHandler; |
- Function _clientCloseHandler; |
-} |