rdf_core_extend library

RDF Core Extension API

This library provides extension points for building custom RDF codecs and tools that integrate with rdf_core.

For implementers only - if you're working with RDF data, use package:rdf_core/rdf_core.dart instead.

Target Audience

This API is designed for:

  • Developers implementing custom RDF serialization formats
  • Library authors building RDF processing tools
  • Advanced users needing low-level IRI and namespace manipulation
  • Codec implementers extending rdf_core with new formats

What's Included

IRI Processing Utilities

Namespace and Prefix Management

  • IriCompaction - Unified prefix generation and IRI compaction logic
  • IriCompactionResult - Results containing prefixes and compacted IRIs
  • IriRole - Enum for different IRI usage contexts (subject, predicate, etc.)
  • CompactIri and its variants - Type-safe IRI compaction results

Namespace Utilities

Key Features

RFC 3986 Compliant IRI Processing

The IRI utilities ensure roundtrip consistency: resolveIri(relativizeIri(iri, base), base) will always return the original IRI.

Shared Compaction Logic

Both Turtle and JSON-LD encoders use the same IriCompaction system, ensuring consistent prefix generation and namespace handling across formats.

Automatic Prefix Generation

The system can automatically generate meaningful prefixes for unknown namespaces while respecting RDF namespace delimiter conventions.

Example: Custom Codec Implementation

import 'package:rdf_core/rdf_core.dart';
import 'package:rdf_core/rdf_core_extend.dart';

class MyCustomEncoder extends RdfGraphEncoder {
  final RdfNamespaceMappings _namespaceMappings;
  late final IriCompaction _iriCompaction;

  MyCustomEncoder({RdfNamespaceMappings? namespaceMappings})
      : _namespaceMappings = namespaceMappings ?? RdfNamespaceMappings() {
    _iriCompaction = IriCompaction(
      _namespaceMappings,
      IriCompactionSettings(
        generateMissingPrefixes: true,
        useNumericLocalNames: false,
        allowRelativeIriForPredicate: false,
        specialPredicates: {Rdf.type},
        specialDatatypes: {Xsd.string, Xsd.integer},
      ),
    );
  }

  @override
  String convert(RdfGraph graph, {String? baseUri}) {
    // Use the IRI compaction system
    final compactionResult = _iriCompaction.compactAllIris(
      graph,
      {}, // custom prefixes
      baseUri: baseUri,
    );

    final buffer = StringBuffer();

    // Write namespace declarations
    for (final entry in compactionResult.prefixes.entries) {
      buffer.writeln('NAMESPACE ${entry.key}: <${entry.value}>');
    }

    // Process triples using compacted IRIs
    for (final triple in graph.triples) {
      final subject = _renderTerm(triple.subject, IriRole.subject, compactionResult, baseUri);
      final predicate = _renderTerm(triple.predicate, IriRole.predicate, compactionResult, baseUri);
      final object = _renderTerm(triple.object, IriRole.object, compactionResult, baseUri);
      buffer.writeln('$subject $predicate $object .');
    }

    return buffer.toString();
  }

  String _renderTerm(RdfTerm term, IriRole role, IriCompactionResult compaction, String? baseUri) {
    switch (term) {
      case IriTerm iri:
        final compacted = compaction.compactIri(iri, role);
        return switch (compacted) {
          PrefixedIri prefixed => prefixed.colonSeparated,
          RelativeIri relative => '<${relative.relative}>',
          FullIri full => '<${full.iri}>',
          SpecialIri special => '<<special:${special.iri.iri}>>',
          null => '<${iri.iri}>',
        };
      case BlankNodeTerm blankNode:
        return '_:b${blankNode.hashCode.abs()}';
      case LiteralTerm literal:
        return '"${literal.value}"';
    }
  }

  @override
  RdfGraphEncoder withOptions(RdfGraphEncoderOptions options) => this;
}

Example: IRI Utilities Usage

import 'package:rdf_core/rdf_core_extend.dart';

// Relativize IRIs for compact serialization
final baseUri = 'http://example.org/data/';
final absoluteIri = 'http://example.org/data/person/john';
final relative = relativizeIri(absoluteIri, baseUri); // Returns: 'person/john'

// Resolve relative IRIs during parsing
final resolvedBack = resolveIri(relative, baseUri); // Returns original IRI
assert(resolvedBack == absoluteIri);

// Handle base URI requirements
try {
  resolveIri('relative/path', null);
} on BaseIriRequiredException catch (e) {
  print('Cannot resolve ${e.relativeUri} without base URI');
}

Stability and Versioning

This extension API may evolve more rapidly than the main rdf_core API as internal needs change. While we aim for stability, implementers should be prepared for occasional breaking changes in minor versions.

For the most stable API, use the high-level interfaces in the main rdf_core library.

See Also

Classes

CompactIri
FullIri
IriCompaction
Utility class for analyzing RDF graphs and generating namespace prefixes.
IriCompactionResult
IriCompactionSettings
PrefixedIri
RdfNamespaceMappings
A class that provides access to RDF namespace mappings with support for custom mappings.
RelativeIri
SpecialIri

Enums

IriCompactionType
IriRole

Properties

allowedCompactionTypesAll Map<IriRole, Set<IriCompactionType>>
final

Functions

relativizeIri(String iri, String? baseIri) String
Converts an absolute IRI to a relative form when possible.
resolveIri(String iri, String? baseIri) String
Converts a relative IRI to absolute form using a base IRI.

Typedefs

AllowedCompactionTypes = Map<IriRole, Set<IriCompactionType>>
IriFilter = bool Function(IriTerm iri, {required IriRole role})
Function type for filtering IRIs that should be considered for prefix generation.

Exceptions / Errors

BaseIriRequiredException
Exception thrown when a base IRI is required but not provided.