OLD | NEW |
---|---|
(Empty) | |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library convert.hex.decoder; | |
6 | |
7 import 'dart:convert'; | |
8 import 'dart:typed_data'; | |
9 | |
10 import 'package:charcode/ascii.dart'; | |
11 | |
12 /// The canonical instance of [HexDecoder]. | |
13 const hexDecoder = const HexDecoder._(); | |
14 | |
15 /// A converter that decodes hexadecimal strings into byte arrays. | |
16 /// | |
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 | |
19 /// [FormatException] if given a string containing non-hexadecimal code units. | |
20 class HexDecoder extends Converter<String, List<int>> { | |
21 const HexDecoder._(); | |
22 | |
23 List<int> convert(String string) { | |
24 if (!string.length.isEven) { | |
25 throw new FormatException("Invalid input length, must be even.", | |
26 string, string.length); | |
27 } | |
28 | |
29 var bytes = new Uint8List(string.length ~/ 2); | |
30 _decode(string.codeUnits, bytes, 0); | |
31 return bytes; | |
32 } | |
33 | |
34 StringConversionSink startChunkedConversion(Sink<List<int>> sink) => | |
35 new _HexDecoderSink(sink); | |
36 } | |
37 | |
38 /// A conversion sink for chunked hexadecimal decoding. | |
39 class _HexDecoderSink extends StringConversionSinkBase { | |
40 /// The underlying sink to which decoded byte arrays will be passed. | |
41 final Sink<List<int>> _sink; | |
42 | |
43 /// The trailing digit from the previous string. | |
44 /// | |
45 /// This will be non-`null` if the most recent string had an odd number of | |
46 /// hexadecimal digits. Since it's the most significant digit, it's always a | |
47 /// multiple of 16. | |
48 int _lastDigit; | |
49 | |
50 _HexDecoderSink(this._sink); | |
51 | |
52 void addSlice(String string, int start, int end, bool isLast) { | |
53 RangeError.checkValidRange(start, end, string.length); | |
Lasse Reichstein Nielsen
2015/09/24 08:07:02
Maybe do
end = RangeError.checkValidRange(...);
nweiz
2015/09/24 23:23:42
StringConversionSink.addSlice's documentation does
| |
54 | |
55 var bytes; | |
56 var bytesStart; | |
57 if (_lastDigit == null) { | |
58 bytes = new Uint8List((end - start) ~/ 2); | |
59 bytesStart = 0; | |
60 } else { | |
61 var hexPairs = (end - start - 1) ~/ 2; | |
62 bytes = new Uint8List(1 + hexPairs); | |
63 bytes[0] = _lastDigit + _digitForCodeUnit(string.codeUnitAt(start)); | |
64 start++; | |
65 bytesStart = 1; | |
66 } | |
67 | |
68 var codeUnits = string.codeUnits.take(end).skip(start); | |
Lasse Reichstein Nielsen
2015/09/24 08:07:02
I want to add getRange(start, end) to Iterable, bu
Lasse Reichstein Nielsen
2015/09/24 11:37:39
However, since codeUnits is a list, you *can* writ
sra1
2015/09/24 18:39:35
On dart2js it is going to be very much better to p
| |
69 _lastDigit = _decode(codeUnits, bytes, bytesStart); | |
70 | |
71 _sink.add(bytes); | |
72 if (isLast) close(); | |
73 } | |
74 | |
75 ByteConversionSink asUtf8Sink(bool allowMalformed) => | |
76 new _HexDecoderByteSink(_sink); | |
77 | |
78 void close() { | |
79 if (_lastDigit != null) { | |
80 throw new FormatException("Invalid input length, must be even."); | |
81 } | |
82 | |
83 _sink.close(); | |
84 } | |
85 } | |
86 | |
87 /// A conversion sink for chunked hexadecimal decoding from UTF-8 bytes. | |
88 class _HexDecoderByteSink extends ByteConversionSinkBase { | |
89 /// The underlying sink to which decoded byte arrays will be passed. | |
90 final Sink<List<int>> _sink; | |
91 | |
92 /// The trailing digit from the previous string. | |
93 /// | |
94 /// This will be non-`null` if the most recent string had an odd number of | |
95 /// hexadecimal digits. Since it's the most significant digit, it's always a | |
96 /// multiple of 16. | |
97 int _lastDigit; | |
98 | |
99 _HexDecoderByteSink(this._sink); | |
100 | |
101 void add(List<int> chunk) => addSlice(chunk, 0, chunk.length, false); | |
102 | |
103 void addSlice(List<int> chunk, int start, int end, bool isLast) { | |
104 RangeError.checkValidRange(start, end, chunk.length); | |
105 | |
Lasse Reichstein Nielsen
2015/09/24 08:07:02
You need to handle the case where start==end someh
nweiz
2015/09/24 23:23:42
Done.
| |
106 var bytes; | |
107 var bytesStart; | |
108 if (_lastDigit == null) { | |
109 bytes = new Uint8List((end - start) ~/ 2); | |
110 bytesStart = 0; | |
111 } else { | |
112 var hexPairs = (end - start - 1) ~/ 2; | |
Lasse Reichstein Nielsen
2015/09/24 08:07:02
Maybe declare
var length = end - start;
Consider
nweiz
2015/09/24 23:23:42
I like how the current writing makes hexPairs the
Lasse Reichstein Nielsen
2015/09/25 06:40:33
I guess that's just a difference in perspective. I
| |
113 bytes = new Uint8List(1 + hexPairs); | |
114 bytes[0] = _lastDigit + _digitForCodeUnit(chunk[start]); | |
115 start++; | |
116 bytesStart = 1; | |
117 } | |
118 | |
119 var codeUnits = chunk.take(end).skip(start); | |
120 _lastDigit = _decode(codeUnits, bytes, bytesStart); | |
Lasse Reichstein Nielsen
2015/09/24 08:07:02
I still think using a list+start+end is more effic
| |
121 | |
122 _sink.add(bytes); | |
123 if (isLast) close(); | |
124 } | |
125 | |
126 void close() { | |
127 if (_lastDigit != null) { | |
128 throw new FormatException("Invalid input length, must be even."); | |
129 } | |
130 | |
131 _sink.close(); | |
132 } | |
133 } | |
134 | |
135 /// Decodes [codeUnits] and writes the result into [destination]. | |
136 /// | |
137 /// This begins writing into destination at the index [start]. It returns the | |
138 /// leftover digit from the end of [codeUnits], if any. | |
139 int _decode(Iterable<int> codeUnits, List<int> destination, int start) { | |
140 var iterator = codeUnits.iterator; | |
141 for (var i = start;; i++) { | |
142 if (!iterator.moveNext()) return null; | |
143 var firstDigit = _digitForCodeUnit(iterator.current) * 16; | |
sra1
2015/09/24 18:39:35
It really helps dart2js to have the constant first
nweiz
2015/09/24 23:23:42
Done.
| |
144 | |
145 if (!iterator.moveNext()) return firstDigit; | |
146 var secondDigit = _digitForCodeUnit(iterator.current); | |
sra1
2015/09/24 18:39:35
dart2js thinks iterator.current can be null (becau
| |
147 | |
148 destination[i] = firstDigit + secondDigit; | |
sra1
2015/09/24 18:39:35
nit: it feels wrong to call both of these xxxDigit
nweiz
2015/09/24 23:23:42
Done.
| |
149 } | |
150 } | |
151 | |
152 /// Returns the digit (0 through 15) corresponding to the hexadecimal code unit | |
153 /// [codeUnit]. | |
154 /// | |
155 /// If [codeUnit] isn't a valid hexadecimal code unit, throws a | |
156 /// [FormatException]. | |
157 int _digitForCodeUnit(int codeUnit) { | |
158 if (codeUnit >= $0 && codeUnit <= $9) return codeUnit - $0; | |
159 if (codeUnit >= $a && codeUnit <= $f) return codeUnit - $a + 10; | |
160 if (codeUnit >= $A && codeUnit <= $F) return codeUnit - $A + 10; | |
161 | |
162 throw new FormatException("Invalid hexadecimal code unit " | |
163 "U+${codeUnit.toRadixString(16).padLeft(4, '0')}."); | |
164 } | |
OLD | NEW |