Source File
quad.go
Belonging Package
github.com/freeeve/libcodex/rdf
package rdfimport ()// Quad is an RDF statement in a named graph: a triple plus the graph it belongs// to. A zero-value graph term (G) denotes the default graph, so a Quad is a// strict superset of a Triple.type Quad struct {S, P, O, G Term}// Triple projects the quad onto its statement, dropping the graph term.func ( Quad) () Triple { return Triple{.S, .P, .O} }// Dataset holds quads — RDF statements each tagged with the graph they belong// to — in document order. It is the quad-level analogue of Graph and what// ParseNQuads returns; it is how provenance (which source a statement came// from) is carried.//// Like [Graph.Triples], Quads is a list and may repeat a statement the document// stated twice; see the Graph doc for why parsing preserves that.type Dataset struct {// Quads are in document order and may repeat; see the type doc.Quads []Quadcounts []graphCount // lazy statement count per graph term, built in one passcountsGen int // len(Quads) when counts was built, to detect appends}// graphCount is one graph term's statement count. The counts are a slice, not a// map: a dataset carries a handful of provenance graphs, so a linear scan over a// few entries beats hashing a Term (three strings) for every quad in the dataset.type graphCount struct {g Termn int}// Add appends a quad.func ( *Dataset) (, , , Term) { .Quads = append(.Quads, Quad{, , , }) }// graphCounts returns the statement count of every graph term, built lazily in a// single pass and shared by every view of this dataset. One pass answers how big// each graph is, so code reading several graphs — or probing for an empty one —// need not scan the dataset once per graph.func ( *Dataset) () []graphCount {if .counts != nil && .countsGen == len(.Quads) {return .counts}:= make([]graphCount, 0, 4):= 0 // quads of one graph cluster, so the previous hit usually hits againfor := range .Quads {:= .Quads[].Gif < len() && [].g == {[].n++continue}:= falsefor := range {if [].g == {[].n++, = , truebreak}}if ! {= append(, graphCount{, 1})= len() - 1}}.counts, .countsGen = , len(.Quads)return}// GraphLen returns the number of statements in the given graph term; pass a// zero-value term for the default graph. It reads a cached per-graph count, so// after the first call on a dataset it costs no scan — asking about several// graphs, or about a graph that turns out to be empty, is one pass in total// rather than one pass per graph.func ( *Dataset) ( Term) int {for , := range .graphCounts() {if .g == {return .n}}return 0}// HasGraph reports whether the dataset carries any statement in the given graph// term. Like GraphLen it answers from the cached counts rather than scanning, so// skipping an absent graph is free.func ( *Dataset) ( Term) bool { return .GraphLen() > 0 }// Graph returns the triples belonging to the given graph term as a Graph; pass a// zero-value term for the default graph. It copies one Triple per matching quad,// so a caller that only reads the result should use GraphView instead, which// answers the same queries over the dataset's quads without copying.func ( *Dataset) ( Term) *Graph {:= &Graph{}for , := range .Quads {if .G == {.Triples = append(.Triples, .Triple())}}return}// Graphs returns the distinct graph terms present in the dataset, in first-seen// order — the set of provenance sources. It reads the same cached counts GraphLen// does, which record each graph on first sight, so repeated calls cost no scan.func ( *Dataset) () []Term {:= .graphCounts()if len() == 0 {return nil}:= make([]Term, len())for , := range {[] = .g}return}// ---- serialization ----// Encoder serializes a sequence of graphs into one document — N-Triples,// N-Quads or Turtle — keeping blank-node labels unique across the graphs it// writes. Blank-node labels are scoped to the whole document, so a single// Encoder must be reused for every graph written to one stream; a fresh encoder// per graph would let graphs that each number their blanks from scratch (such as// one BIBFRAME graph per record) collide and merge. It is the serialization// counterpart to Decoder; the whole-graph Graph.NTriples, Graph.NQuads and// Graph.Turtle methods each use a fresh Encoder internally.type Encoder struct {bn blankNamer}// AppendQuad appends one quad to b as an N-Quads line. A default-graph quad// (zero-value G) is written as a three-term N-Triples line.func ( *Encoder) ( []byte, Quad) []byte {= appendNTTerm(, .S, &.bn)= append(, ' ')= appendNTTerm(, .P, &.bn)= append(, ' ')= appendNTTerm(, .O, &.bn)if .G != (Term{}) { // omit the graph term for the default graph= append(, ' ')= appendNTTerm(, .G, &.bn)}return append(, ' ', '.', '\n')}// AppendNQuads appends every triple of g to b as N-Quads tagged with the graph// term — g's named graph / provenance. A zero-value graph term writes the// default graph (plain N-Triples). Each call is a fresh blank-node scope, so// graphs that label their blanks from scratch never merge, while their output// labels stay unique across the whole document.func ( *Encoder) ( []byte, *Graph, Term) []byte {.bn.newScope():= 0for , := range .Triples {+= quadBytes(.S, .P, .O, )}= slices.Grow(, ) // one growth instead of the log(n) of an empty startfor , := range .Triples {= .AppendQuad(, Quad{.S, .P, .O, })}return}// AppendNTriples appends g's triples to b as N-Triples in a fresh blank-node// scope — N-Triples being N-Quads restricted to the default graph.func ( *Encoder) ( []byte, *Graph) []byte {return .AppendNQuads(, , Term{})}// quadBytes estimates the serialized N-Quads byte length of one statement, used// to pre-size output buffers. It ignores escaping, so the count is a slight// undercount that removes the reallocations of growing a buffer from empty.func quadBytes(, , , Term) int {:= 14 + len(.Value) + len(.Value) + len(.Value) // brackets, spaces, " .\n"if .Kind == Literal {+= len(.Lang) + len(.Datatype) + 6}if != (Term{}) {+= len(.Value) + 3}return}// NQuads serializes the dataset as an N-Quads document.func ( *Dataset) () []byte {var Encoder:= 0for , := range .Quads {+= quadBytes(.S, .P, .O, .G)}:= make([]byte, 0, )for , := range .Quads {= .AppendQuad(, )}return}// NQuads serializes the graph's triples as N-Quads, each tagged with the given// graph term (its named graph / provenance). A zero-value graph term produces// plain N-Triples.func ( *Graph) ( Term) []byte {var Encoderreturn .AppendNQuads(nil, , )}// ---- N-Quads parsing ----// ParseNQuads parses an N-Quads document into a Dataset, preserving each// statement's graph term; lines carrying only three terms fall in the default// graph. It shares the N-Triples term reader, so the same escaping and leniency// apply — blank, comment and malformed lines are skipped. One private copy of// the input backs every term, so data is free for reuse once it returns.func ( []byte) (*Dataset, error) {return parseNQuads(string())}// ParseNQuadsShared is ParseNQuads without the private input copy: terms are// zero-copy views into data itself, saving one input-sized allocation — worth// it when read-heavy consumers parse corpus-scale documents. In exchange the// caller must not modify data while the Dataset, or any Graph or Term derived// from it, remains in use.func ( []byte) (*Dataset, error) {return parseNQuads(bytesView())}func parseNQuads( string) (*Dataset, error) {:= &Dataset{Quads: make([]Quad, 0, strings.Count(, "\n")+1)}var arena:= 0for := range strings.SplitSeq(, "\n") {++switch , := parseNQuadLine(, &); {case lineStatement:.Quads = append(.Quads, )case lineMalformed:return , &SyntaxError{Line: , Text: strings.TrimSpace()}}}return , nil}
![]() |
The pages are generated with Golds v0.8.7. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |