| Index: packages/convert/lib/src/hex/decoder.dart
|
| diff --git a/packages/convert/lib/src/hex/decoder.dart b/packages/convert/lib/src/hex/decoder.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d7a7555b34c4203314fba296e2755e5ef64c812d
|
| --- /dev/null
|
| +++ b/packages/convert/lib/src/hex/decoder.dart
|
| @@ -0,0 +1,172 @@
|
| +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +library convert.hex.decoder;
|
| +
|
| +import 'dart:convert';
|
| +import 'dart:typed_data';
|
| +
|
| +import '../utils.dart';
|
| +
|
| +/// The canonical instance of [HexDecoder].
|
| +const hexDecoder = const HexDecoder._();
|
| +
|
| +/// A converter that decodes hexadecimal strings into byte arrays.
|
| +///
|
| +/// Because two hexadecimal digits correspond to a single byte, this will throw
|
| +/// a [FormatException] if given an odd-length string. It will also throw a
|
| +/// [FormatException] if given a string containing non-hexadecimal code units.
|
| +class HexDecoder extends Converter<String, List<int>> {
|
| + const HexDecoder._();
|
| +
|
| + List<int> convert(String string) {
|
| + if (!string.length.isEven) {
|
| + throw new FormatException("Invalid input length, must be even.",
|
| + string, string.length);
|
| + }
|
| +
|
| + var bytes = new Uint8List(string.length ~/ 2);
|
| + _decode(string.codeUnits, 0, string.length, bytes, 0);
|
| + return bytes;
|
| + }
|
| +
|
| + StringConversionSink startChunkedConversion(Sink<List<int>> sink) =>
|
| + new _HexDecoderSink(sink);
|
| +}
|
| +
|
| +/// A conversion sink for chunked hexadecimal decoding.
|
| +class _HexDecoderSink extends StringConversionSinkBase {
|
| + /// The underlying sink to which decoded byte arrays will be passed.
|
| + final Sink<List<int>> _sink;
|
| +
|
| + /// The trailing digit from the previous string.
|
| + ///
|
| + /// This will be non-`null` if the most recent string had an odd number of
|
| + /// hexadecimal digits. Since it's the most significant digit, it's always a
|
| + /// multiple of 16.
|
| + int _lastDigit;
|
| +
|
| + _HexDecoderSink(this._sink);
|
| +
|
| + void addSlice(String string, int start, int end, bool isLast) {
|
| + RangeError.checkValidRange(start, end, string.length);
|
| +
|
| + if (start == end) {
|
| + if (isLast) _close(string, end);
|
| + return;
|
| + }
|
| +
|
| + var codeUnits = string.codeUnits;
|
| + Uint8List bytes;
|
| + int bytesStart;
|
| + if (_lastDigit == null) {
|
| + bytes = new Uint8List((end - start) ~/ 2);
|
| + bytesStart = 0;
|
| + } else {
|
| + var hexPairs = (end - start - 1) ~/ 2;
|
| + bytes = new Uint8List(1 + hexPairs);
|
| + bytes[0] = _lastDigit + digitForCodeUnit(codeUnits, start);
|
| + start++;
|
| + bytesStart = 1;
|
| + }
|
| +
|
| + _lastDigit = _decode(codeUnits, start, end, bytes, bytesStart);
|
| +
|
| + _sink.add(bytes);
|
| + if (isLast) _close(string, end);
|
| + }
|
| +
|
| + ByteConversionSink asUtf8Sink(bool allowMalformed) =>
|
| + new _HexDecoderByteSink(_sink);
|
| +
|
| + void close() => _close();
|
| +
|
| + /// Like [close], but includes [string] and [index] in the [FormatException]
|
| + /// if one is thrown.
|
| + void _close([String string, int index]) {
|
| + if (_lastDigit != null) {
|
| + throw new FormatException(
|
| + "Input ended with incomplete encoded byte.", string, index);
|
| + }
|
| +
|
| + _sink.close();
|
| + }
|
| +}
|
| +
|
| +/// A conversion sink for chunked hexadecimal decoding from UTF-8 bytes.
|
| +class _HexDecoderByteSink extends ByteConversionSinkBase {
|
| + /// The underlying sink to which decoded byte arrays will be passed.
|
| + final Sink<List<int>> _sink;
|
| +
|
| + /// The trailing digit from the previous string.
|
| + ///
|
| + /// This will be non-`null` if the most recent string had an odd number of
|
| + /// hexadecimal digits. Since it's the most significant digit, it's always a
|
| + /// multiple of 16.
|
| + int _lastDigit;
|
| +
|
| + _HexDecoderByteSink(this._sink);
|
| +
|
| + void add(List<int> chunk) => addSlice(chunk, 0, chunk.length, false);
|
| +
|
| + void addSlice(List<int> chunk, int start, int end, bool isLast) {
|
| + RangeError.checkValidRange(start, end, chunk.length);
|
| +
|
| + if (start == end) {
|
| + if (isLast) _close(chunk, end);
|
| + return;
|
| + }
|
| +
|
| + Uint8List bytes;
|
| + int bytesStart;
|
| + if (_lastDigit == null) {
|
| + bytes = new Uint8List((end - start) ~/ 2);
|
| + bytesStart = 0;
|
| + } else {
|
| + var hexPairs = (end - start - 1) ~/ 2;
|
| + bytes = new Uint8List(1 + hexPairs);
|
| + bytes[0] = _lastDigit + digitForCodeUnit(chunk, start);
|
| + start++;
|
| + bytesStart = 1;
|
| + }
|
| +
|
| + _lastDigit = _decode(chunk, start, end, bytes, bytesStart);
|
| +
|
| + _sink.add(bytes);
|
| + if (isLast) _close(chunk, end);
|
| + }
|
| +
|
| + void close() => _close();
|
| +
|
| + /// Like [close], but includes [chunk] and [index] in the [FormatException]
|
| + /// if one is thrown.
|
| + void _close([List<int> chunk, int index]) {
|
| + if (_lastDigit != null) {
|
| + throw new FormatException(
|
| + "Input ended with incomplete encoded byte.", chunk, index);
|
| + }
|
| +
|
| + _sink.close();
|
| + }
|
| +}
|
| +
|
| +/// Decodes [codeUnits] and writes the result into [destination].
|
| +///
|
| +/// This reads from [codeUnits] between [sourceStart] and [sourceEnd]. It writes
|
| +/// the result into [destination] starting at [destinationStart].
|
| +///
|
| +/// If there's a leftover digit at the end of the decoding, this returns that
|
| +/// digit. Otherwise it returns `null`.
|
| +int _decode(List<int> codeUnits, int sourceStart, int sourceEnd,
|
| + List<int> destination, int destinationStart) {
|
| + var destinationIndex = destinationStart;
|
| + for (var i = sourceStart; i < sourceEnd - 1; i += 2) {
|
| + var firstDigit = digitForCodeUnit(codeUnits, i);
|
| + var secondDigit = digitForCodeUnit(codeUnits, i + 1);
|
| + destination[destinationIndex++] = 16 * firstDigit + secondDigit;
|
| + }
|
| +
|
| + if ((sourceEnd - sourceStart).isEven) return null;
|
| + return 16 * digitForCodeUnit(codeUnits, sourceEnd - 1);
|
| +}
|
|
|