// Package rdf is a small, fast, dependency-free RDF toolkit. It provides the RDF // triple model (Term, Triple, Graph), parsers and serializers for the four common // serializations — RDF/XML, JSON-LD, Turtle and N-Triples — and a streaming // decoder for the line-based formats. // // Two reading modes: // // - Whole document: ParseRDFXML, ParseJSONLD, ParseTurtle and ParseNTriples take // a []byte and return a *Graph. Fast and convenient for inputs that fit in // memory. The line-based parsers have zero-copy variants (ParseNTriplesShared, // ParseNQuadsShared) that back terms with the caller's buffer instead of a // private copy — one input-sized allocation less, for callers that keep the // buffer immutable. // - Streaming: NewDecoder reads N-Triples, N-Quads, RDF/XML or Turtle from an // io.Reader one triple at a time in constant memory, for inputs too large to // materialize (e.g. the multi-gigabyte Library of Congress authority dumps). // JSON-LD is whole-document only. // // A parsed N-Quads document is a Dataset. Reading one of its named graphs has two // modes: Dataset.Graph materializes the graph's triples as a Graph, while // Dataset.GraphView answers the same queries (the GraphQuery surface) over // positions into the dataset's own quads, copying no triple. Prefer the view for // read-only access at corpus scale, where the copy dominates the allocation // profile. // // The parsers target the constructs real-world RDF uses rather than the whole of // each specification; see each parser's documentation for what is and isn't // handled (notably, relative-IRI resolution against a document base is not // performed). There are no third-party dependencies.
package rdf import ( ) // Well-known IRIs. const ( NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" TypeIRI = NS + "type" FirstIRI = NS + "first" RestIRI = NS + "rest" NilIRI = NS + "nil" langString = NS + "langString" XSDString = "http://www.w3.org/2001/XMLSchema#string" ) // Kind distinguishes the three RDF term types. type Kind uint8 const ( IRI Kind = iota Blank Literal ) // Term is an RDF term: an IRI, a blank node, or a literal. type Term struct { Kind Kind Value string // IRI, blank-node identifier, or a literal's lexical form Lang string // literal language tag (Literal only) Datatype string // literal datatype IRI (Literal only) } // NewIRI, NewBlank and NewLiteral construct the three term kinds. func ( string) Term { return Term{Kind: IRI, Value: } } func ( string) Term { return Term{Kind: Blank, Value: } } // NewLiteral makes a literal; an empty datatype defaults per RDF (xsd:string, or // rdf:langString when a language tag is present). func (, , string) Term { if == "" { if != "" { = langString } else { = XSDString } } return Term{Kind: Literal, Value: , Lang: , Datatype: } } // IsIRI, IsBlank and IsLiteral report the term kind. func ( Term) () bool { return .Kind == IRI } func ( Term) () bool { return .Kind == Blank } func ( Term) () bool { return .Kind == Literal } // Triple is an RDF statement. type Triple struct { S, P, O Term } // Graph holds the triples of one RDF graph in document order, with simple // lookup helpers built on first use. Term is comparable, so it indexes by // subject term directly — no key strings. // // Triples is a list, not a set. RDF 1.1 defines a graph as "a set of RDF // triples", but a serialization is free to state the same triple more than // once, and real ones do: LC's own marc2bibframe2 output repeats a shared // agency or vocabulary node under every property that references it, so one of // their N-Triples files carries 449 lines for 389 distinct triples. Parsing // preserves what the document said, which keeps the parse allocation-light and // keeps redundant statements visible to a caller who cares. It also means // len(g.Triples) can exceed the triple count a set-backed parser such as rdflib // reports for the same document. // // The query helpers below hide that: [Graph.Objects] and [Graph.SubjectsOfType] // return each distinct answer once. Callers who want set semantics over the // triples themselves can call [Graph.Dedupe], and [Graph.Canonical] already // collapses duplicates on its way to canonical form. type Graph struct { // Triples are in document order and may repeat; see the type doc. Triples []Triple spo map[Term][]int32 // subject -> positions in Triples, carved from one shared arena } // Dedupe removes duplicate triples in place, keeping the first occurrence of // each and preserving document order among the survivors, and reports how many // it removed. It gives the graph the set semantics RDF 1.1 ascribes to one, at // the cost of a hash of every triple — which is why parsing does not do it. func ( *Graph) () int { if len(.Triples) < 2 { return 0 } := make(map[Triple]struct{}, len(.Triples)) := .Triples[:0] for , := range .Triples { if , := []; { continue } [] = struct{}{} = append(, ) } := len(.Triples) - len() if > 0 { .Triples = .spo = nil // positions shifted; invalidate the index } return } // Add appends a triple. func ( *Graph) (, , Term) { .Triples = append(.Triples, Triple{, , }) .spo = nil // invalidate the index } // index builds the lazy subject index in two passes: count the triples per // subject, then carve each subject's bucket from one shared arena at exactly // its final size. Buckets hold int32 positions into Triples rather than Triple // copies, so a corpus-scale build costs a few bytes per triple instead of the // hundreds that per-subject append growth over copied triples did. (int32 // bounds a graph at 2^31 triples — hundreds of gigabytes of Triple values, // far past in-memory reach.) func ( *Graph) () { if .spo != nil { return } := make(map[Term]int32, len(.Triples)/4+1) for := range .Triples { [.Triples[].S]++ } := make([]int32, 0, len(.Triples)) := make(map[Term][]int32, len()) for := range .Triples { := .Triples[].S , := [] if ! { := len() + int([]) = [len():len():] = [:] } [] = append(, int32()) } .spo = } // Objects returns the distinct objects of every triple with the given subject // and predicate IRI, in document order. A document that states the same triple // twice yields that object once: the answer is a property's value set, not a // count of how often the document repeated itself. func ( *Graph) ( Term, string) []Term { .index() := .spo[] var []Term := objectSet{hint: len()} for , := range { if := &.Triples[]; .P.Kind == IRI && .P.Value == { = .append(, .O) } } return } // objectSetMapAbove is the result length past which objectSet stops scanning // linearly and builds a hash set. A property's value set is almost always a // handful of terms, so the scan wins for every realistic subject; the map only // exists so a pathological subject with thousands of objects cannot go // quadratic. const objectSetMapAbove = 16 // objectSet accumulates the distinct objects of one (subject, predicate) as // they are found, cheaply for the small results that dominate and safely for // the large ones that do not. hint bounds the distinct objects from above -- // the subject's whole index bucket -- and sizes the hash set in one allocation // if the result ever grows into one. The zero value degrades to an unsized map, // so callers should set hint. type objectSet struct { seen map[Term]struct{} hint int } // append adds o to out unless an equal term is already there, returning the // possibly-grown slice. func ( *objectSet) ( []Term, Term) []Term { if .seen != nil { if , := .seen[]; { return } .seen[] = struct{}{} return append(, ) } if slices.Contains(, ) { return } if len() >= objectSetMapAbove { .seen = make(map[Term]struct{}, max(.hint, len()+1)) for , := range { .seen[] = struct{}{} } .seen[] = struct{}{} } return append(, ) } // ObjectsWithRepeats returns the objects of every triple with the given subject // and predicate IRI, statement for statement, in document order -- including a // term the document stated more than once. // // This is the raw list view, and it exists because [Graph.Triples] is a list. // Prefer [Graph.Objects]: a repeated statement carries no information in RDF, so // a caller that treats its multiplicity as meaningful is reading something the // abstract syntax does not say, and will disagree with any set-backed store that // touches the same document. The one honest use is inspecting a serialization as // written -- counting redundant statements, or round-tripping a positional // encoding that a conformant parser would already have flattened. func ( *Graph) ( Term, string) []Term { .index() var []Term for , := range .spo[] { if := &.Triples[]; .P.Kind == IRI && .P.Value == { = append(, .O) } } return } // Object returns the first object for (subject, predicate), or false. It scans the // index without allocating an intermediate slice. func ( *Graph) ( Term, string) (Term, bool) { .index() for , := range .spo[] { if := &.Triples[]; .P.Kind == IRI && .P.Value == { return .O, true } } return Term{}, false } // HasType reports whether the subject has rdf:type typeIRI. It scans the index // without allocating. func ( *Graph) ( Term, string) bool { .index() for , := range .spo[] { if := &.Triples[]; .P.Kind == IRI && .P.Value == TypeIRI && .O.Kind == IRI && .O.Value == { return true } } return false } // Literal returns the value of the subject's first literal object for the // predicate, or false. It scans the index without allocating an intermediate // slice, serving the frequent single-value field reads. func ( *Graph) ( Term, string) (string, bool) { .index() for , := range .spo[] { if := &.Triples[]; .P.Kind == IRI && .P.Value == && .O.Kind == Literal { return .O.Value, true } } return "", false } // SubjectsOfType returns every subject with rdf:type typeIRI, in document order // (deduplicated). func ( *Graph) ( string) []Term { var []Term := map[Term]bool{} for , := range .Triples { if .P.Kind == IRI && .P.Value == TypeIRI && .O.Kind == IRI && .O.Value == { if ![.S] { [.S] = true = append(, .S) } } } return } // LocalName returns the fragment or last path segment of an IRI (the part after // the final '#' or '/'). func ( string) string { if := strings.LastIndexByte(, '#'); >= 0 { return [+1:] } if := strings.LastIndexByte(, '/'); >= 0 { return [+1:] } return }