OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 library convert.hex.decoder; | 5 library convert.hex.decoder; |
6 | 6 |
7 import 'dart:convert'; | 7 import 'dart:convert'; |
8 import 'dart:typed_data'; | 8 import 'dart:typed_data'; |
9 | 9 |
10 import 'package:charcode/ascii.dart'; | 10 import '../utils.dart'; |
11 | 11 |
12 /// The canonical instance of [HexDecoder]. | 12 /// The canonical instance of [HexDecoder]. |
13 const hexDecoder = const HexDecoder._(); | 13 const hexDecoder = const HexDecoder._(); |
14 | 14 |
15 /// A converter that decodes hexadecimal strings into byte arrays. | 15 /// A converter that decodes hexadecimal strings into byte arrays. |
16 /// | 16 /// |
17 /// Because two hexadecimal digits correspond to a single byte, this will throw | 17 /// Because two hexadecimal digits correspond to a single byte, this will throw |
18 /// a [FormatException] if given an odd-length string. It will also throw a | 18 /// a [FormatException] if given an odd-length string. It will also throw a |
19 /// [FormatException] if given a string containing non-hexadecimal code units. | 19 /// [FormatException] if given a string containing non-hexadecimal code units. |
20 class HexDecoder extends Converter<String, List<int>> { | 20 class HexDecoder extends Converter<String, List<int>> { |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
59 | 59 |
60 var codeUnits = string.codeUnits; | 60 var codeUnits = string.codeUnits; |
61 var bytes; | 61 var bytes; |
62 var bytesStart; | 62 var bytesStart; |
63 if (_lastDigit == null) { | 63 if (_lastDigit == null) { |
64 bytes = new Uint8List((end - start) ~/ 2); | 64 bytes = new Uint8List((end - start) ~/ 2); |
65 bytesStart = 0; | 65 bytesStart = 0; |
66 } else { | 66 } else { |
67 var hexPairs = (end - start - 1) ~/ 2; | 67 var hexPairs = (end - start - 1) ~/ 2; |
68 bytes = new Uint8List(1 + hexPairs); | 68 bytes = new Uint8List(1 + hexPairs); |
69 bytes[0] = _lastDigit + _digitForCodeUnit(codeUnits, start); | 69 bytes[0] = _lastDigit + digitForCodeUnit(codeUnits, start); |
70 start++; | 70 start++; |
71 bytesStart = 1; | 71 bytesStart = 1; |
72 } | 72 } |
73 | 73 |
74 _lastDigit = _decode(codeUnits, start, end, bytes, bytesStart); | 74 _lastDigit = _decode(codeUnits, start, end, bytes, bytesStart); |
75 | 75 |
76 _sink.add(bytes); | 76 _sink.add(bytes); |
77 if (isLast) close(); | 77 if (isLast) close(); |
78 } | 78 } |
79 | 79 |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
114 } | 114 } |
115 | 115 |
116 var bytes; | 116 var bytes; |
117 var bytesStart; | 117 var bytesStart; |
118 if (_lastDigit == null) { | 118 if (_lastDigit == null) { |
119 bytes = new Uint8List((end - start) ~/ 2); | 119 bytes = new Uint8List((end - start) ~/ 2); |
120 bytesStart = 0; | 120 bytesStart = 0; |
121 } else { | 121 } else { |
122 var hexPairs = (end - start - 1) ~/ 2; | 122 var hexPairs = (end - start - 1) ~/ 2; |
123 bytes = new Uint8List(1 + hexPairs); | 123 bytes = new Uint8List(1 + hexPairs); |
124 bytes[0] = _lastDigit + _digitForCodeUnit(chunk, start); | 124 bytes[0] = _lastDigit + digitForCodeUnit(chunk, start); |
125 start++; | 125 start++; |
126 bytesStart = 1; | 126 bytesStart = 1; |
127 } | 127 } |
128 | 128 |
129 _lastDigit = _decode(chunk, start, end, bytes, bytesStart); | 129 _lastDigit = _decode(chunk, start, end, bytes, bytesStart); |
130 | 130 |
131 _sink.add(bytes); | 131 _sink.add(bytes); |
132 if (isLast) close(); | 132 if (isLast) close(); |
133 } | 133 } |
134 | 134 |
(...skipping 10 matching lines...) Expand all Loading... |
145 /// | 145 /// |
146 /// This reads from [codeUnits] between [sourceStart] and [sourceEnd]. It writes | 146 /// This reads from [codeUnits] between [sourceStart] and [sourceEnd]. It writes |
147 /// the result into [destination] starting at [destinationStart]. | 147 /// the result into [destination] starting at [destinationStart]. |
148 /// | 148 /// |
149 /// If there's a leftover digit at the end of the decoding, this returns that | 149 /// If there's a leftover digit at the end of the decoding, this returns that |
150 /// digit. Otherwise it returns `null`. | 150 /// digit. Otherwise it returns `null`. |
151 int _decode(List<int> codeUnits, int sourceStart, int sourceEnd, | 151 int _decode(List<int> codeUnits, int sourceStart, int sourceEnd, |
152 List<int> destination, int destinationStart) { | 152 List<int> destination, int destinationStart) { |
153 var destinationIndex = destinationStart; | 153 var destinationIndex = destinationStart; |
154 for (var i = sourceStart; i < sourceEnd - 1; i += 2) { | 154 for (var i = sourceStart; i < sourceEnd - 1; i += 2) { |
155 var firstDigit = _digitForCodeUnit(codeUnits, i); | 155 var firstDigit = digitForCodeUnit(codeUnits, i); |
156 var secondDigit = _digitForCodeUnit(codeUnits, i + 1); | 156 var secondDigit = digitForCodeUnit(codeUnits, i + 1); |
157 destination[destinationIndex++] = 16 * firstDigit + secondDigit; | 157 destination[destinationIndex++] = 16 * firstDigit + secondDigit; |
158 } | 158 } |
159 | 159 |
160 if ((sourceEnd - sourceStart).isEven) return null; | 160 if ((sourceEnd - sourceStart).isEven) return null; |
161 return 16 * _digitForCodeUnit(codeUnits, sourceEnd - 1); | 161 return 16 * digitForCodeUnit(codeUnits, sourceEnd - 1); |
162 } | 162 } |
163 | |
164 /// Returns the digit (0 through 15) corresponding to the hexadecimal code unit | |
165 /// at index [i] in [codeUnits]. | |
166 /// | |
167 /// If the given code unit isn't valid hexadecimal, throws a [FormatException]. | |
168 int _digitForCodeUnit(List<int> codeUnits, int index) { | |
169 // If the code unit is a numeral, get its value. XOR works because 0 in ASCII | |
170 // is `0b110000` and the other numerals come after it in ascending order and | |
171 // take up at most four bits. | |
172 // | |
173 // We check for digits first because it ensures there's only a single branch | |
174 // for 10 out of 16 of the expected cases. We don't count the `digit >= 0` | |
175 // check because branch prediction will always work on it for valid data. | |
176 var codeUnit = codeUnits[index]; | |
177 var digit = $0 ^ codeUnit; | |
178 if (digit <= 9) { | |
179 if (digit >= 0) return digit; | |
180 } else { | |
181 // If the code unit is an uppercase letter, convert it to lowercase. This | |
182 // works because uppercase letters in ASCII are exactly `0b100000 = 0x20` | |
183 // less than lowercase letters, so if we ensure that that bit is 1 we ensure | |
184 // that the letter is lowercase. | |
185 var letter = 0x20 | codeUnit; | |
186 if ($a <= letter && letter <= $f) return letter - $a + 10; | |
187 } | |
188 | |
189 throw new FormatException( | |
190 "Invalid hexadecimal code unit " | |
191 "U+${codeUnit.toRadixString(16).padLeft(4, '0')}.", | |
192 codeUnits, index); | |
193 } | |
OLD | NEW |