diff --git a/CHANGELOG.md b/CHANGELOG.md index acd4018..582eb4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,4 +28,5 @@ - Implement `xsd:duration` codec and data type - Implement `xsd:datetime` codec and data type - Implement `xsd:anyUri` codec -- Implement `xsd:decimal` codec \ No newline at end of file +- Implement `xsd:decimal` codec +- Implement `xsd:double` codec diff --git a/README.md b/README.md index 9965b50..b17335a 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ This library aims to support the following XSD 1.1 built-in datatypes that are c | `xsd:language` | ✅ | ✅ | `Locale` | `package:intl` | | `xsd:NCName` | ✅ | ✅ | `String` | `dart:core` | | `xsd:decimal` | ✅ | ✅ | `Decimal` | `package:decimal` | -| `xsd:double` | ✅ | ❌ | `double` | `dart:core` | +| `xsd:double` | ✅ | ✅ | `double` | `dart:core` | | `xsd:float` | ✅ | ❌ | `double` | `dart:core` | | `xsd:integer` | ✅ | ❌ | `BigInt` | `dart:core` | | `xsd:nonPositiveInteger` | ✅ | ✅ | `BigInt` | `dart:core` | diff --git a/lib/src/codecs/codecs.dart b/lib/src/codecs/codecs.dart index e33555b..a59210e 100644 --- a/lib/src/codecs/codecs.dart +++ b/lib/src/codecs/codecs.dart @@ -26,3 +26,4 @@ export 'duration/xsd_duration_codec.dart'; export 'datetime/xsd_datetime_codec.dart'; export 'any_uri/any_uri_codec.dart'; export 'decimal/decimal_codec.dart'; +export 'double/double_codec.dart'; diff --git a/lib/src/codecs/double/double_codec.dart b/lib/src/codecs/double/double_codec.dart new file mode 100644 index 0000000..6e5e2ef --- /dev/null +++ b/lib/src/codecs/double/double_codec.dart @@ -0,0 +1,42 @@ +import 'dart:convert'; + +import 'package:xsd/src/helpers/whitespace.dart'; + +/// A [Codec] that converts between XSD `double` strings and [double] values. +class XsdDoubleCodec extends Codec { + const XsdDoubleCodec(); + + @override + Converter get encoder => const XsdDoubleEncoder(); + + @override + Converter get decoder => const XsdDoubleDecoder(); +} + +class XsdDoubleEncoder extends Converter { + const XsdDoubleEncoder(); + + @override + String convert(double input) { + if (input.isNaN) return 'NaN'; + if (input == double.infinity) return 'INF'; + if (input == double.negativeInfinity) return '-INF'; + // Dart's toString() might produce scientific notation 1.2e+3, XSD accepts 1.2E3 or 1.2e3. + // XSD canonical form uses 'E'. + return input.toString().toUpperCase(); + } +} + +class XsdDoubleDecoder extends Converter { + const XsdDoubleDecoder(); + + @override + double convert(String input) { + final trimmed = processWhiteSpace(input, Whitespace.collapse); + if (trimmed == 'INF') return double.infinity; + if (trimmed == '-INF') return double.negativeInfinity; + if (trimmed == 'NaN') return double.nan; + + return double.parse(trimmed); + } +} diff --git a/test/codecs/double_codec_test.dart b/test/codecs/double_codec_test.dart new file mode 100644 index 0000000..eada2c7 --- /dev/null +++ b/test/codecs/double_codec_test.dart @@ -0,0 +1,37 @@ +import 'package:test/test.dart'; +import 'package:xsd/xsd.dart'; + +void main() { + group('XsdDoubleCodec', () { + const codec = XsdDoubleCodec(); + + test('should encode valid double', () { + expect(codec.encode(123.45), '123.45'); + expect(codec.encode(-123.45), '-123.45'); + expect(codec.encode(double.infinity), 'INF'); + expect(codec.encode(double.negativeInfinity), '-INF'); + expect(codec.encode(double.nan), 'NaN'); + expect(codec.encode(double.nan), 'NaN'); + }); + + test('should decode valid double', () { + expect(codec.decode('123.45'), 123.45); + expect(codec.decode('-123.45'), -123.45); + expect(codec.decode('INF'), double.infinity); + expect(codec.decode('-INF'), double.negativeInfinity); + expect(codec.decode('NaN').isNaN, isTrue); + expect(codec.decode('1.2E3'), 1200.0); + expect(codec.decode('1.2e3'), 1200.0); + }); + + test('should handle whitespace collapsing', () { + expect(codec.decode(' 123.45 '), 123.45); + expect(codec.decode('\n-123.45\t'), -123.45); + }); + + test('should throw FormatException for invalid input', () { + expect(() => codec.decode('invalid'), throwsFormatException); + expect(() => codec.decode('1.2.3'), throwsFormatException); + }); + }); +}