diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e220ae..1cd4195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,3 +27,4 @@ - Implement `xsd:gMonthDay` codec and data type - Implement `xsd:duration` codec and data type - Implement `xsd:datetime` codec and data type +- Implement `xsd:anyUri` codec \ No newline at end of file diff --git a/README.md b/README.md index 57e17e5..71a2bc4 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ This library aims to support the following XSD 1.1 built-in datatypes that are c | `xsd:unsignedInt` | ✅ | ❌ | `int` | `dart:core` | | `xsd:unsignedShort` | ✅ | ✅ | `int` | `dart:core` | | `xsd:unsignedByte` | ✅ | ✅ | `int` | `dart:core` | -| `xsd:anyURI` | ✅ | ❌ | `Uri` | `dart:core` | +| `xsd:anyURI` | ✅ | ✅ | `Uri` | `dart:core` | | `xsd:hexBinary` | ✅ | ✅ | `Uint8List` | `dart:typed_data` | | `xsd:base64Binary` | ✅ | ✅ | `Uint8List` | `dart:typed_data` | | `xsd:duration` | ✅ | ✅ | `XsdDuration`[1] | `package:xsd` | @@ -71,6 +71,7 @@ This library aims to support the following XSD 1.1 built-in datatypes that are c | `xsd:NMTOKENS` | ❌ | ❌ | ❌ | ❌ | [1] **Note:** Although Dart already has a `Duration` class it actually represents an entirely different concept to the ISO8600 / XSD idea of a duration which `XsdDuration` implements. + [2] **Note:** Dart's native `DateTime` class doesn't preserve the original timezone offset from a parsed string, which is important for round-tripping `xsd:dateTime` values. This wrapper class stores the original offset to address this. ## Limitations diff --git a/lib/src/codecs/any_uri/any_uri_codec.dart b/lib/src/codecs/any_uri/any_uri_codec.dart new file mode 100644 index 0000000..a2acd42 --- /dev/null +++ b/lib/src/codecs/any_uri/any_uri_codec.dart @@ -0,0 +1,31 @@ +import 'dart:convert'; + +import 'package:xsd/src/helpers/whitespace.dart'; + +/// A [Codec] that converts between XSD `anyURI` strings and [Uri] objects. +class XsdAnyUriCodec extends Codec { + const XsdAnyUriCodec(); + + @override + Converter get encoder => const XsdAnyUriEncoder(); + + @override + Converter get decoder => const XsdAnyUriDecoder(); +} + +class XsdAnyUriEncoder extends Converter { + const XsdAnyUriEncoder(); + + @override + String convert(Uri input) => input.toString(); +} + +class XsdAnyUriDecoder extends Converter { + const XsdAnyUriDecoder(); + + @override + Uri convert(String input) { + final trimmed = processWhiteSpace(input, Whitespace.collapse); + return Uri.parse(trimmed); + } +} diff --git a/lib/src/codecs/codecs.dart b/lib/src/codecs/codecs.dart index 33d8a37..ac8d63f 100644 --- a/lib/src/codecs/codecs.dart +++ b/lib/src/codecs/codecs.dart @@ -24,3 +24,4 @@ export 'g_day/g_day_codec.dart'; export 'g_month_day/gregorian_month_day_codec.dart'; export 'duration/xsd_duration_codec.dart'; export 'datetime/xsd_datetime_codec.dart'; +export 'any_uri/any_uri_codec.dart'; diff --git a/test/codecs/any_uri_test.dart b/test/codecs/any_uri_test.dart new file mode 100644 index 0000000..d3b44d2 --- /dev/null +++ b/test/codecs/any_uri_test.dart @@ -0,0 +1,37 @@ +import 'package:test/test.dart'; +import 'package:xsd/xsd.dart'; + +void main() { + group('XsdAnyUriCodec', () { + const codec = XsdAnyUriCodec(); + + test('should encode valid URI', () { + expect( + codec.encode(Uri.parse('http://example.com')), + 'http://example.com', + ); + expect( + codec.encode(Uri.parse('urn:isbn:1234567890')), + 'urn:isbn:1234567890', + ); + }); + + test('should decode valid URI', () { + expect( + codec.decode('http://example.com'), + Uri.parse('http://example.com'), + ); + expect( + codec.decode(' http://example.com '), + Uri.parse('http://example.com'), + ); + }); + + test('should throw FormatException for invalid URI', () { + expect( + () => codec.decode('http://example.com::'), + throwsA(isA()), + ); + }); + }); +}