rdf_core logo

rdf_core

A type-safe, extensible Dart library for RDF data manipulation

pub package build codecov

Type Safety

Strongly-typed IRIs, Literals, Triples, and Graphs ensure correctness and prevent bugs at compile time.

Extensible & Modular

Clean plugin architecture lets you add new codecs with ease.

Blazing Fast

Automatic query optimization with lazy indexing provides O(1) subject-based queries. Zero memory cost until first use, transparent performance improvements for existing code.

Standards-Compliant

Implements W3C RDF 1.1 including datasets with named graphs and related specs for maximum interoperability.

Dataset Support

Full RDF 1.1 dataset support with named graphs, N-Quads format, and seamless quad/triple conversion.

πŸš€ Quick Start

import 'package:rdf_core/rdf_core.dart';

// Create a graph manually
final graph = RdfGraph(triples: [
  Triple(
    const IriTerm('https://example.org/subject'),
    const IriTerm('https://example.org/predicate'),
    LiteralTerm.string('Hello, World!'),
  ),
]);

// Use global convenience variables
final turtleData = '@prefix ex:  . ex:subject ex:predicate "Hello, World!" .';
final graphFromTurtle = turtle.decode(turtleData);

final jsonLdData = '{"@id": "http://example.org/subject", "http://example.org/predicate": "Hello, World!"}';
final graphFromJsonLd = jsonldGraph.decode(jsonLdData);

// Or use the pre-configured RdfCore instance
final rdfGraph = rdf.decode(turtleData, contentType: 'text/turtle');

// New: Query and filter with automatic performance optimization
if (graph.hasTriples(subject: const IriTerm('https://example.org/subject'))) {
  print('Found subject in graph!');
}

// New: Create filtered graphs for composition workflows
final filtered = graph.matching(predicate: const IriTerm('https://example.org/predicate'));
final combined = filtered.merge(otherGraph);

πŸ’‘ Decode and Encode

// Decode and encode Turtle using the global convenience variable
final doc = '''
  @prefix ex:  .
  @prefix foaf:  .

  ex:alice foaf:knows ex:bob;
    foaf:name "Alice" .
  ex:bob foaf:name "Bob" .
''';
final parsed = turtle.decode(doc);
final serialized = turtle.encode(parsed);

// Alternatively, use the pre-configured RdfCore instance
final rdf = RdfCore.withStandardCodecs();
final parsed2 = rdf.decode(doc, contentType: 'text/turtle');
final serialized2 = rdf.encode(parsed2, contentType: 'text/turtle');

// Outputs nearly the same as doc, but we did not provide customPrefixes for the encoder so this time we get the full IRIs for example.com.
// Note that well known prefixes like foaf: are used automatically.
// If we wanted exactly the same, we would have to call it like this:
// final serialized = turtle.encode(parsed, customPrefixes: {'ex': 'http://example.org/'} );
print(serialized); 

πŸ”§ Non-Standard Turtle RDF Support

// Decode documents with non-standard Turtle syntax
import 'package:rdf_core/rdf_core.dart';

// Configure a TurtleCodec with specific parsing flags
final turtleCodec = TurtleCodec(
  decoderOptions: TurtleDecoderOptions(
    parsingFlags: {
      TurtleParsingFlag.allowDigitInLocalName,        // Allow local names with digits (e.g., "resource123")
      TurtleParsingFlag.allowMissingDotAfterPrefix,   // Allow prefix declarations without trailing dot
      TurtleParsingFlag.allowIdentifiersWithoutColon, // Treat terms without colon as IRIs resolved against base URI
      TurtleParsingFlag.allowMissingFinalDot,         // Allow missing dot at end of triple
    }
  )
);

// Create an RDF Core instance with the custom codec
final rdf = RdfCore.withCodecs(codecs: [turtleCodec]);

// Decode a non-standard document that would fail with strict parsing
final nonStandardTurtle = '''
  @base  .
  @prefix ex:  // Missing dot after prefix declaration
  ex:resource123 a Type . // Digit in local name (123) and "Type" without prefix resolves to 
''';

final graph = rdf.decode(nonStandardTurtle, contentType: 'text/turtle');

πŸ”„ N-Triples Format

// Decode and encode N-Triples using the global convenience variable
final ntriplesDoc = '''
    "Alice"@en .
     .
    "Bob" .
''';

// Option 1: Use the global convenience variable
final graph = ntriples.decode(ntriplesDoc);
final encodedNTriples = ntriples.encode(graph);

// Option 2: Use the pre-configured RdfCore instance
final rdf = RdfCore.withStandardCodecs();
// Decode N-Triples explicitly by content type
final graph2 = rdf.decode(ntriplesDoc, contentType: 'application/n-triples');

// Or let the library auto-detect the format
final autoDetected = rdf.decode(ntriplesDoc);

// Encode to N-Triples
final encodedNTriples2 = rdf.encode(graph2, contentType: 'application/n-triples');
print(encodedNTriples2);

// Convert between formats - decode N-Triples and encode to Turtle
final encodedTurtle = rdf.encode(graph2, contentType: 'text/turtle');
print(encodedTurtle);

πŸ—‚οΈ N-Quads & RDF Datasets

// Create and work with RDF datasets containing named graphs
import 'package:rdf_core/rdf_core.dart';

// Create quads with graph context
final alice = const IriTerm('http://example.org/alice');
final bob = const IriTerm('http://example.org/bob');
final foafName = const IriTerm('http://xmlns.com/foaf/0.1/name');
final foafKnows = const IriTerm('http://xmlns.com/foaf/0.1/knows');
final peopleGraph = const IriTerm('http://example.org/graphs/people');

final quads = [
  Quad(alice, foafName, LiteralTerm.string('Alice')), // default graph
  Quad(alice, foafKnows, bob, peopleGraph), // named graph
  Quad(bob, foafName, LiteralTerm.string('Bob'), peopleGraph), // named graph
];

// Create dataset from quads
final dataset = RdfDataset.fromQuads(quads);

// Option 1: Use the global convenience variable
final nquadsData = nquads.encode(dataset);

// Option 2: Use the pre-configured RdfCore instance
final rdf = RdfCore.withStandardCodecs();
final nquadsData2 = rdf.encodeDataset(dataset, contentType: 'application/n-quads');

print('N-Quads output:\n$nquadsData');

// Decode N-Quads data back to dataset
final decodedDataset = nquads.decode(nquadsData);

// Access default and named graphs
print('Default graph has ${decodedDataset.defaultGraph.triples.length} triples');
print('Dataset has ${decodedDataset.namedGraphs.length} named graphs');

πŸ”— Graph Composition & Performance

// Automatic query optimization with lazy indexing
import 'package:rdf_core/rdf_core.dart';

final john = const IriTerm('http://example.org/john');
final jane = const IriTerm('http://example.org/jane');
final foafName = const IriTerm('http://xmlns.com/foaf/0.1/name');
final foafKnows = const IriTerm('http://xmlns.com/foaf/0.1/knows');

final graph = RdfGraph(triples: [
  Triple(john, foafName, LiteralTerm.string('John Smith')),
  Triple(john, foafKnows, jane),
  Triple(jane, foafName, LiteralTerm.string('Jane Doe')),
]);

// Efficient boolean queries - O(1) with automatic indexing
if (graph.hasTriples(subject: john, predicate: foafName)) {
  print('John has a name'); // βœ… Fast lookup
}

// Create filtered graphs for composition
final johnGraph = graph.matching(subject: john);          // John's info
final relationships = graph.matching(predicate: foafKnows); // All relationships

// Chain operations for powerful workflows
final result = graph
  .matching(subject: john)      // Start with John's data
  .merge(additionalData)        // Add more information
  .matching(predicate: foafKnows); // Filter to relationships only

// Performance: First query builds index, subsequent queries are O(1)
final nameTriples = graph.findTriples(subject: john); // Index created here
final hasName = graph.hasTriples(subject: john);      // Uses existing index - very fast!

πŸ“Š JSON-LD Format

// Decode and encode JSON-LD using the global convenience variable
final jsonLdDoc = '''
{
  "@context": {
    "name": "http://xmlns.com/foaf/0.1/name",
    "knows": {
      "@id": "http://xmlns.com/foaf/0.1/knows",
      "@type": "@id"
    },
    "Person": "http://xmlns.com/foaf/0.1/Person"
  },
  "@id": "http://example.org/alice",
  "@type": "Person",
  "name": "Alice",
  "knows": [
    {
      "@id": "http://example.org/bob",
      "@type": "Person",
      "name": "Bob"
    }
  ]
}
''';

// Option 1: Use the global convenience variable
final graph = jsonldGraph.decode(jsonLdDoc);
final encodedJsonLd = jsonldGraph.encode(graph);

// Option 2: Use the pre-configured RdfCore instance
final rdf = RdfCore.withStandardCodecs();
// Decode JSON-LD by content type
final graph2 = rdf.decode(jsonLdDoc, contentType: 'application/ld+json');

// Encode back to JSON-LD
final encodedJsonLd2 = rdf.encode(graph2, contentType: 'application/ld+json');
print(encodedJsonLd2);

// Convert between formats - decode JSON-LD and encode to Turtle
final encodedTurtle = rdf.encode(graph2, contentType: 'text/turtle');
print(encodedTurtle);

βš–οΈ RDF Dataset Canonicalization

For full RDF Dataset Canonicalization (RDF-CANON) compliance, use the separate rdf_canonicalization package.

dart pub add rdf_canonicalization
import 'package:rdf_core/rdf_core.dart';
import 'package:rdf_canonicalization/rdf_canonicalization.dart';

// Create a dataset with blank nodes
final dataset = RdfDataset.fromQuads([
  Quad(BlankNodeTerm(), const IriTerm('http://example.org/name'), LiteralTerm.string('Alice')),
  Quad(BlankNodeTerm(), const IriTerm('http://example.org/name'), LiteralTerm.string('Bob')),
]);

// Canonicalize the dataset according to RDF-CANON spec
final canonicalNQuads = canonicalize(dataset);
print('Canonical N-Quads:\n$canonicalNQuads');

// Or canonicalize a single graph
final graph = RdfGraph(triples: [
  Triple(BlankNodeTerm(), const IriTerm('http://example.org/name'), LiteralTerm.string('Example')),
]);

final canonicalGraph = canonicalizeGraph(graph);
print('Canonical graph:\n$canonicalGraph');

// Test if two datasets are semantically equivalent
final dataset2 = RdfDataset.fromQuads([
  Quad(BlankNodeTerm(), const IriTerm('http://example.org/name'), LiteralTerm.string('Bob')),
  Quad(BlankNodeTerm(), const IriTerm('http://example.org/name'), LiteralTerm.string('Alice')),
]);
final isEquivalent = isIsomorphic(dataset, dataset2);
print('Are datasets equivalent? $isEquivalent');

Note: The canonical option in this library's N-Quads and N-Triples encoders provides basic deterministic output but does not implement the full RDF Dataset Canonicalization specification. For complete RDF-CANON compliance (including proper blank node canonicalization), use the rdf_canonicalization package.

πŸ—ΊοΈ API Overview

Type Description
IriTerm Represents an IRI (Internationalized Resource Identifier)
LiteralTerm Represents an RDF literal value
BlankNodeTerm Represents a blank node
Triple Atomic RDF statement (subject, predicate, object)
Quad RDF statement with optional graph context (subject, predicate, object, graph)
RdfGraph Collection of RDF triples with automatic query optimization
RdfDataset Collection of named graphs plus a default graph
RdfNamedGraph A named graph pair (name + graph)
hasTriples() Check if matching triples exist (boolean result, O(1) optimized)
matching() Create filtered graphs for composition and chaining workflows
RdfGraphCodec Base class for decoding/encoding RdfGraph in various formats
RdfDatasetCodec Base class for decoding/encoding RdfDataset in various formats
RdfGraphDecoder Base class for decoding RdfGraph
RdfGraphEncoder Base class for encoding RdfGraph
RdfDatasetDecoder Base class for decoding RdfDataset
RdfDatasetEncoder Base class for encoding RdfDataset
turtle Global convenience variable for Turtle format
jsonldGraph Global convenience variable for JSON-LD format
ntriples Global convenience variable for N-Triples format
nquads Global convenience variable for N-Quads format
rdf Global RdfCore instance with standard codecs