| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 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. |
| 4 |
| 5 part of dart.io; |
| 6 |
| 7 /** |
| 8 * Stream transformer that can decode a stream of bytes into a stream of |
| 9 * strings using [encoding]. |
| 10 * |
| 11 * Invalid or forbidden byte-sequences will not produce errors, but will instead |
| 12 * insert [replacementChar] in the decoded strings. |
| 13 */ |
| 14 class StringDecoder implements StreamTransformer<List<int>, String> { |
| 15 var _decoder; |
| 16 |
| 17 /** |
| 18 * Create a new [StringDecoder] with an optional [encoding] and |
| 19 * [replacementChar]. |
| 20 */ |
| 21 StringDecoder([Encoding encoding = Encoding.UTF_8, int replacementChar]) { |
| 22 switch (encoding) { |
| 23 case Encoding.UTF_8: |
| 24 if (replacementChar == null) { |
| 25 replacementChar = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT; |
| 26 } |
| 27 _decoder = new Utf8DecoderTransformer(replacementChar); |
| 28 break; |
| 29 case Encoding.ASCII: |
| 30 if (replacementChar == null) { |
| 31 replacementChar = '?'.charCodeAt(0); |
| 32 } else if (replacementChar > 127) { |
| 33 throw new ArgumentError("Invalid replacement character for ASCII"); |
| 34 } |
| 35 _decoder = new _AsciiDecoder(replacementChar); |
| 36 break; |
| 37 case Encoding.ISO_8859_1: |
| 38 if (replacementChar == null) { |
| 39 replacementChar = '?'.charCodeAt(0); |
| 40 } else if (replacementChar > 255) { |
| 41 throw new ArgumentError( |
| 42 "Invalid replacement character for ISO_8859_1"); |
| 43 } |
| 44 _decoder = new _Latin1Decoder(replacementChar); |
| 45 break; |
| 46 case Encoding.SYSTEM: |
| 47 if (Platform.operatingSystem == "windows") { |
| 48 _decoder = new _WindowsCodePageDecoder(); |
| 49 } else { |
| 50 if (replacementChar != null) { |
| 51 // TODO(ajohnsen): Handle replacement character. |
| 52 throw new UnsupportedError( |
| 53 "Replacement character is not supported for SYSTEM encoding"); |
| 54 } |
| 55 _decoder = new Utf8DecoderTransformer(); |
| 56 } |
| 57 break; |
| 58 default: |
| 59 throw new ArgumentError("Unsupported encoding '$encoding'"); |
| 60 } |
| 61 } |
| 62 |
| 63 Stream<String> bind(Stream<List<int>> stream) => _decoder.bind(stream); |
| 64 } |
| 65 |
| 66 |
| 67 /** |
| 68 * Stream transformer that can encode a stream of strings info a stream of |
| 69 * bytes using [encoding]. |
| 70 * |
| 71 * Strings that cannot be represented in the given encoding will result in an |
| 72 * error and a close event on the stream. |
| 73 */ |
| 74 class StringEncoder implements StreamTransformer<String, List<int>> { |
| 75 var _encoder; |
| 76 |
| 77 /** |
| 78 * Create a new [StringDecoder] with an optional [encoding] and |
| 79 * [replacementChar]. |
| 80 */ |
| 81 StringEncoder([Encoding encoding = Encoding.UTF_8]) { |
| 82 switch (encoding) { |
| 83 case Encoding.UTF_8: |
| 84 _encoder = new Utf8EncoderTransformer(); |
| 85 break; |
| 86 case Encoding.ASCII: |
| 87 _encoder = new _AsciiEncoder(); |
| 88 break; |
| 89 case Encoding.ISO_8859_1: |
| 90 _encoder = new _Latin1Encoder(); |
| 91 break; |
| 92 case Encoding.SYSTEM: |
| 93 if (Platform.operatingSystem == "windows") { |
| 94 _encoder = new _WindowsCodePageEncoder(); |
| 95 } else { |
| 96 _encoder = new Utf8EncoderTransformer(); |
| 97 } |
| 98 break; |
| 99 default: |
| 100 throw new ArgumentError("Unsupported encoding '$encoding'"); |
| 101 } |
| 102 } |
| 103 |
| 104 Stream<List<int>> bind(Stream<String> stream) => _encoder.bind(stream); |
| 105 } |
| 106 |
| 107 |
| 108 // Utility function to synchronously decode a list of bytes. |
| 109 String _decodeString(List<int> bytes, [Encoding encoding = Encoding.UTF_8]) { |
| 110 if (bytes.length == 0) return ""; |
| 111 var string; |
| 112 var controller = new StreamController(); |
| 113 controller.stream |
| 114 .transform(new StringDecoder(encoding)) |
| 115 .listen((data) => string = data); |
| 116 controller.add(bytes); |
| 117 controller.close(); |
| 118 assert(string != null); |
| 119 return string; |
| 120 } |
| 121 |
| 122 |
| 123 // Utility function to synchronously encode a String. |
| 124 // Will throw an exception if the encoding is invalid. |
| 125 List<int> _encodeString(String string, [Encoding encoding = Encoding.UTF_8]) { |
| 126 if (string.length == 0) return []; |
| 127 var bytes; |
| 128 var controller = new StreamController(); |
| 129 controller.stream |
| 130 .transform(new StringEncoder(encoding)) |
| 131 .listen((data) => bytes = data); |
| 132 controller.add(string); |
| 133 controller.close(); |
| 134 assert(bytes != null); |
| 135 return bytes; |
| 136 } |
| 137 |
| 138 |
| 139 class LineTransformer implements StreamTransformer<String, String> { |
| 140 const int _LF = 10; |
| 141 const int _CR = 13; |
| 142 |
| 143 final StringBuffer _buffer = new StringBuffer(); |
| 144 |
| 145 StreamSubscription<String> _subscription; |
| 146 StreamController<String> _controller; |
| 147 String _carry; |
| 148 |
| 149 Stream<String> bind(Stream<String> stream) { |
| 150 _controller = new StreamController<String>( |
| 151 onPauseStateChange: _pauseChanged, |
| 152 onSubscriptionStateChange: _subscriptionChanged); |
| 153 |
| 154 void handle(String data, bool isClosing) { |
| 155 if (_carry != null) { |
| 156 data = _carry.concat(data); |
| 157 _carry = null; |
| 158 } |
| 159 int startPos = 0; |
| 160 int pos = 0; |
| 161 while (pos < data.length) { |
| 162 int skip = 0; |
| 163 int char = data.charCodeAt(pos); |
| 164 if (char == _LF) { |
| 165 skip = 1; |
| 166 } else if (char == _CR) { |
| 167 skip = 1; |
| 168 if (pos + 1 < data.length) { |
| 169 if (data.charCodeAt(pos + 1) == _LF) { |
| 170 skip = 2; |
| 171 } |
| 172 } else if (!isClosing) { |
| 173 _carry = data.substring(startPos); |
| 174 return; |
| 175 } |
| 176 } |
| 177 if (skip > 0) { |
| 178 _buffer.add(data.substring(startPos, pos)); |
| 179 _controller.add(_buffer.toString()); |
| 180 _buffer.clear(); |
| 181 startPos = pos = pos + skip; |
| 182 } else { |
| 183 pos++; |
| 184 } |
| 185 } |
| 186 if (pos != startPos) { |
| 187 // Add remaining |
| 188 _buffer.add(data.substring(startPos, pos)); |
| 189 } |
| 190 if (isClosing && !_buffer.isEmpty) { |
| 191 _controller.add(_buffer.toString()); |
| 192 _buffer.clear(); |
| 193 } |
| 194 } |
| 195 |
| 196 _subscription = stream.listen( |
| 197 (data) => handle(data, false), |
| 198 onDone: () { |
| 199 // Handle remaining data (mainly _carry). |
| 200 handle("", true); |
| 201 _controller.close(); |
| 202 }, |
| 203 onError: _controller.signalError); |
| 204 return _controller.stream; |
| 205 } |
| 206 |
| 207 void _pauseChanged() { |
| 208 if (_controller.isPaused) { |
| 209 _subscription.pause(); |
| 210 } else { |
| 211 _subscription.resume(); |
| 212 } |
| 213 } |
| 214 |
| 215 void _subscriptionChanged() { |
| 216 if (!_controller.hasSubscribers) { |
| 217 _subscription.cancel(); |
| 218 } |
| 219 } |
| 220 } |
| 221 |
| 222 |
| 223 class _SingleByteDecoder implements StreamTransformer<List<int>, String> { |
| 224 StreamSubscription<List<int>> _subscription; |
| 225 StreamController<String> _controller; |
| 226 final int _replacementChar; |
| 227 |
| 228 _SingleByteDecoder(this._replacementChar); |
| 229 |
| 230 Stream<String> bind(Stream<List<int>> stream) { |
| 231 _controller = new StreamController<String>( |
| 232 onPauseStateChange: _pauseChanged, |
| 233 onSubscriptionStateChange: _subscriptionChanged); |
| 234 _subscription = stream.listen( |
| 235 (data) { |
| 236 var buffer = new List<int>.fixedLength(data.length); |
| 237 for (int i = 0; i < data.length; i++) { |
| 238 int char = _decodeByte(data[i]); |
| 239 if (char < 0) char = _replacementChar; |
| 240 buffer[i] = char; |
| 241 } |
| 242 _controller.add(new String.fromCharCodes(buffer)); |
| 243 }, |
| 244 onDone: _controller.close, |
| 245 onError: _controller.signalError); |
| 246 return _controller.stream; |
| 247 } |
| 248 |
| 249 int _decodeByte(int byte); |
| 250 |
| 251 void _pauseChanged() { |
| 252 if (_controller.isPaused) { |
| 253 _subscription.pause(); |
| 254 } else { |
| 255 _subscription.resume(); |
| 256 } |
| 257 } |
| 258 |
| 259 void _subscriptionChanged() { |
| 260 if (!_controller.hasSubscribers) { |
| 261 _subscription.cancel(); |
| 262 } |
| 263 } |
| 264 } |
| 265 |
| 266 |
| 267 // Utility class for decoding ascii data delivered as a stream of |
| 268 // bytes. |
| 269 class _AsciiDecoder extends _SingleByteDecoder { |
| 270 _AsciiDecoder(int replacementChar) : super(replacementChar); |
| 271 |
| 272 int _decodeByte(int byte) => ((byte & 0x7f) == byte) ? byte : -1; |
| 273 } |
| 274 |
| 275 |
| 276 // Utility class for decoding Latin-1 data delivered as a stream of |
| 277 // bytes. |
| 278 class _Latin1Decoder extends _SingleByteDecoder { |
| 279 _Latin1Decoder(int replacementChar) : super(replacementChar); |
| 280 |
| 281 int _decodeByte(int byte) => ((byte & 0xFF) == byte) ? byte : -1; |
| 282 } |
| 283 |
| 284 |
| 285 class _SingleByteEncoder implements StreamTransformer<String, List<int>> { |
| 286 StreamSubscription<String> _subscription; |
| 287 StreamController<List<int>> _controller; |
| 288 |
| 289 Stream<List<int>> bind(Stream<String> stream) { |
| 290 _controller = new StreamController<List<int>>( |
| 291 onPauseStateChange: _pauseChanged, |
| 292 onSubscriptionStateChange: _subscriptionChanged); |
| 293 _subscription = stream.listen( |
| 294 (string) { |
| 295 var bytes = _encode(string); |
| 296 if (bytes == null) { |
| 297 _controller.signalError(new FormatException( |
| 298 "Invalid character for encoding")); |
| 299 _controller.close(); |
| 300 _subscription.cancel(); |
| 301 } else { |
| 302 _controller.add(bytes); |
| 303 } |
| 304 }, |
| 305 onDone: _controller.close, |
| 306 onError: _controller.signalError); |
| 307 return _controller.stream; |
| 308 } |
| 309 |
| 310 List<int> _encode(String string); |
| 311 |
| 312 void _pauseChanged() { |
| 313 if (_controller.isPaused) { |
| 314 _subscription.pause(); |
| 315 } else { |
| 316 _subscription.resume(); |
| 317 } |
| 318 } |
| 319 |
| 320 void _subscriptionChanged() { |
| 321 if (!_controller.hasSubscribers) { |
| 322 _subscription.cancel(); |
| 323 } |
| 324 } |
| 325 } |
| 326 |
| 327 |
| 328 // Utility class for encoding a string into an ASCII byte stream. |
| 329 class _AsciiEncoder extends _SingleByteEncoder { |
| 330 List<int> _encode(String string) { |
| 331 var bytes = string.charCodes; |
| 332 for (var byte in bytes) { |
| 333 if (byte > 127) return null; |
| 334 } |
| 335 return bytes; |
| 336 } |
| 337 } |
| 338 |
| 339 |
| 340 // Utility class for encoding a string into a Latin1 byte stream. |
| 341 class _Latin1Encoder extends _SingleByteEncoder { |
| 342 List<int> _encode(String string) { |
| 343 var bytes = string.charCodes; |
| 344 for (var byte in bytes) { |
| 345 if (byte > 255) return null; |
| 346 } |
| 347 return bytes; |
| 348 } |
| 349 } |
| 350 |
| 351 |
| 352 // Utility class for encoding a string into a current windows |
| 353 // code page byte list. |
| 354 // Implemented on top of a _SingleByteEncoder, even though it's not really a |
| 355 // single byte encoder, to avoid copying boilerplate. |
| 356 class _WindowsCodePageEncoder extends _SingleByteEncoder { |
| 357 List<int> _encode(String string) => _encodeString(string); |
| 358 |
| 359 external static List<int> _encodeString(String string); |
| 360 } |
| 361 |
| 362 |
| 363 // Utility class for decoding Windows current code page data delivered |
| 364 // as a stream of bytes. |
| 365 class _WindowsCodePageDecoder implements StreamTransformer<List<int>, String> { |
| 366 StreamSubscription<List<int>> _subscription; |
| 367 StreamController<String> _controller; |
| 368 |
| 369 Stream<String> bind(Stream<List<int>> stream) { |
| 370 _controller = new StreamController<String>( |
| 371 onPauseStateChange: _pauseChanged, |
| 372 onSubscriptionStateChange: _subscriptionChanged); |
| 373 _subscription = stream.listen( |
| 374 (data) { |
| 375 _controller.add(_decodeBytes(data)); |
| 376 }, |
| 377 onDone: _controller.close, |
| 378 onError: _controller.signalError); |
| 379 return _controller.stream; |
| 380 } |
| 381 |
| 382 external static String _decodeBytes(List<int> bytes); |
| 383 |
| 384 void _pauseChanged() { |
| 385 if (_controller.isPaused) { |
| 386 _subscription.pause(); |
| 387 } else { |
| 388 _subscription.resume(); |
| 389 } |
| 390 } |
| 391 |
| 392 void _subscriptionChanged() { |
| 393 if (!_controller.hasSubscribers) { |
| 394 _subscription.cancel(); |
| 395 } |
| 396 } |
| 397 } |
| OLD | NEW |