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

Side by Side Diff: sdk/lib/convert/ascii.dart

Issue 22875034: Add ASCII encoding to lib/convert. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Address review comments Created 7 years, 4 months 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 | « no previous file | sdk/lib/convert/convert.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) 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 ASCII
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 = ASCII.encode("This is ASCII!");
16 * var decoded = LATIN1.decode([0x62, 0x6c, 0xe5, 0x62, 0xe6, 16 * var decoded = ASCII.decode([0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73,
17 * 0x72, 0x67, 0x72, 0xf8, 0x64]); 17 * 0x20, 0x41, 0x53, 0x43, 0x49, 0x49, 0x21]);
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 * An [AsciiCodec] allows encoding strings as ASCII bytes
23 * and decodes Latin-1 bytes to strings. 25 * and decoding 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.
32 * values are decoded into the Unicode Replacement character (U+FFFD). 34 * If allowing invalid values, the values will be decoded into the Unicode
33 * Calls to the [decode] method can override this default. 35 * Replacement character (U+FFFD). If not, an exception will be thrown.
36 * Calls to the [decode] method can choose to override this default.
34 * 37 *
35 * Encoders will not accept invalid (non Latin-1) characters. 38 * Encoders will not accept invalid (non Latin-1) characters.
36 */ 39 */
37 const Latin1Codec({bool allowInvalid: false}) : _allowInvalid = allowInvalid; 40 const AsciiCodec({bool allowInvalid: false}) : _allowInvalid = allowInvalid;
38 41
39 /** 42 /**
40 * Decodes the Latin-1 [bytes] (a list of unsigned 8-bit integers) to the 43 * Decodes the ASCII [bytes] (a list of unsigned 7-bit integers) to the
41 * corresponding string. 44 * corresponding string.
42 * 45 *
43 * If [bytes] contains values that are not in the range 0 .. 255, the decoder 46 * If [bytes] contains values that are not in the range 0 .. 127, the decoder
44 * will eventually throw a [FormatException]. 47 * will eventually throw a [FormatException].
45 * 48 *
46 * If [allowInvalid] is not provided, it defaults to the value used to create 49 * If [allowInvalid] is not provided, it defaults to the value used to create
47 * this [Latin1Codec]. 50 * this [AsciiCodec].
48 */ 51 */
49 String decode(List<int> bytes, { bool allowInvalid }) { 52 String decode(List<int> bytes, { bool allowInvalid }) {
50 if (allowInvalid == null) allowInvalid = _allowInvalid; 53 if (allowInvalid == null) allowInvalid = _allowInvalid;
51 if (allowInvalid) { 54 if (allowInvalid) {
52 return const Latin1Decoder(allowInvalid: true).convert(bytes); 55 return const AsciiDecoder(allowInvalid: true).convert(bytes);
53 } else { 56 } else {
54 return const Latin1Decoder(allowInvalid: false).convert(bytes); 57 return const AsciiDecoder(allowInvalid: false).convert(bytes);
55 } 58 }
56 } 59 }
57 60
58 Converter<String, List<int>> get encoder => const Latin1Encoder(); 61 Converter<String, List<int>> get encoder => const AsciiEncoder();
59 62
60 Converter<List<int>, String> get decoder => 63 Converter<List<int>, String> get decoder =>
61 _allowInvalid ? const Latin1Decoder(allowInvalid: true) 64 _allowInvalid ? const AsciiDecoder(allowInvalid: true)
62 : const Latin1Decoder(allowInvalid: false); 65 : const AsciiDecoder(allowInvalid: false);
63 } 66 }
64 67
65 /** 68 // Superclass for [AsciiEncoder] and [Latin1Encoder].
66 * This class converts strings of only ISO Latin-1 characters to bytes. 69 // Generalizes common operations that only differ by a mask;
67 */ 70 class _UnicodeSubsetEncoder extends Converter<String, List<int>> {
68 class Latin1Encoder extends Converter<String, List<int>> { 71 final int _subsetMask;
69 const Latin1Encoder();
70 72
71 /** 73 const _UnicodeSubsetEncoder(this._subsetMask);
72 * Converts [string] to its Latin-1 bytes (a list of 74
73 * unsigned 8-bit integers).
74 */
75 List<int> convert(String string) { 75 List<int> convert(String string) {
76 // TODO(11971): Use Uint8List when possible. 76 // TODO(11971): Use Uint8List when possible.
77 List result = new List<int>(string.length); 77 List result = new List<int>(string.length);
78 for (int i = 0; i < string.length; i++) { 78 for (int i = 0; i < string.length; i++) {
79 var codeUnit = string.codeUnitAt(i); 79 var codeUnit = string.codeUnitAt(i);
80 if ((codeUnit & ~0xFF) != 0) { 80 if ((codeUnit & ~_subsetMask) != 0) {
81 throw new ArgumentError("String contains non-Latin-1 characters."); 81 throw new ArgumentError("String contains invalid characters.");
82 } 82 }
83 result[i] = codeUnit; 83 result[i] = codeUnit;
84 } 84 }
85 return result; 85 return result;
86 } 86 }
87 87
88 /** 88 /**
89 * Starts a chunked conversion. 89 * Starts a chunked conversion.
90 * 90 *
91 * The converter works more efficiently if the given [sink] is a 91 * The converter works more efficiently if the given [sink] is a
92 * [ByteConversionSink]. 92 * [ByteConversionSink].
93 */ 93 */
94 StringConversionSink startChunkedConversion( 94 StringConversionSink startChunkedConversion(
95 ChunkedConversionSink<List<int>> sink) { 95 ChunkedConversionSink<List<int>> sink) {
96 if (sink is! ByteConversionSink) { 96 if (sink is! ByteConversionSink) {
97 sink = new ByteConversionSink.from(sink); 97 sink = new ByteConversionSink.from(sink);
98 } 98 }
99 return new _Latin1EncoderSink(sink); 99 return new _UnicodeSubsetEncoderSink(_subsetMask, sink);
100 } 100 }
101 101
102 // Override the base-class' bind, to provide a better type. 102 // Override the base-class' bind, to provide a better type.
103 Stream<List<int>> bind(Stream<String> stream) => super.bind(stream); 103 Stream<List<int>> bind(Stream<String> stream) => super.bind(stream);
104 } 104 }
105 105
106 /** 106 /**
107 * This class converts strings of only ASCII characters to bytes.
108 */
109 class AsciiEncoder extends _UnicodeSubsetEncoder {
110 const AsciiEncoder() : super(_ASCII_MASK);
111 }
112
113 /**
107 * This class encodes chunked strings to bytes (unsigned 8-bit 114 * This class encodes chunked strings to bytes (unsigned 8-bit
108 * integers). 115 * integers).
109 */ 116 */
110 class _Latin1EncoderSink extends StringConversionSinkBase { 117 class _UnicodeSubsetEncoderSink extends StringConversionSinkBase {
111 static const _DEFAULT_BYTE_BUFFER_SIZE = 1024;
112 final ByteConversionSink _sink; 118 final ByteConversionSink _sink;
119 final int _subsetMask;
113 120
114 // TODO(11971): Use Uint8List when available. 121 _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 122
120 void close() { 123 void close() {
121 if (_bufferIndex > 0) { 124 _sink.close();
122 _sink.addSlice(_buffer, 0, _bufferIndex, true);
123 } else {
124 _sink.close();
125 }
126 } 125 }
127 126
128 void addSlice(String source, int start, int end, bool isLast) { 127 void addSlice(String source, int start, int end, bool isLast) {
129 if (start < 0 || start > source.length) { 128 if (start < 0 || start > source.length) {
130 throw new RangeError.range(start, 0, source.length); 129 throw new RangeError.range(start, 0, source.length);
131 } 130 }
132 if (end < start || end > source.length) { 131 if (end < start || end > source.length) {
133 throw new RangeError.range(end, start, source.length); 132 throw new RangeError.range(end, start, source.length);
134 } 133 }
135 for (int i = start; i < end; i++) { 134 for (int i = start; i < end; i++) {
136 int codeUnit = source.codeUnitAt(i); 135 int codeUnit = source.codeUnitAt(i);
137 if ((codeUnit & ~0xFF) != 0) { 136 if ((codeUnit & ~_subsetMask) != 0) {
138 throw new ArgumentError("Source contains non-Latin-1 characters."); 137 throw new ArgumentError(
139 } 138 "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 } 139 }
146 } 140 }
147 if (isLast) close(); 141 _sink.add(source.codeUnits);
142 if (isLast) {
143 close();
144 }
148 } 145 }
149 } 146 }
150 147
151 /** 148 /**
152 * This class converts Latin-1 bytes (lists of unsigned 8-bit integers) 149 * This class converts Latin-1 bytes (lists of unsigned 8-bit integers)
153 * to a string. 150 * to a string.
154 */ 151 */
155 class Latin1Decoder extends Converter<List<int>, String> { 152 abstract class _UnicodeSubsetDecoder extends Converter<List<int>, String> {
156 final bool _allowInvalid; 153 final bool _allowInvalid;
154 final int _subsetMask;
157 155
158 /** 156 /**
159 * Instantiates a new [Latin1Decoder]. 157 * Instantiates a new decoder.
160 * 158 *
161 * The optional [allowInvalid] argument defines how [convert] deals 159 * The [_allowInvalid] argument defines how [convert] deals
162 * with invalid bytes. 160 * with invalid bytes.
163 * 161 *
164 * If it is `true`, [convert] replaces invalid bytes with the Unicode 162 * The [_subsetMask] argument is a bit mask used to define the subset
165 * Replacement character `U+FFFD` (�). 163 * of Unicode being decoded. Use [_LATIN1_MASK] for Latin-1 (8-bit) or
164 * [_ASCII_MASK] for ASCII (7-bit).
165 *
166 * If [_allowInvalid] is `true`, [convert] replaces invalid bytes with the
167 * Unicode Replacement character `U+FFFD` (�).
166 * Otherwise it throws a [FormatException]. 168 * Otherwise it throws a [FormatException].
167 */ 169 */
168 const Latin1Decoder({ bool allowInvalid: false }) 170 const _UnicodeSubsetDecoder(this._allowInvalid, this._subsetMask);
169 : this._allowInvalid = allowInvalid;
170 171
171 /** 172 /**
172 * Converts the Latin=1 [bytes] (a list of unsigned 8-bit integers) to the 173 * Converts the [bytes] (a list of unsigned 7- or 8-bit integers) to the
173 * corresponding string. 174 * corresponding string.
174 */ 175 */
175 String convert(List<int> bytes) { 176 String convert(List<int> bytes) {
176 for (int i = 0; i < bytes.length; i++) { 177 for (int i = 0; i < bytes.length; i++) {
177 int byte = bytes[i]; 178 int byte = bytes[i];
178 if ((byte & ~0xFF) != 0) { 179 if ((byte & ~_subsetMask) != 0) {
179 if (!_allowInvalid) { 180 if (!_allowInvalid) {
180 throw new FormatException("Non-byte in byte list"); 181 throw new FormatException("Invalid value in input: $byte");
181 } 182 }
182 return _convertInvalid(bytes); 183 return _convertInvalid(bytes);
183 } 184 }
184 } 185 }
185 return new String.fromCharCodes(bytes); 186 return new String.fromCharCodes(bytes);
186 } 187 }
187 188
188 String _convertInvalid(List<int> bytes) { 189 String _convertInvalid(List<int> bytes) {
189 StringBuffer buffer = new StringBuffer(); 190 StringBuffer buffer = new StringBuffer();
190 for (int i = 0; i < bytes.length; i++) { 191 for (int i = 0; i < bytes.length; i++) {
191 int value = bytes[i]; 192 int value = bytes[i];
192 if ((value & ~0xFF) != 0) value = 0xFFFD; 193 if ((value & ~_subsetMask) != 0) value = 0xFFFD;
193 buffer.writeCharCode(value); 194 buffer.writeCharCode(value);
194 } 195 }
195 return buffer.toString(); 196 return buffer.toString();
196 } 197 }
197 198
198 /** 199 /**
199 * Starts a chunked conversion. 200 * Starts a chunked conversion.
200 * 201 *
201 * The converter works more efficiently if the given [sink] is a 202 * The converter works more efficiently if the given [sink] is a
202 * [StringConversionSink]. 203 * [StringConversionSink].
203 */ 204 */
204 ByteConversionSink startChunkedConversion( 205 ByteConversionSink startChunkedConversion(
205 ChunkedConversionSink<String> sink) { 206 ChunkedConversionSink<String> sink) {
206 StringConversionSink stringSink; 207 StringConversionSink stringSink;
207 if (sink is StringConversionSink) { 208 if (sink is StringConversionSink) {
208 stringSink = sink; 209 stringSink = sink;
209 } else { 210 } else {
210 stringSink = new StringConversionSink.from(sink); 211 stringSink = new StringConversionSink.from(sink);
211 } 212 }
212 // TODO(lrn): Use stringSink.asUtf16Sink() if it becomes available. 213 // TODO(lrn): Use stringSink.asUtf16Sink() if it becomes available.
213 return new _Latin1DecoderSink(_allowInvalid, stringSink); 214 return new _Latin1DecoderSink(_allowInvalid, stringSink);
214 } 215 }
215 216
216 // Override the base-class's bind, to provide a better type. 217 // Override the base-class's bind, to provide a better type.
217 Stream<String> bind(Stream<List<int>> stream) => super.bind(stream); 218 Stream<String> bind(Stream<List<int>> stream) => super.bind(stream);
218 } 219 }
219 220
220 class _Latin1DecoderSink extends ByteConversionSinkBase { 221 class AsciiDecoder extends _UnicodeSubsetDecoder {
222 const AsciiDecoder({bool allowInvalid: false})
223 : super(allowInvalid, _ASCII_MASK);
224
225 /**
226 * Starts a chunked conversion.
227 *
228 * The converter works more efficiently if the given [sink] is a
229 * [StringConversionSink].
230 */
231 ByteConversionSink startChunkedConversion(
232 ChunkedConversionSink<String> sink) {
233 StringConversionSink stringSink;
234 if (sink is StringConversionSink) {
235 stringSink = sink;
236 } else {
237 stringSink = new StringConversionSink.from(sink);
238 }
239 // TODO(lrn): Use asUtf16Sink when it becomes available. It
240 // works just as well, is likely to have less decoding overhead,
241 // and make adding U+FFFD easier.
242 // At that time, merge this with _Latin1DecoderSink;
243 return new _AsciiDecoderSink(_allowInvalid, stringSink.asUtf8Sink(false));
244 }
245 }
246
247 class _AsciiDecoderSink extends ByteConversionSinkBase {
221 final bool _allowInvalid; 248 final bool _allowInvalid;
222 StringConversionSink _sink; 249 ByteConversionSink _utf8Sink;
223 _Latin1DecoderSink(this._allowInvalid, this._sink); 250 _AsciiDecoderSink(this._allowInvalid, this._utf8Sink);
224 251
225 void close() { 252 void close() {
226 _sink.close(); 253 _utf8Sink.close();
227 } 254 }
228 255
229 void add(List<int> source) { 256 void add(List<int> source) {
230 addSlice(source, 0, source.length, false); 257 addSlice(source, 0, source.length, false);
231 } 258 }
232 259
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) { 260 void addSlice(List<int> source, int start, int end, bool isLast) {
243 if (start < 0 || start > source.length) { 261 if (start < 0 || start > source.length) {
244 throw new RangeError.range(start, 0, source.length); 262 throw new RangeError.range(start, 0, source.length);
245 } 263 }
246 if (end < start || end > source.length) { 264 if (end < start || end > source.length) {
247 throw new RangeError.range(end, start, source.length); 265 throw new RangeError.range(end, start, source.length);
248 } 266 }
249 for (int i = start; i < end; i++) { 267 for (int i = start; i < end; i++) {
250 if ((source[i] & ~0xFF) != 0) { 268 if ((source[i] & ~_ASCII_MASK) != 0) {
251 if (_allowInvalid) { 269 if (_allowInvalid) {
252 if (i > start) _addSliceToSink(source, start, i, false); 270 if (i > start) _utf8Sink.addSlice(source, start, i, false);
253 // Add UTF-8 encoding of U+FFFD. 271 // Add UTF-8 encoding of U+FFFD.
254 _addSliceToSink(const[0xFFFD], 0, 1, false); 272 _utf8Sink.add(const<int>[0xEF, 0xBF, 0xBD]);
255 start = i + 1; 273 start = i + 1;
256 } else { 274 } else {
257 throw new FormatException("Source contains non-Latin-1 characters."); 275 throw new FormatException("Source contains non-ASCII bytes.");
258 } 276 }
259 } 277 }
260 } 278 }
261 if (start < end) { 279 if (start < end) {
262 _addSliceToSink(source, start, end, isLast); 280 _utf8Sink.addSlice(source, start, end, isLast);
263 } else if (isLast) { 281 } else if (isLast) {
264 close(); 282 close();
265 } 283 }
266 } 284 }
267 } 285 }
OLDNEW
« no previous file with comments | « no previous file | sdk/lib/convert/convert.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698