Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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 part of dart.convert; | 5 part of dart.convert; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * An instance of the default implementation of the [Latin1Codec]. | 8 * An instance of the default implementation of the [AsciiCodec]. |
| 9 * | 9 * |
| 10 * This instance provides a convenient access to the most common ISO Latin 1 | 10 * This instance provides a convenient access to the most common ISO Latin 1 |
|
floitsch
2013/08/22 12:34:31
not ISO Latin 1.
Lasse Reichstein Nielsen
2013/08/22 13:04:56
Done.
| |
| 11 * use cases. | 11 * use cases. |
| 12 * | 12 * |
| 13 * Examples: | 13 * Examples: |
| 14 * | 14 * |
| 15 * var encoded = LATIN1.encode("blåbærgrød"); | 15 * var encoded = LATIN1.encode("This is ASCII!"); |
|
floitsch
2013/08/22 12:34:31
ASCII
Lasse Reichstein Nielsen
2013/08/22 13:04:56
Done.
| |
| 16 * var decoded = LATIN1.decode([0x62, 0x6c, 0xe5, 0x62, 0xe6, | 16 * var decoded = LATIN1.decode([0x62, 0x6c, 0x61, 0x61, 0x62, 0x61, 0x65, |
|
floitsch
2013/08/22 12:34:31
maybe still pick a better ASCII example.
Lasse Reichstein Nielsen
2013/08/22 13:04:56
Pfft!
Done.
| |
| 17 * 0x72, 0x67, 0x72, 0xf8, 0x64]); | 17 * 0x72, 0x67, 0x72, 0x6f, 0x65, 0x64]); |
| 18 */ | 18 */ |
| 19 const LATIN1 = const Latin1Codec(); | 19 const ASCII = const AsciiCodec(); |
| 20 | |
| 21 const int _ASCII_MASK = 0x7F; | |
| 20 | 22 |
| 21 /** | 23 /** |
| 22 * A [LatinCodec] encodes strings to ISO Latin-1 (aka ISO-8859-1) bytes | 24 * A [AsciiCodec] encodes strings to ASCII bytes |
|
floitsch
2013/08/22 12:34:31
An
Lasse Reichstein Nielsen
2013/08/22 13:04:56
Done.
| |
| 23 * and decodes Latin-1 bytes to strings. | 25 * and decodes ASCII bytes to strings. |
| 24 */ | 26 */ |
| 25 class Latin1Codec extends _Encoding { | 27 class AsciiCodec extends _Encoding { |
| 26 final bool _allowInvalid; | 28 final bool _allowInvalid; |
| 27 /** | 29 /** |
| 28 * Instantiates a new [Latin1Codec]. | 30 * Instantiates a new [AsciiCodec]. |
| 29 * | 31 * |
| 30 * If [allowInvalid] is true, the [decode] method and the converter | 32 * If [allowInvalid] is true, the [decode] method and the converter |
| 31 * returned by [decoder] will default to allowing invalid values. Invalid | 33 * returned by [decoder] will default to allowing invalid values. Invalid |
| 32 * values are decoded into the Unicode Replacement character (U+FFFD). | 34 * values are decoded into the Unicode Replacement character (U+FFFD). |
| 33 * Calls to the [decode] method can override this default. | 35 * Calls to the [decode] method can override this default. |
|
floitsch
2013/08/22 12:34:31
It is not clear that you are talking about the "al
Lasse Reichstein Nielsen
2013/08/22 13:04:56
Done.
| |
| 34 * | 36 * |
| 35 * Encoders will not accept invalid (non Latin-1) characters. | 37 * Encoders will not accept invalid (non Latin-1) characters. |
| 36 */ | 38 */ |
| 37 const Latin1Codec({bool allowInvalid: false}) : _allowInvalid = allowInvalid; | 39 const AsciiCodec({bool allowInvalid: false}) : _allowInvalid = allowInvalid; |
| 38 | 40 |
| 39 /** | 41 /** |
| 40 * Decodes the Latin-1 [bytes] (a list of unsigned 8-bit integers) to the | 42 * Decodes the ASCII [bytes] (a list of unsigned 7-bit integers) to the |
| 41 * corresponding string. | 43 * corresponding string. |
| 42 * | 44 * |
| 43 * If [bytes] contains values that are not in the range 0 .. 255, the decoder | 45 * If [bytes] contains values that are not in the range 0 .. 127, the decoder |
| 44 * will eventually throw a [FormatException]. | 46 * will eventually throw a [FormatException]. |
| 45 * | 47 * |
| 46 * If [allowInvalid] is not provided, it defaults to the value used to create | 48 * If [allowInvalid] is not provided, it defaults to the value used to create |
| 47 * this [Latin1Codec]. | 49 * this [AsciiCodec]. |
| 48 */ | 50 */ |
| 49 String decode(List<int> bytes, { bool allowInvalid }) { | 51 String decode(List<int> bytes, { bool allowInvalid }) { |
| 50 if (allowInvalid == null) allowInvalid = _allowInvalid; | 52 if (allowInvalid == null) allowInvalid = _allowInvalid; |
| 51 if (allowInvalid) { | 53 if (allowInvalid) { |
| 52 return const Latin1Decoder(allowInvalid: true).convert(bytes); | 54 return const AsciiDecoder(allowInvalid: true).convert(bytes); |
| 53 } else { | 55 } else { |
| 54 return const Latin1Decoder(allowInvalid: false).convert(bytes); | 56 return const AsciiDecoder(allowInvalid: false).convert(bytes); |
| 55 } | 57 } |
| 56 } | 58 } |
| 57 | 59 |
| 58 Converter<String, List<int>> get encoder => const Latin1Encoder(); | 60 Converter<String, List<int>> get encoder => const AsciiEncoder(); |
| 59 | 61 |
| 60 Converter<List<int>, String> get decoder => | 62 Converter<List<int>, String> get decoder => |
| 61 _allowInvalid ? const Latin1Decoder(allowInvalid: true) | 63 _allowInvalid ? const AsciiDecoder(allowInvalid: true) |
| 62 : const Latin1Decoder(allowInvalid: false); | 64 : const AsciiDecoder(allowInvalid: false); |
| 63 } | 65 } |
| 64 | 66 |
| 65 /** | 67 // Superclass for [AsciiEncoder] and [Latin1nocoder]. |
|
floitsch
2013/08/22 12:34:31
Latin1Encoder
Lasse Reichstein Nielsen
2013/08/22 13:04:56
Done.
| |
| 66 * This class converts strings of only ISO Latin-1 characters to bytes. | 68 // Generalizes common operations that only differ by a mask; |
| 67 */ | 69 class _UnicodeSubsetEncoder extends Converter<String, List<int>> { |
| 68 class Latin1Encoder extends Converter<String, List<int>> { | 70 final int _subsetMask; |
| 69 const Latin1Encoder(); | |
| 70 | 71 |
| 71 /** | 72 const _UnicodeSubsetEncoder(this._subsetMask); |
| 72 * Converts [string] to its Latin-1 bytes (a list of | 73 |
| 73 * unsigned 8-bit integers). | |
| 74 */ | |
| 75 List<int> convert(String string) { | 74 List<int> convert(String string) { |
| 76 // TODO(11971): Use Uint8List when possible. | 75 // TODO(11971): Use Uint8List when possible. |
| 77 List result = new List<int>(string.length); | 76 List result = new List<int>(string.length); |
| 78 for (int i = 0; i < string.length; i++) { | 77 for (int i = 0; i < string.length; i++) { |
| 79 var codeUnit = string.codeUnitAt(i); | 78 var codeUnit = string.codeUnitAt(i); |
| 80 if ((codeUnit & ~0xFF) != 0) { | 79 if ((codeUnit & ~_subsetMask) != 0) { |
| 81 throw new ArgumentError("String contains non-Latin-1 characters."); | 80 throw new ArgumentError("String contains invalid characters."); |
| 82 } | 81 } |
| 83 result[i] = codeUnit; | 82 result[i] = codeUnit; |
| 84 } | 83 } |
| 85 return result; | 84 return result; |
| 86 } | 85 } |
| 87 | 86 |
| 88 /** | 87 /** |
| 89 * Starts a chunked conversion. | 88 * Starts a chunked conversion. |
| 90 * | 89 * |
| 91 * The converter works more efficiently if the given [sink] is a | 90 * The converter works more efficiently if the given [sink] is a |
| 92 * [ByteConversionSink]. | 91 * [ByteConversionSink]. |
| 93 */ | 92 */ |
| 94 StringConversionSink startChunkedConversion( | 93 StringConversionSink startChunkedConversion( |
| 95 ChunkedConversionSink<List<int>> sink) { | 94 ChunkedConversionSink<List<int>> sink) { |
| 96 if (sink is! ByteConversionSink) { | 95 if (sink is! ByteConversionSink) { |
| 97 sink = new ByteConversionSink.from(sink); | 96 sink = new ByteConversionSink.from(sink); |
| 98 } | 97 } |
| 99 return new _Latin1EncoderSink(sink); | 98 return new _UnicodeSubsetEncoderSink(_subsetMask, sink); |
| 100 } | 99 } |
| 101 | 100 |
| 102 // Override the base-class' bind, to provide a better type. | 101 // Override the base-class' bind, to provide a better type. |
| 103 Stream<List<int>> bind(Stream<String> stream) => super.bind(stream); | 102 Stream<List<int>> bind(Stream<String> stream) => super.bind(stream); |
| 104 } | 103 } |
| 105 | 104 |
| 106 /** | 105 /** |
| 106 * This class converts strings of only ISO Latin-1 characters to bytes. | |
|
floitsch
2013/08/22 12:34:31
Ascii
Lasse Reichstein Nielsen
2013/08/22 13:04:56
Done.
| |
| 107 */ | |
| 108 class AsciiEncoder extends _UnicodeSubsetEncoder { | |
| 109 const AsciiEncoder() : super(_ASCII_MASK); | |
| 110 } | |
| 111 | |
| 112 /** | |
| 107 * This class encodes chunked strings to bytes (unsigned 8-bit | 113 * This class encodes chunked strings to bytes (unsigned 8-bit |
| 108 * integers). | 114 * integers). |
| 109 */ | 115 */ |
| 110 class _Latin1EncoderSink extends StringConversionSinkBase { | 116 class _UnicodeSubsetEncoderSink extends StringConversionSinkBase { |
| 111 static const _DEFAULT_BYTE_BUFFER_SIZE = 1024; | |
| 112 final ByteConversionSink _sink; | 117 final ByteConversionSink _sink; |
| 118 final int _subsetMask; | |
| 113 | 119 |
| 114 // TODO(11971): Use Uint8List when available. | 120 _UnicodeSubsetEncoderSink(this._subsetMask, this._sink); |
| 115 List<int> _buffer = new List<int>(_DEFAULT_BYTE_BUFFER_SIZE); | |
| 116 int _bufferIndex = 0; | |
| 117 | |
| 118 _Latin1EncoderSink(this._sink); | |
| 119 | 121 |
| 120 void close() { | 122 void close() { |
| 121 if (_bufferIndex > 0) { | 123 _sink.close(); |
| 122 _sink.addSlice(_buffer, 0, _bufferIndex, true); | |
| 123 } else { | |
| 124 _sink.close(); | |
| 125 } | |
| 126 } | 124 } |
| 127 | 125 |
| 128 void addSlice(String source, int start, int end, bool isLast) { | 126 void addSlice(String source, int start, int end, bool isLast) { |
| 129 if (start < 0 || start > source.length) { | 127 if (start < 0 || start > source.length) { |
| 130 throw new RangeError.range(start, 0, source.length); | 128 throw new RangeError.range(start, 0, source.length); |
| 131 } | 129 } |
| 132 if (end < start || end > source.length) { | 130 if (end < start || end > source.length) { |
| 133 throw new RangeError.range(end, start, source.length); | 131 throw new RangeError.range(end, start, source.length); |
| 134 } | 132 } |
| 135 for (int i = start; i < end; i++) { | 133 for (int i = start; i < end; i++) { |
| 136 int codeUnit = source.codeUnitAt(i); | 134 int codeUnit = source.codeUnitAt(i); |
| 137 if ((codeUnit & ~0xFF) != 0) { | 135 if ((codeUnit & ~_subsetMask) != 0) { |
| 138 throw new ArgumentError("Source contains non-Latin-1 characters."); | 136 throw new ArgumentError( |
| 139 } | 137 "Source contains invalid character with code point: $codeUnit."); |
| 140 _buffer[_bufferIndex] = codeUnit; | |
| 141 _bufferIndex++; | |
| 142 if (_bufferIndex == _buffer.length) { | |
| 143 _sink.addSlice(_buffer, 0, _bufferIndex, false); | |
| 144 _bufferIndex = 0; | |
| 145 } | 138 } |
| 146 } | 139 } |
| 147 if (isLast) close(); | 140 _sink.add(source.codeUnits); |
| 141 if (isLast) { | |
| 142 close(); | |
| 143 } | |
| 148 } | 144 } |
| 149 } | 145 } |
| 150 | 146 |
| 151 /** | 147 /** |
| 152 * This class converts Latin-1 bytes (lists of unsigned 8-bit integers) | 148 * This class converts Latin-1 bytes (lists of unsigned 8-bit integers) |
| 153 * to a string. | 149 * to a string. |
| 154 */ | 150 */ |
| 155 class Latin1Decoder extends Converter<List<int>, String> { | 151 abstract class _UnicodeSubsetDecoder extends Converter<List<int>, String> { |
| 156 final bool _allowInvalid; | 152 final bool _allowInvalid; |
| 153 final int _subsetMask; | |
| 157 | 154 |
| 158 /** | 155 /** |
| 159 * Instantiates a new [Latin1Decoder]. | 156 * Instantiates a new decoder. |
| 160 * | 157 * |
| 161 * The optional [allowInvalid] argument defines how [convert] deals | 158 * The [_allowInvalid] argument defines how [convert] deals |
| 162 * with invalid bytes. | 159 * with invalid bytes. |
| 163 * | 160 * |
| 164 * If it is `true`, [convert] replaces invalid bytes with the Unicode | 161 * The [_subsetMask] argument is a bit mask used to define the subset |
| 165 * Replacement character `U+FFFD` (�). | 162 * of Unicode being decoded. Use [_LATIN1_MASK] for Latin-1 (8-bit) or |
| 163 * [_ASCII_MASK] for ASCII (7-bit). | |
| 164 * | |
| 165 * If [_allowInvalid] is `true`, [convert] replaces invalid bytes with the | |
| 166 * Unicode Replacement character `U+FFFD` (�). | |
| 166 * Otherwise it throws a [FormatException]. | 167 * Otherwise it throws a [FormatException]. |
| 167 */ | 168 */ |
| 168 const Latin1Decoder({ bool allowInvalid: false }) | 169 const _UnicodeSubsetDecoder(this._allowInvalid, this._subsetMask); |
| 169 : this._allowInvalid = allowInvalid; | |
| 170 | 170 |
| 171 /** | 171 /** |
| 172 * Converts the Latin=1 [bytes] (a list of unsigned 8-bit integers) to the | 172 * Converts the [bytes] (a list of unsigned 7- or 8-bit integers) to the |
| 173 * corresponding string. | 173 * corresponding string. |
| 174 */ | 174 */ |
| 175 String convert(List<int> bytes) { | 175 String convert(List<int> bytes) { |
| 176 for (int i = 0; i < bytes.length; i++) { | 176 for (int i = 0; i < bytes.length; i++) { |
| 177 int byte = bytes[i]; | 177 int byte = bytes[i]; |
| 178 if ((byte & ~0xFF) != 0) { | 178 if ((byte & ~_subsetMask) != 0) { |
| 179 if (!_allowInvalid) { | 179 if (!_allowInvalid) { |
| 180 throw new FormatException("Non-byte in byte list"); | 180 throw new FormatException("Invalid value in input: $byte"); |
| 181 } | 181 } |
| 182 return _convertInvalid(bytes); | 182 return _convertInvalid(bytes); |
| 183 } | 183 } |
| 184 } | 184 } |
| 185 return new String.fromCharCodes(bytes); | 185 return new String.fromCharCodes(bytes); |
| 186 } | 186 } |
| 187 | 187 |
| 188 String _convertInvalid(List<int> bytes) { | 188 String _convertInvalid(List<int> bytes) { |
| 189 StringBuffer buffer = new StringBuffer(); | 189 StringBuffer buffer = new StringBuffer(); |
| 190 for (int i = 0; i < bytes.length; i++) { | 190 for (int i = 0; i < bytes.length; i++) { |
| 191 int value = bytes[i]; | 191 int value = bytes[i]; |
| 192 if ((value & ~0xFF) != 0) value = 0xFFFD; | 192 if ((value & ~_subsetMask) != 0) value = 0xFFFD; |
| 193 buffer.writeCharCode(value); | 193 buffer.writeCharCode(value); |
| 194 } | 194 } |
| 195 return buffer.toString(); | 195 return buffer.toString(); |
| 196 } | 196 } |
| 197 | 197 |
| 198 /** | 198 /** |
| 199 * Starts a chunked conversion. | 199 * Starts a chunked conversion. |
| 200 * | 200 * |
| 201 * The converter works more efficiently if the given [sink] is a | 201 * The converter works more efficiently if the given [sink] is a |
| 202 * [StringConversionSink]. | 202 * [StringConversionSink]. |
| 203 */ | 203 */ |
| 204 ByteConversionSink startChunkedConversion( | 204 ByteConversionSink startChunkedConversion( |
| 205 ChunkedConversionSink<String> sink) { | 205 ChunkedConversionSink<String> sink) { |
| 206 StringConversionSink stringSink; | 206 StringConversionSink stringSink; |
| 207 if (sink is StringConversionSink) { | 207 if (sink is StringConversionSink) { |
| 208 stringSink = sink; | 208 stringSink = sink; |
| 209 } else { | 209 } else { |
| 210 stringSink = new StringConversionSink.from(sink); | 210 stringSink = new StringConversionSink.from(sink); |
| 211 } | 211 } |
| 212 // TODO(lrn): Use stringSink.asUtf16Sink() if it becomes available. | 212 // TODO(lrn): Use stringSink.asUtf16Sink() if it becomes available. |
| 213 return new _Latin1DecoderSink(_allowInvalid, stringSink); | 213 return new _Latin1DecoderSink(_allowInvalid, stringSink); |
| 214 } | 214 } |
| 215 | 215 |
| 216 // Override the base-class's bind, to provide a better type. | 216 // Override the base-class's bind, to provide a better type. |
| 217 Stream<String> bind(Stream<List<int>> stream) => super.bind(stream); | 217 Stream<String> bind(Stream<List<int>> stream) => super.bind(stream); |
| 218 } | 218 } |
| 219 | 219 |
| 220 class _Latin1DecoderSink extends ByteConversionSinkBase { | 220 class AsciiDecoder extends _UnicodeSubsetDecoder { |
| 221 const AsciiDecoder({bool allowInvalid: false}) | |
| 222 : super(allowInvalid, _ASCII_MASK); | |
| 223 | |
| 224 /** | |
| 225 * Starts a chunked conversion. | |
| 226 * | |
| 227 * The converter works more efficiently if the given [sink] is a | |
| 228 * [StringConversionSink]. | |
| 229 */ | |
| 230 ByteConversionSink startChunkedConversion( | |
| 231 ChunkedConversionSink<String> sink) { | |
| 232 StringConversionSink stringSink; | |
| 233 if (sink is StringConversionSink) { | |
| 234 stringSink = sink; | |
| 235 } else { | |
| 236 stringSink = new StringConversionSink.from(sink); | |
| 237 } | |
| 238 // TODO(lrn): Use asUtf16Sink when it becomes available. It | |
| 239 // works just as well, is likely to have less decoding overhead, | |
| 240 // and make adding U+FFFD easier. | |
| 241 // At that time, merge this with _Latin1DecoderSink; | |
| 242 return new _AsciiDecoderSink(_allowInvalid, stringSink.asUtf8Sink(false)); | |
| 243 } | |
| 244 } | |
| 245 | |
| 246 class _AsciiDecoderSink extends ByteConversionSinkBase { | |
| 221 final bool _allowInvalid; | 247 final bool _allowInvalid; |
| 222 StringConversionSink _sink; | 248 ByteConversionSink _utf8Sink; |
| 223 _Latin1DecoderSink(this._allowInvalid, this._sink); | 249 _AsciiDecoderSink(this._allowInvalid, this._utf8Sink); |
| 224 | 250 |
| 225 void close() { | 251 void close() { |
| 226 _sink.close(); | 252 _utf8Sink.close(); |
| 227 } | 253 } |
| 228 | 254 |
| 229 void add(List<int> source) { | 255 void add(List<int> source) { |
| 230 addSlice(source, 0, source.length, false); | 256 addSlice(source, 0, source.length, false); |
| 231 } | 257 } |
| 232 | 258 |
| 233 void _addSliceToSink(List<int> source, int start, int end, bool isLast) { | |
| 234 // If _sink was a UTF-16 conversion sink, just add the slice directly with | |
| 235 // _sink.addSlice(source, start, end, isLast). | |
| 236 // The code below is an incredibly stupid workaround until a real | |
| 237 // solution can be made. | |
| 238 _sink.add(new String.fromCharCodes(source.getRange(start, end))); | |
| 239 if (isLast) close(); | |
| 240 } | |
| 241 | |
| 242 void addSlice(List<int> source, int start, int end, bool isLast) { | 259 void addSlice(List<int> source, int start, int end, bool isLast) { |
| 243 if (start < 0 || start > source.length) { | 260 if (start < 0 || start > source.length) { |
| 244 throw new RangeError.range(start, 0, source.length); | 261 throw new RangeError.range(start, 0, source.length); |
| 245 } | 262 } |
| 246 if (end < start || end > source.length) { | 263 if (end < start || end > source.length) { |
| 247 throw new RangeError.range(end, start, source.length); | 264 throw new RangeError.range(end, start, source.length); |
| 248 } | 265 } |
| 249 for (int i = start; i < end; i++) { | 266 for (int i = start; i < end; i++) { |
| 250 if ((source[i] & ~0xFF) != 0) { | 267 if ((source[i] & ~_ASCII_MASK) != 0) { |
| 251 if (_allowInvalid) { | 268 if (_allowInvalid) { |
| 252 if (i > start) _addSliceToSink(source, start, i); | 269 if (i > start) _utf8Sink.addSlice(source, start, i, false); |
| 253 // Add UTF-8 encoding of U+FFFD. | 270 // Add UTF-8 encoding of U+FFFD. |
| 254 _addSliceToSink(const[0xFFFD], 0, 1, false); | 271 _utf8Sink.add(const<int>[0xEF, 0xBF, 0xBD]); |
| 255 start = i + 1; | 272 start = i + 1; |
| 256 } else { | 273 } else { |
| 257 throw new FormatException("Source contains non-Latin-1 characters."); | 274 throw new FormatException("Source contains non-ASCII bytes."); |
| 258 } | 275 } |
| 259 } | 276 } |
| 260 } | 277 } |
| 261 if (start < end) { | 278 if (start < end) { |
| 262 _addSliceToSink(source, start, end, isLast); | 279 _utf8Sink.addSlice(source, start, end, isLast); |
| 263 } else if (isLast) { | 280 } else if (isLast) { |
| 264 close(); | 281 close(); |
| 265 } | 282 } |
| 266 } | 283 } |
| 267 } | 284 } |
| OLD | NEW |