resolveIri function
Converts a relative IRI to absolute form using a base IRI.
Takes a potentially relative iri and resolves it against baseIri to
produce an absolute IRI. This is the inverse operation of relativizeIri.
If iri is already absolute (contains a scheme like 'http:'), it's
returned unchanged regardless of baseIri.
Throws BaseIriRequiredException if iri is relative but baseIri
is null or empty.
Examples:
resolveIri('file.txt', 'http://example.org/path/')
// Returns: 'http://example.org/path/file.txt'
resolveIri('#section', 'http://example.org/document')
// Returns: 'http://example.org/document#section'
resolveIri('http://other.org/file', 'http://example.org/')
// Returns: 'http://other.org/file' (unchanged - already absolute)
The function uses Dart's built-in Uri.resolveUri when possible, falling back to manual resolution for edge cases.
Implementation
String resolveIri(String iri, String? baseIri) {
if (_isAbsoluteUri(iri)) {
return iri;
}
if (baseIri == null || baseIri.isEmpty) {
throw BaseIriRequiredException(relativeUri: iri);
}
// For query/fragment-only and empty references, RFC 3986 keeps the base path
// as-is (no dot-segment normalization).
if (iri.isEmpty || iri.startsWith('?') || iri.startsWith('#')) {
return _resolveSimpleReferencePreservingBasePath(iri, baseIri);
}
if (_baseEndsWithDotSegment(baseIri) && _isRelativePathReference(iri)) {
return _resolveRelativePathReferenceAgainstRawBase(iri, baseIri);
}
// Reject IRIs with empty scheme (e.g. "://invalid") — these are invalid
// per RFC 3986 as both absolute URIs and relative references.
if (iri.startsWith('://')) {
throw FormatException('Invalid empty scheme', iri, 0);
}
try {
final base = Uri.parse(baseIri);
// Pre-process: Dart's Uri.parse treats `.?q` and `.#f` as a single path
// segment instead of recognizing `?`/`#` as delimiters after `.`.
// Fix by resolving `.` separately and reattaching query/fragment.
final dotRef = _extractDotReference(iri);
if (dotRef != null) {
final resolvedDot = base.resolveUri(Uri.parse(dotRef.path));
return '$resolvedDot${dotRef.suffix}';
}
final resolved = base.resolveUri(Uri.parse(iri));
var result = resolved.toString();
// Post-process: Dart adds a trailing `/` to authority-only references
// (`//auth`) for file: scheme. RFC 3986 says the path should be empty.
if (iri.startsWith('//') && !iri.contains('/./') && !iri.endsWith('/')) {
final afterAuth = iri.substring(2);
// Pure authority reference: no path component after authority
if (!afterAuth.contains('/')) {
result = result.replaceFirst(RegExp(r'/$'), '');
}
}
return result;
} catch (e) {
// Fall back to manual resolution if URI parsing fails
return _manualResolveUri(iri, baseIri);
}
}