rdf

package
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 16, 2022 License: BSD-3-Clause Imports: 19 Imported by: 7

Documentation

Overview

Package rdf implements decoding the RDF 1.1 N-Quads line-based plain text format for encoding an RDF dataset. N-Quad parsing is performed as defined by http://www.w3.org/TR/n-quads/

Example (Graph)
package main

import (
	"fmt"
	"log"
	"os"
	"strings"
	"text/tabwriter"

	"gonum.org/v1/gonum/graph"
	"gonum.org/v1/gonum/graph/encoding"
	"gonum.org/v1/gonum/graph/encoding/dot"
	"gonum.org/v1/gonum/graph/formats/rdf"
	"gonum.org/v1/gonum/graph/multi"
)

// dotNode implements graph.Node and dot.Node to allow the
// RDF term value to be given to the DOT encoder.
type dotNode struct {
	rdf.Term
}

func (n dotNode) DOTID() string { return n.Term.Value }

// dotLine implements graph.Line and encoding.Attributer to
// allow the line's RDF term value to be given to the DOT
// encoder and for the nodes to be shimmed to the dotNode
// type.
//
// Because the graph here is directed and we are not performing
// any line reversals, it is safe not to implement the
// ReversedLine method on dotLine; it will never be called.
type dotLine struct {
	*rdf.Statement
}

func (l dotLine) From() graph.Node { return dotNode{l.Subject} }
func (l dotLine) To() graph.Node   { return dotNode{l.Object} }

func (l dotLine) Attributes() []encoding.Attribute {
	return []encoding.Attribute{{Key: "label", Value: l.Predicate.Value}}
}

func main() {
	const statements = `
_:alice <http://xmlns.com/foaf/0.1/knows> _:bob .
_:alice <http://xmlns.com/foaf/0.1/givenName> "Alice" .
_:alice <http://xmlns.com/foaf/0.1/familyName> "Smith" .
_:bob <http://xmlns.com/foaf/0.1/knows> _:alice .
_:bob <http://xmlns.com/foaf/0.1/givenName> "Bob" .
_:bob <http://xmlns.com/foaf/0.1/familyName> "Smith" .
`

	// Decode the statement stream and insert the lines into a multigraph.
	g := multi.NewDirectedGraph()
	dec := rdf.NewDecoder(strings.NewReader(statements))
	for {
		l, err := dec.Unmarshal()
		if err != nil {
			break
		}

		// Wrap the line with a shim type to allow the RDF values
		// to be passed to the DOT marshaling routine.
		g.SetLine(dotLine{l})
	}

	// Marshal the graph into DOT.
	b, err := dot.MarshalMulti(g, "smiths", "", "\t")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n\n", b)

	// Get the ID look-up table.
	w := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0)
	fmt.Fprintln(w, "Term\tID")
	for t, id := range dec.Terms() {
		fmt.Fprintf(w, "%s\t%d\n", t, id)
	}
	w.Flush()

}
Output:


digraph smiths {
	// Node definitions.
	"_:alice";
	"_:bob";
	"Alice";
	"Smith";
	"Bob";

	// Edge definitions.
	"_:alice" -> "_:bob" [label=<http://xmlns.com/foaf/0.1/knows>];
	"_:alice" -> "Alice" [label=<http://xmlns.com/foaf/0.1/givenName>];
	"_:alice" -> "Smith" [label=<http://xmlns.com/foaf/0.1/familyName>];
	"_:bob" -> "_:alice" [label=<http://xmlns.com/foaf/0.1/knows>];
	"_:bob" -> "Smith" [label=<http://xmlns.com/foaf/0.1/familyName>];
	"_:bob" -> "Bob" [label=<http://xmlns.com/foaf/0.1/givenName>];
}

Term                                   ID
_:alice                                1
_:bob                                  2
<http://xmlns.com/foaf/0.1/knows>      3
"Alice"                                4
<http://xmlns.com/foaf/0.1/givenName>  5
"Smith"                                6
<http://xmlns.com/foaf/0.1/familyName> 7
"Bob"                                  8

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalid        = errors.New("invalid N-Quad")
	ErrIncomplete     = errors.New("incomplete N-Quad")
	ErrInvalidTerm    = errors.New("invalid term")
	ErrIncompleteTerm = errors.New("incomplete term")
)

Functions

func ConnectedByAny

func ConnectedByAny(e graph.Edge, with func(*Statement) bool) bool

ConnectedByAny is a helper function to for simplifying graph traversal conditions.

func IsoCanonicalHashes

func IsoCanonicalHashes(statements []*Statement, decomp, dist bool, h hash.Hash, zero []byte) (hashes map[string][]byte, terms map[string]map[string]bool)

IsoCanonicalHashes returns a mapping between the nodes of the RDF graph dataset described by the given statements using the provided hash function. If decomp is true, the graphs are decomposed before hashing. If dist is true the input graph is decomposed into identical splits, the entire graph will be hashed to distinguish nodes. If decomp is false, dist has no effect. Blank node hashes are initially set to the value of zero. Hash values are provided for literal and IRI nodes as well as for blank node. The hash input for literal nodes includes the quotes and the input for IRI nodes first removes the angle quotes around the IRI, although these are included in the map keys.

Note that hashes returned by IsoCanonicalHashes with decomp=true are not comparable with hashes returned by IsoCanonicalHashes with decomp=false.

See http://aidanhogan.com/docs/rdf-canonicalisation.pdf for details of the hashing algorithm.

Example (IsomorphicParts)
package main

import (
	"crypto/md5"
	"fmt"
	"os"
	"sort"
	"strings"
	"text/tabwriter"

	"gonum.org/v1/gonum/graph/formats/rdf"
)

func main() {
	for _, statements := range []string{
		`
# Part 1
_:a <ex:q> <ex:p> .
_:b <ex:q> <ex:p> .
_:c <ex:p> _:a .
_:d <ex:p> _:b .
_:c <ex:r> _:d .

# Part 2
_:a1 <ex:q> <ex:p> .
_:b1 <ex:q> <ex:p> .
_:c1 <ex:p> _:a1 .
_:d1 <ex:p> _:b1 .
_:c1 <ex:r> _:d1 .
`,
	} {
		// Decode the statement stream.
		dec := rdf.NewDecoder(strings.NewReader(statements))
		var s []*rdf.Statement
		for {
			l, err := dec.Unmarshal()
			if err != nil {
				break
			}
			s = append(s, l)
		}

		// Get the node label to hash look-up table. This time
		// we will decompose the dataset into splits and not
		// distinguish nodes. This will then group nodes from
		// the two isomorphic parts. Otherwise each node in
		// the complete dataset would get a unique hash.
		hashes, _ := rdf.IsoCanonicalHashes(s, true, false, md5.New(), make([]byte, 16))

		// Get all the blank nodes.
		var blanks []string
		for k := range hashes {
			if strings.HasPrefix(k, "_:") {
				blanks = append(blanks, k)
			}
		}
		sort.Strings(blanks)

		if len(blanks) == 0 {
			fmt.Println("No blank nodes.")
		} else {
			w := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0)
			fmt.Fprintln(w, "Node\tHash")
			for _, k := range blanks {
				fmt.Fprintf(w, "%s\t%032x\n", k, hashes[k])
			}
			w.Flush()
		}
		fmt.Println()
	}

}
Output:


Node Hash
_:a  d4db6df055d5611e9d8aa6ea621561d1
_:a1 d4db6df055d5611e9d8aa6ea621561d1
_:b  ad70e47f2b026064c7f0922060512b9a
_:b1 ad70e47f2b026064c7f0922060512b9a
_:c  dafd81e6fa603d3e11c898d631e8673f
_:c1 dafd81e6fa603d3e11c898d631e8673f
_:d  7e318557b09444e88791721becc2a8e7
_:d1 7e318557b09444e88791721becc2a8e7
Example (Isomorphisms)
package main

import (
	"crypto/md5"
	"fmt"
	"os"
	"sort"
	"strings"
	"text/tabwriter"

	"gonum.org/v1/gonum/graph/formats/rdf"
)

func main() {
	for _, statements := range []string{
		`
<https://example.com/1> <https://example.com/2> <https://example.com/3> .
<https://example.com/3> <https://example.com/4> <https://example.com/5> .
`,
		`
_:a <ex:q> <ex:p> .
_:b <ex:q> <ex:p> .
_:c <ex:p> _:a .
_:d <ex:p> _:b .
_:c <ex:r> _:d .
`,
		`
_:a1 <ex:q> <ex:p> .
_:b1 <ex:q> <ex:p> .
_:c1 <ex:p> _:a1 .
_:d1 <ex:p> _:b1 .
_:c1 <ex:r> _:d1 .
`,
		`
# G
<ex:p> <ex:q> _:a .
<ex:p> <ex:q> _:b .
<ex:s> <ex:p> _:a .
<ex:s> <ex:r> _:c .
_:c <ex:p> _:b .

# H
<ex:p> <ex:q> _:d .
<ex:p> <ex:q> _:e .
_:f <ex:p> _:d .
_:f <ex:r> _:g .
_:g <ex:p> _:e .
`,
		`
_:greet <l:is> "hola"@es .
`,
	} {
		// Decode the statement stream.
		dec := rdf.NewDecoder(strings.NewReader(statements))
		var s []*rdf.Statement
		for {
			l, err := dec.Unmarshal()
			if err != nil {
				break
			}
			s = append(s, l)
		}

		// Get the node label to hash look-up table.
		hashes, _ := rdf.IsoCanonicalHashes(s, false, true, md5.New(), make([]byte, 16))

		// Get all the blank nodes.
		var blanks []string
		for k := range hashes {
			if strings.HasPrefix(k, "_:") {
				blanks = append(blanks, k)
			}
		}
		sort.Strings(blanks)

		if len(blanks) == 0 {
			fmt.Println("No blank nodes.")
		} else {
			w := tabwriter.NewWriter(os.Stdout, 0, 4, 1, ' ', 0)
			fmt.Fprintln(w, "Node\tHash")
			for _, k := range blanks {
				fmt.Fprintf(w, "%s\t%032x\n", k, hashes[k])
			}
			w.Flush()
		}
		fmt.Println()
	}

}
Output:


No blank nodes.

Node Hash
_:a  d4db6df055d5611e9d8aa6ea621561d1
_:b  ad70e47f2b026064c7f0922060512b9a
_:c  dafd81e6fa603d3e11c898d631e8673f
_:d  7e318557b09444e88791721becc2a8e7

Node Hash
_:a1 d4db6df055d5611e9d8aa6ea621561d1
_:b1 ad70e47f2b026064c7f0922060512b9a
_:c1 dafd81e6fa603d3e11c898d631e8673f
_:d1 7e318557b09444e88791721becc2a8e7

Node Hash
_:a  44ad49b6df3aea91ddbcef932c93e3b4
_:b  ba3ffd8b271a8545b1a3a9042e75ce4b
_:c  34e1bd90b6758b4a766e000128caa6a6
_:d  eb2a47c1032f623647d0497a2ff74052
_:e  1d9ed02f28d87e555feb904688bc2449
_:f  d3b00d36ea503dcc8d234e4405feab81
_:g  55127e4624c0a4fe5990933a48840af8

Node    Hash
_:greet 0d9ba18a037a3fa67e46fce821fe51b4

func Isomorphic

func Isomorphic(a, b []*Statement, decomp bool, h hash.Hash) bool

Isomorphic returns whether the RDF graph datasets a and b are isomorphic, where there is a bijective mapping between blank nodes in a and b using the given hash function. If decomp is true, the graphs are decomposed before canonicalization.

Types

type Decoder

type Decoder struct {
	// contains filtered or unexported fields
}

Decoder is an RDF stream decoder. Statements returned by calls to the Unmarshal method have their Terms' UID fields set so that unique terms will have unique IDs and so can be used directly in a graph.Multi, or in a graph.Graph if all predicate terms are identical. IDs created by the decoder all exist within a single namespace and so Terms can be uniquely identified by their UID. Term UIDs are based from 1 to allow RDF-aware client graphs to assign ID if no ID has been assigned.

func NewDecoder

func NewDecoder(r io.Reader) *Decoder

NewDecoder returns a new Decoder that takes input from r.

func (*Decoder) Reset

func (dec *Decoder) Reset(r io.Reader)

Reset resets the decoder to use the provided io.Reader, retaining the existing Term ID mapping.

func (*Decoder) Terms

func (dec *Decoder) Terms() map[string]int64

Terms returns the mapping between terms and graph node IDs constructed during decoding the RDF statement stream.

func (*Decoder) Unmarshal

func (dec *Decoder) Unmarshal() (*Statement, error)

Unmarshal returns the next statement from the input stream.

type Graph

type Graph struct {
	// contains filtered or unexported fields
}

Graph implements an RDF graph satisfying the graph.Graph and graph.Multigraph interfaces.

Example
package main

import (
	"io"
	"log"
	"os"

	"gonum.org/v1/gonum/graph/formats/rdf"
)

func main() {
	f, err := os.Open("path/to/graph.nq")
	if err != nil {
		log.Fatal(err)
	}

	dec := rdf.NewDecoder(f)
	var statements []*rdf.Statement
	for {
		s, err := dec.Unmarshal()
		if err != nil {
			if err != io.EOF {
				log.Fatalf("error during decoding: %v", err)
			}
			break
		}

		// Statements can be filtered at this point to exclude unwanted
		// or irrelevant parts of the graph.
		statements = append(statements, s)
	}
	f.Close()

	// Canonicalize blank nodes to reduce memory footprint.
	statements, err = rdf.URDNA2015(statements, statements)
	if err != nil {
		log.Fatal(err)
	}

	g := rdf.NewGraph()
	for _, s := range statements {
		g.AddStatement(s)
	}

	// Do something with the graph.
}
Output:

func NewGraph

func NewGraph() *Graph

NewGraph returns a new empty Graph.

func (*Graph) AddStatement

func (g *Graph) AddStatement(s *Statement)

AddStatement adds s to the graph. It panics if Term UIDs in the statement are not consistent with existing terms in the graph. Statements must not be altered while being held by the graph. If the UID fields of the terms in s are zero, they will be set to values consistent with the rest of the graph on return, mutating the parameter, otherwise the UIDs must match terms that already exist in the graph. The statement must be a valid RDF statement otherwise AddStatement will panic.

func (*Graph) AllStatements

func (g *Graph) AllStatements() *Statements

AllStatements returns an iterator of the statements that make up the graph.

func (*Graph) Edge

func (g *Graph) Edge(uid, vid int64) graph.Edge

Edge returns the edge from u to v if such an edge exists and nil otherwise. The node v must be directly reachable from u as defined by the From method. The returned graph.Edge is a multi.Edge if an edge exists.

func (*Graph) Edges

func (g *Graph) Edges() graph.Edges

Edges returns all the edges in the graph. Each edge in the returned slice is a multi.Edge.

func (*Graph) From

func (g *Graph) From(id int64) graph.Nodes

From returns all nodes in g that can be reached directly from n.

The returned graph.Nodes is only valid until the next mutation of the receiver.

func (*Graph) FromSubject

func (g *Graph) FromSubject(t Term) graph.Nodes

FromSubject returns all nodes in g that can be reached directly from an RDF subject term.

The returned graph.Nodes is only valid until the next mutation of the receiver.

func (*Graph) HasEdgeBetween

func (g *Graph) HasEdgeBetween(xid, yid int64) bool

HasEdgeBetween returns whether an edge exists between nodes x and y without considering direction.

func (*Graph) HasEdgeFromTo

func (g *Graph) HasEdgeFromTo(uid, vid int64) bool

HasEdgeFromTo returns whether an edge exists in the graph from u to v.

func (*Graph) Lines

func (g *Graph) Lines(uid, vid int64) graph.Lines

Lines returns the lines from u to v if such any such lines exists and nil otherwise. The node v must be directly reachable from u as defined by the From method.

func (*Graph) Node

func (g *Graph) Node(id int64) graph.Node

Node returns the node with the given ID if it exists in the graph, and nil otherwise.

func (*Graph) Nodes

func (g *Graph) Nodes() graph.Nodes

Nodes returns all the nodes in the graph.

The returned graph.Nodes is only valid until the next mutation of the receiver.

func (*Graph) Predicates

func (g *Graph) Predicates() []Term

Predicates returns a slice of all the predicates used in the graph.

func (*Graph) Query

func (g *Graph) Query(from ...Term) Query

Query returns a query of the receiver starting from the given nodes. Queries may not be mixed between distinct graphs.

func (*Graph) RemoveStatement

func (g *Graph) RemoveStatement(s *Statement)

RemoveStatement removes s from the graph, leaving the terminal nodes if they are part of another statement. If the statement does not exist in g it is a no-op.

func (*Graph) RemoveTerm

func (g *Graph) RemoveTerm(t Term)

RemoveTerm removes t and any statements referencing t from the graph. If the term is a predicate, all statements with the predicate are removed. If the term does not exist it is a no-op.

func (*Graph) Statements

func (g *Graph) Statements(uid, vid int64) *Statements

Statements returns an iterator of the statements that connect the subject term node u to the object term node v.

func (*Graph) TermFor

func (g *Graph) TermFor(text string) (term Term, ok bool)

TermFor returns the Term for the given text. The text must be an exact match for the Term's Value field.

func (*Graph) To

func (g *Graph) To(id int64) graph.Nodes

To returns all nodes in g that can reach directly to n.

The returned graph.Nodes is only valid until the next mutation of the receiver.

func (*Graph) ToObject

func (g *Graph) ToObject(t Term) graph.Nodes

ToObject returns all nodes in g that can reach directly to an RDF object term.

The returned graph.Nodes is only valid until the next mutation of the receiver.

type Kind

type Kind int

Kind represents the kind of an RDF term.

const (
	// Invalid is an invalid RDF term.
	Invalid Kind = iota

	// IRI is the kind of an IRI term.
	// https://www.w3.org/TR/n-quads/#sec-iri
	IRI

	// Literal is the kind of an RDF literal.
	// https://www.w3.org/TR/n-quads/#sec-literals
	Literal

	// Blank is the kind of an RDF blank node term.
	// https://www.w3.org/TR/n-quads/#BNodes
	Blank
)

func (Kind) String

func (i Kind) String() string

type Query

type Query struct {
	// contains filtered or unexported fields
}

Query represents a step in an RDF graph query. The methods on Query provide a simple graph query language.

Example
package main

import (
	"fmt"
	"io"
	"log"
	"strings"

	"gonum.org/v1/gonum/graph/formats/rdf"
)

const gods = `
_:alcmene <l:type> "human" .
_:alcmene <p:name> "Alcmene" .
_:cerberus <a:lives> _:cerberushome .
_:cerberus <l:type> "monster" .
_:cerberus <p:name> "Cerberus" .
_:cerberushome <p:location> _:tartarus .
_:cronos <l:type> "titan" .
_:cronos <p:name> "Cronos" .
_:hades <a:lives> _:hadeshome .
_:hades <h:brother> _:poseidon .
_:hades <h:brother> _:zeus .
_:hades <h:pet> _:cerberus .
_:hades <l:type> "god" .
_:hades <p:name> "Hades" .
_:hadeshome <p:location> _:tartarus .
_:hadeshome <p:reason> "it is peaceful" .
_:heracles <a:battled> _:cerberus .
_:heracles <a:battled> _:hydra .
_:heracles <a:battled> _:nemean .
_:heracles <h:father> _:zeus .
_:heracles <h:mother> _:alcmene .
_:heracles <l:type> "demigod" .
_:heracles <p:name> "Heracles" .
_:hydra <l:type> "monster" .
_:hydra <p:name> "Lernean Hydra" .
_:nemean <l:type> "monster" .
_:nemean <p:name> "Nemean Lion" .
_:olympus <l:type> "location" .
_:olympus <p:name> "Olympus" .
_:poseidon <a:lives> _:poseidonhome .
_:poseidon <h:brother> _:hades .
_:poseidon <h:brother> _:zeus .
_:poseidon <l:type> "god" .
_:poseidon <p:name> "Poseidon" .
_:poseidonhome <p:location> _:sea .
_:poseidonhome <p:reason> "it was given to him" .
_:sea <l:type> "location" .
_:sea <p:name> "Sea" .
_:tartarus <l:type> "location" .
_:tartarus <p:name> "Tartarus" .
_:theseus <a:battled> _:cerberus .
_:theseus <h:father> _:poseidon .
_:theseus <l:type> "human" .
_:theseus <p:name> "Theseus" .
_:zeus <a:lives> _:zeushome .
_:zeus <h:brother> _:hades .
_:zeus <h:brother> _:poseidon .
_:zeus <h:father> _:cronos .
_:zeus <l:type> "god" .
_:zeus <p:name> "Zeus" .
_:zeushome <p:location> _:olympus .
_:zeushome <p:reason> "he can see everything" .
`

func main() {
	g := rdf.NewGraph()
	dec := rdf.NewDecoder(strings.NewReader(gods))
	for {
		s, err := dec.Unmarshal()
		if err != nil {
			if err != io.EOF {
				log.Fatalf("error during decoding: %v", err)
			}
			break
		}
		g.AddStatement(s)
	}

	it := g.Nodes()
	nodes := make([]rdf.Term, 0, it.Len())
	for it.Next() {
		nodes = append(nodes, it.Node().(rdf.Term))
	}

	// Construct a query start point. This can be reused. If a specific
	// node is already known it can be used to reduce the work required here.
	heracles := g.Query(nodes...).In(func(s *rdf.Statement) bool {
		// Traverse in from the name "Heracles".
		return s.Predicate.Value == "<p:name>" && s.Object.Value == `"Heracles"`
	})

	// father and name filter statements on their predicate values. These
	// are used in the queries that follow.
	father := func(s *rdf.Statement) bool {
		// Traverse across <h:father>.
		return s.Predicate.Value == "<h:father>"
	}
	name := func(s *rdf.Statement) bool {
		// Traverse across <p:name>.
		return s.Predicate.Value == "<p:name>"
	}

	// g.V().has('name', 'heracles').out('father').out('father').values('name')
	for _, r := range heracles.
		Out(father). // Traverse out across <h:father> to get to Zeus.
		Out(father). // and again to get to Cronos.
		Out(name).   // Retrieve the name by traversing the <p:name> edges.
		Result() {
		fmt.Printf("Heracles' grandfather: %s\n", r.Value)
	}

	// g.V().has('name', 'heracles').repeat(out('father')).emit().values('name')
	var i int
	heracles.Repeat(func(q rdf.Query) (rdf.Query, bool) {
		q = q.Out(father)
		for _, r := range q.Out(name).Result() {
			fmt.Printf("Heracles' lineage %d: %s\n", i, r.Value)
		}
		i++
		return q, true
	})

	// parents and typ are helper filters for queries below.
	parents := func(s *rdf.Statement) bool {
		// Traverse across <h:father> or <h:mother>
		return s.Predicate.Value == "<h:father>" || s.Predicate.Value == "<h:mother>"
	}
	typ := func(s *rdf.Statement) bool {
		// Traverse across <l:type>.
		return s.Predicate.Value == "<l:type>"
	}

	// g.V(heracles).out('father', 'mother').label()
	for _, r := range heracles.Out(parents).Out(typ).Result() {
		fmt.Printf("Heracles' parents' types: %s\n", r.Value)
	}

	// battled is a helper filter for queries below.
	battled := func(s *rdf.Statement) bool {
		// Traverse across <a:battled>.
		return s.Predicate.Value == "<a:battled>"
	}

	// g.V(heracles).out('battled').label()
	for _, r := range heracles.Out(battled).Out(typ).Result() {
		fmt.Printf("Heracles' antagonists' types: %s\n", r.Value)
	}

	// g.V(heracles).out('battled').valueMap()
	for _, r := range heracles.Out(battled).Result() {
		m := make(map[string]string)
		g.Query(r).Out(func(s *rdf.Statement) bool {
			// Store any p: namespace in the map.
			if strings.HasPrefix(s.Predicate.Value, "<p:") {
				prop := strings.TrimSuffix(strings.TrimPrefix(s.Predicate.Value, "<p:"), ">")
				m[prop] = s.Object.Value
			}
			// But don't store the result into the query.
			return false
		})
		fmt.Println(m)
	}

	// g.V(heracles).as('h').out('battled').in('battled').where(neq('h')).values('name')
	for _, r := range heracles.Out(battled).In(battled).Not(heracles).Out(name).Result() {
		fmt.Printf("Heracles' allies: %s\n", r.Value)
	}

	// Construct a query start point for Hades, this time using a restricted
	// starting set only including the name. It would also be possible to
	// start directly from a query with the term _:hades, but that depends
	// on the blank node identity, which may be altered, for example by
	// canonicalization.
	h, ok := g.TermFor(`"Hades"`)
	if !ok {
		log.Fatal("could not find term for Hades")
	}
	hades := g.Query(h).In(name)

	// g.V(hades).as('x').out('lives').in('lives').where(neq('x')).values('name')
	//
	// This is more complex with RDF since properties are encoded by
	// attachment to anonymous blank nodes, so we take two steps, the
	// first to the blank node for where Hades lives and then the second
	// to get the actual location.
	lives := func(s *rdf.Statement) bool {
		// Traverse across <a:lives>.
		return s.Predicate.Value == "<a:lives>"
	}
	location := func(s *rdf.Statement) bool {
		// Traverse across <p:location>.
		return s.Predicate.Value == "<p:location>"
	}
	for _, r := range hades.Out(lives).Out(location).In(location).In(lives).Not(hades).Out(name).Result() {
		fmt.Printf("Hades lives with: %s\n", r.Value)
	}

	// g.V(hades).out('brother').as('god').out('lives').as('place').select('god', 'place').by('name')
	brother := func(s *rdf.Statement) bool {
		// Traverse across <h:brother>.
		return s.Predicate.Value == "<h:brother>"
	}
	for _, r := range hades.Out(brother).Result() {
		m := make(map[string]string)
		as := func(key string) func(s *rdf.Statement) bool {
			return func(s *rdf.Statement) bool {
				// Store any <p:name> objects in the map.
				if s.Predicate.Value == "<p:name>" {
					m[key] = s.Object.Value
				}
				// But don't store the result into the query.
				return false
			}
		}
		sub := g.Query(r)
		sub.Out(as("god"))
		sub.Out(lives).Out(location).Out(as("place"))
		fmt.Println(m)
	}

	// The query above but with the reason for their choice.
	for _, r := range hades.Out(brother).Result() {
		m := make(map[string]string)
		// as stores the query result under the provided key
		// for m, and if cont is not nil, allows the chain
		// to continue.
		as := func(query, key string, cont func(s *rdf.Statement) bool) func(s *rdf.Statement) bool {
			return func(s *rdf.Statement) bool {
				// Store any objects matching the query in the map.
				if s.Predicate.Value == query {
					m[key] = s.Object.Value
				}
				// Continue with chain if cont is not nil and
				// the statement satisfies its condition.
				if cont == nil {
					return false
				}
				return cont(s)
			}
		}
		sub := g.Query(r)
		sub.Out(as("<p:name>", "god", nil))
		sub.Out(lives).
			Out(as("<p:reason>", "reason", location)).
			Out(as("<p:name>", "place", nil))
		fmt.Println(m)
	}

}
Output:


Heracles' grandfather: "Cronos"
Heracles' lineage 0: "Zeus"
Heracles' lineage 1: "Cronos"
Heracles' parents' types: "god"
Heracles' parents' types: "human"
Heracles' antagonists' types: "monster"
Heracles' antagonists' types: "monster"
Heracles' antagonists' types: "monster"
map[name:"Cerberus"]
map[name:"Lernean Hydra"]
map[name:"Nemean Lion"]
Heracles' allies: "Theseus"
Hades lives with: "Cerberus"
map[god:"Zeus" place:"Olympus"]
map[god:"Poseidon" place:"Sea"]
map[god:"Zeus" place:"Olympus" reason:"he can see everything"]
map[god:"Poseidon" place:"Sea" reason:"it was given to him"]

func NewQuery

func NewQuery(g graph.Directed, from ...Term) Query

NewQuery returns a query of g starting from the given nodes. Queries may not be mixed between distinct graphs. The type of g must be comparable. Query operations only consider edges that are represented by a *Statement or is an edge with lines held in a graph.Lines with at least one *Statement.

func (Query) And

func (q Query) And(p Query) Query

And returns a query that holds the conjunction of q and p.

func (Query) HasAllIn

func (q Query) HasAllIn(fn func(s *Statement) bool) Query

HasAllIn returns a query holding nodes from the receiver's initial set where all incoming statements satisfy fn. The query short circuits, so fn is not called after the first failure to match.

func (Query) HasAllOut

func (q Query) HasAllOut(fn func(s *Statement) bool) Query

HasAllOut returns a query holding nodes from the receiver's initial set where all outgoing statements satisfy fn. The query short circuits, so fn is not called after the first failure to match.

func (Query) HasAnyIn

func (q Query) HasAnyIn(fn func(s *Statement) bool) Query

HasAnyIn returns a query holding nodes from the receiver's initial set where any incoming statements satisfies fn. The query short circuits, so fn is not called after the first match.

func (Query) HasAnyOut

func (q Query) HasAnyOut(fn func(s *Statement) bool) Query

HasAnyOut returns a query holding nodes from the receiver's initial set where any outgoing statements satisfies fn. The query short circuits, so fn is not called after the first match.

func (Query) In

func (q Query) In(fn func(s *Statement) bool) Query

In returns a query holding nodes reachable in from the receiver's starting nodes via statements that satisfy fn.

func (Query) Len

func (q Query) Len() int

Len returns the number of terms held by the query.

func (Query) Not

func (q Query) Not(p Query) Query

Not returns a query that holds q less p.

func (Query) Or

func (q Query) Or(p Query) Query

Or returns a query that holds the disjunction of q and p.

func (Query) Out

func (q Query) Out(fn func(s *Statement) bool) Query

Out returns a query holding nodes reachable out from the receiver's starting nodes via statements that satisfy fn.

func (Query) Repeat

func (q Query) Repeat(fn func(Query) (q Query, ok bool)) Query

Repeat repeatedly calls fn on q until the set of results is empty or ok is false, and then returns the result. If the last non-empty result is wanted, fn should return its input and false when the partial traversal returns an empty result.

result := start.Repeat(func(q rdf.Query) (rdf.Query, bool) {
	r := q.Out(condition)
	if r.Len() == 0 {
		return q, false
	}
	return r, true
}).Result()

func (Query) Result

func (q Query) Result() []Term

Result returns the terms held by the query.

func (Query) Unique

func (q Query) Unique() Query

Unique returns a copy of the receiver that contains only one instance of each term.

type Statement

type Statement struct {
	Subject   Term
	Predicate Term
	Object    Term
	Label     Term
}

Statement is an RDF statement. It implements the graph.Edge and graph.Line interfaces.

func C14n

func C14n(dst, src []*Statement, terms map[string]map[string]bool) ([]*Statement, error)

C14n performs a relabeling of the statements in src based on the terms obtained from IsoCanonicalHashes, placing the results in dst and returning them. The relabeling scheme is the same as for the Universal RDF Dataset Normalization Algorithm, blank terms are ordered lexically by their hash value and then given a blank label with the prefix "_:c14n" and an identifier counter corresponding to the label's sort rank.

If dst is nil, it is allocated, otherwise the length of dst must match the length of src.

Example
package main

import (
	"crypto/md5"
	"fmt"
	"log"
	"strings"

	"gonum.org/v1/gonum/graph/formats/rdf"
)

func main() {
	for _, statements := range []string{
		`
_:a <ex:q> <ex:p> .
_:b <ex:q> <ex:p> .
_:c <ex:p> _:a .
_:d <ex:p> _:b .
_:c <ex:r> _:d .
`,
		`
_:c1 <ex:p> _:a1 .
_:b1 <ex:q> <ex:p> .
_:d1 <ex:p> _:b1 .
_:a1 <ex:q> <ex:p> .
_:c1 <ex:r> _:d1 .
`,
	} {
		// Decode the statement stream.
		dec := rdf.NewDecoder(strings.NewReader(statements))
		var s []*rdf.Statement
		for {
			l, err := dec.Unmarshal()
			if err != nil {
				break
			}
			s = append(s, l)
		}

		// Get the hash to term label look-up table.
		_, terms := rdf.IsoCanonicalHashes(s, false, true, md5.New(), make([]byte, 16))

		relabeled, err := rdf.C14n(nil, s, terms)
		if err != nil {
			log.Fatal(err)
		}

		for _, s := range relabeled {
			fmt.Println(s)
		}
		fmt.Println()
	}

}
Output:


_:c14n0 <ex:p> _:c14n1 .
_:c14n1 <ex:q> <ex:p> .
_:c14n2 <ex:q> <ex:p> .
_:c14n3 <ex:p> _:c14n2 .
_:c14n3 <ex:r> _:c14n0 .

_:c14n0 <ex:p> _:c14n1 .
_:c14n1 <ex:q> <ex:p> .
_:c14n2 <ex:q> <ex:p> .
_:c14n3 <ex:p> _:c14n2 .
_:c14n3 <ex:r> _:c14n0 .

func Deduplicate

func Deduplicate(s []*Statement) []*Statement

Deduplicate removes duplicate statements in s, working in place, and returns the deduplicated slice with statements sorted in lexical order. Term UID fields are not considered and their values may be lost during deduplication.

func Lean

func Lean(g []*Statement) ([]*Statement, error)

Lean returns an RDF core of g that entails g. If g contains any non-zero labels, Lean will return a non-nil error and a core of g assuming no graph labels exist.

See http://aidanhogan.com/docs/rdf-canonicalisation.pdf for details of the algorithm.

Example
package main

import (
	"fmt"
	"strings"

	"gonum.org/v1/gonum/graph/formats/rdf"
)

func main() {
	for i, statements := range []string{
		0: `
_:author1 <ex:contributesTo> _:go .
_:author2 <ex:contributesTo> _:go .
_:author2 <ex:contributesTo> _:gonum .
_:author3 <ex:contributesTo> _:gonum .
`,
		1: `
_:author1 <ex:contributesTo> _:go .
_:author2 <ex:contributesTo> _:go .
_:author2 <ex:contributesTo> _:gonum .
_:author3 <ex:contributesTo> _:gonum .
_:gonum <ex:dependsOn> _:go .
`,
		2: `
_:author1 <ex:contributesTo> _:go .
_:author1 <ex:notContributesTo> _:gonum .
_:author2 <ex:contributesTo> _:go .
_:author2 <ex:contributesTo> _:gonum .
_:author3 <ex:contributesTo> _:gonum .
_:gonum <ex:dependsOn> _:go .
`,
		3: `
_:author1 <ex:is> "Alice" .
_:author1 <ex:contributesTo> _:go .
_:author2 <ex:contributesTo> _:go .
_:author2 <ex:contributesTo> _:gonum .
_:author3 <ex:contributesTo> _:gonum .
_:gonum <ex:dependsOn> _:go .
`,
		4: `
_:author1 <ex:contributesTo> _:go .
_:author1 <ex:notContributesTo> _:gonum .
_:author2 <ex:contributesTo> _:go .
_:author2 <ex:contributesTo> _:gonum .
_:author3 <ex:contributesTo> _:gonum .
_:author3 <ex:notContributesTo> _:go .
_:gonum <ex:dependsOn> _:go .
`,
		5: `
_:author1 <ex:is> "Alice" .
_:author2 <ex:is> "Bob" .
_:author1 <ex:contributesTo> _:go .
_:author2 <ex:contributesTo> _:go .
_:author2 <ex:contributesTo> _:gonum .
_:author3 <ex:contributesTo> _:gonum .
_:gonum <ex:dependsOn> _:go .
`,
		6: `
_:author1 <ex:is> "Alice" .
_:author2 <ex:is> "Bob" .
_:author3 <ex:is> "Charlie" .
_:author1 <ex:contributesTo> _:go .
_:author2 <ex:contributesTo> _:go .
_:author2 <ex:contributesTo> _:gonum .
_:author3 <ex:contributesTo> _:gonum .
_:gonum <ex:dependsOn> _:go .
`,
	} {
		// Decode the statement stream.
		dec := rdf.NewDecoder(strings.NewReader(statements))
		var s []*rdf.Statement
		for {
			l, err := dec.Unmarshal()
			if err != nil {
				break
			}
			s = append(s, l)
		}

		// Lean the graph to remove redundant statements.
		lean, err := rdf.Lean(s)
		if err != nil {
			fmt.Println(err)
		}

		// Canonicalize the blank nodes in-place.
		_, err = rdf.URDNA2015(lean, lean)
		if err != nil {
			fmt.Println(err)
			continue
		}

		fmt.Printf("%d:\n", i)
		for _, s := range lean {
			fmt.Println(s)
		}
		fmt.Println()
	}

}
Output:


0:
_:c14n0 <ex:contributesTo> _:c14n1 .

1:
_:c14n0 <ex:contributesTo> _:c14n1 .
_:c14n0 <ex:contributesTo> _:c14n2 .
_:c14n2 <ex:dependsOn> _:c14n1 .

2:
_:c14n0 <ex:contributesTo> _:c14n1 .
_:c14n0 <ex:contributesTo> _:c14n3 .
_:c14n2 <ex:contributesTo> _:c14n1 .
_:c14n2 <ex:notContributesTo> _:c14n3 .
_:c14n3 <ex:dependsOn> _:c14n1 .

3:
_:c14n0 <ex:contributesTo> _:c14n1 .
_:c14n0 <ex:contributesTo> _:c14n3 .
_:c14n2 <ex:contributesTo> _:c14n1 .
_:c14n2 <ex:is> "Alice" .
_:c14n3 <ex:dependsOn> _:c14n1 .

4:
_:c14n0 <ex:contributesTo> _:c14n1 .
_:c14n0 <ex:contributesTo> _:c14n2 .
_:c14n2 <ex:dependsOn> _:c14n1 .
_:c14n3 <ex:contributesTo> _:c14n1 .
_:c14n3 <ex:notContributesTo> _:c14n2 .
_:c14n4 <ex:contributesTo> _:c14n2 .
_:c14n4 <ex:notContributesTo> _:c14n1 .

5:
_:c14n1 <ex:contributesTo> _:c14n0 .
_:c14n1 <ex:contributesTo> _:c14n3 .
_:c14n1 <ex:is> "Bob" .
_:c14n2 <ex:contributesTo> _:c14n0 .
_:c14n2 <ex:is> "Alice" .
_:c14n3 <ex:dependsOn> _:c14n0 .

6:
_:c14n0 <ex:dependsOn> _:c14n1 .
_:c14n2 <ex:contributesTo> _:c14n0 .
_:c14n2 <ex:contributesTo> _:c14n1 .
_:c14n2 <ex:is> "Bob" .
_:c14n3 <ex:contributesTo> _:c14n1 .
_:c14n3 <ex:is> "Alice" .
_:c14n4 <ex:contributesTo> _:c14n0 .
_:c14n4 <ex:is> "Charlie" .

func ParseNQuad

func ParseNQuad(statement string) (*Statement, error)

ParseNQuad parses the statement and returns the corresponding Statement. All Term UID fields are zero on return.

func URDNA2015

func URDNA2015(dst, src []*Statement) ([]*Statement, error)

URDNA2015 applies the Universal RDF Dataset Normalization Algorithm 2015 to the statements in src, placing the result in dst and returning it. If dst is nil a slice of statements will be allocated. If dst is not nil and not the same length as src, URDNA2015 will return an error.

See https://json-ld.github.io/rdf-dataset-canonicalization/spec/index.html for details.

Example
package main

import (
	"fmt"
	"log"
	"strings"

	"gonum.org/v1/gonum/graph/formats/rdf"
)

func main() {
	for _, statements := range []string{
		`
_:a <ex:q> <ex:p> .
_:b <ex:q> <ex:p> .
_:c <ex:p> _:a .
_:d <ex:p> _:b .
_:c <ex:r> _:d .
`,
		`
_:c1 <ex:p> _:a1 .
_:b1 <ex:q> <ex:p> .
_:d1 <ex:p> _:b1 .
_:a1 <ex:q> <ex:p> .
_:c1 <ex:r> _:d1 .
`,
	} {
		// Decode the statement stream.
		dec := rdf.NewDecoder(strings.NewReader(statements))
		var s []*rdf.Statement
		for {
			l, err := dec.Unmarshal()
			if err != nil {
				break
			}
			s = append(s, l)
		}

		relabeled, err := rdf.URDNA2015(nil, s)
		if err != nil {
			log.Fatal(err)
		}
		for _, s := range relabeled {
			fmt.Println(s)
		}
		fmt.Println()
	}

}
Output:


_:c14n0 <ex:p> _:c14n2 .
_:c14n1 <ex:p> _:c14n3 .
_:c14n1 <ex:r> _:c14n0 .
_:c14n2 <ex:q> <ex:p> .
_:c14n3 <ex:q> <ex:p> .

_:c14n0 <ex:p> _:c14n2 .
_:c14n1 <ex:p> _:c14n3 .
_:c14n1 <ex:r> _:c14n0 .
_:c14n2 <ex:q> <ex:p> .
_:c14n3 <ex:q> <ex:p> .

func URGNA2012

func URGNA2012(dst, src []*Statement) ([]*Statement, error)

URGNA2012 applies the Universal RDF Graph Normalization Algorithm 2012 to the statements in src, placing the result in dst and returning it. If dst is nil a slice of statements will be allocated. If dst is not nil and not the same length as src, URGNA2012 will return an error.

See https://json-ld.github.io/rdf-dataset-canonicalization/spec/index.html for details.

Example
package main

import (
	"fmt"
	"log"
	"strings"

	"gonum.org/v1/gonum/graph/formats/rdf"
)

func main() {
	for _, statements := range []string{
		`
_:a <ex:q> <ex:p> .
_:b <ex:q> <ex:p> .
_:c <ex:p> _:a .
_:d <ex:p> _:b .
_:c <ex:r> _:d .
`,
		`
_:c1 <ex:p> _:a1 .
_:b1 <ex:q> <ex:p> .
_:d1 <ex:p> _:b1 .
_:a1 <ex:q> <ex:p> .
_:c1 <ex:r> _:d1 .
`,
	} {
		// Decode the statement stream.
		dec := rdf.NewDecoder(strings.NewReader(statements))
		var s []*rdf.Statement
		for {
			l, err := dec.Unmarshal()
			if err != nil {
				break
			}
			s = append(s, l)
		}

		relabeled, err := rdf.URGNA2012(nil, s)
		if err != nil {
			log.Fatal(err)
		}
		for _, s := range relabeled {
			fmt.Println(s)
		}
		fmt.Println()
	}

}
Output:


_:c14n0 <ex:p> _:c14n3 .
_:c14n0 <ex:r> _:c14n1 .
_:c14n1 <ex:p> _:c14n2 .
_:c14n2 <ex:q> <ex:p> .
_:c14n3 <ex:q> <ex:p> .

_:c14n0 <ex:p> _:c14n3 .
_:c14n0 <ex:r> _:c14n1 .
_:c14n1 <ex:p> _:c14n2 .
_:c14n2 <ex:q> <ex:p> .
_:c14n3 <ex:q> <ex:p> .

func (*Statement) From

func (s *Statement) From() graph.Node

From returns the subject of the statement.

func (*Statement) ID

func (s *Statement) ID() int64

ID returns the UID of the Predicate field.

func (*Statement) ReversedEdge

func (s *Statement) ReversedEdge() graph.Edge

ReversedEdge returns the receiver unaltered. If there is a semantically valid edge reversal operation for the data, the user should implement this by wrapping Statement in a type performing that operation. See the ReversedLine example for details.

func (*Statement) ReversedLine

func (s *Statement) ReversedLine() graph.Line

ReversedLine returns the receiver unaltered. If there is a semantically valid line reversal operation for the data, the user should implement this by wrapping Statement in a type performing that operation.

Example
package main

import (
	"fmt"
	"log"
	"strings"

	"gonum.org/v1/gonum/graph"
	"gonum.org/v1/gonum/graph/encoding"
	"gonum.org/v1/gonum/graph/encoding/dot"
	"gonum.org/v1/gonum/graph/formats/rdf"
	"gonum.org/v1/gonum/graph/multi"
)

// foodNode implements graph.Node, dot.Node and encoding.Attributer
// to allow the RDF term value to be given to the DOT encoder.
type foodNode struct {
	rdf.Term
}

func (n foodNode) DOTID() string {
	text, _, kind, err := n.Term.Parts()
	if err != nil {
		return fmt.Sprintf("error:%s", n.Term.Value)
	}
	switch kind {
	case rdf.Blank:
		return n.Term.Value
	case rdf.IRI:
		return text
	case rdf.Literal:
		return fmt.Sprintf("%q", text)
	default:
		return fmt.Sprintf("invalid:%s", n.Term.Value)
	}
}

func (n foodNode) Attributes() []encoding.Attribute {
	_, qual, _, err := n.Term.Parts()
	if err != nil {
		return []encoding.Attribute{{Key: "error", Value: err.Error()}}
	}
	if qual == "" {
		return nil
	}
	parts := strings.Split(qual, ":")
	return []encoding.Attribute{{Key: parts[0], Value: parts[1]}}
}

// foodLine implements graph.Line and encoding.Attributer to
// allow the line's RDF term value to be given to the DOT
// encoder and for the nodes to be shimmed to the foodNode
// type.
//
// It also implements line reversal for the semantics of
// a food web with some taxonomic information.
type foodLine struct {
	*rdf.Statement
}

func (l foodLine) From() graph.Node { return foodNode{l.Subject} }
func (l foodLine) To() graph.Node   { return foodNode{l.Object} }
func (l foodLine) ReversedLine() graph.Line {
	if l.Predicate.Value == "<tax:is>" {
		// This should remain unreversed, so return as is.
		return l
	}
	s := *l.Statement
	// Reverse the line end points.
	s.Subject, s.Object = s.Object, s.Subject
	// Invert the semantics of the predicate.
	switch s.Predicate.Value {
	case "<eco:eats>":
		s.Predicate.Value = "<eco:eaten-by>"
	case "<eco:eaten-by>":
		s.Predicate.Value = "<eco:eats>"
	case "<tax:is-a>":
		s.Predicate.Value = "<tax:includes>"
	case "<tax:includes>":
		s.Predicate.Value = "<tax:is-a>"
	default:
		panic("invalid predicate")
	}
	// All IDs returned by the RDF parser are positive, so
	// sign reverse the edge ID to avoid any collisions.
	s.Predicate.UID *= -1
	return foodLine{&s}
}

func (l foodLine) Attributes() []encoding.Attribute {
	text, _, _, err := l.Predicate.Parts()
	if err != nil {
		return []encoding.Attribute{{Key: "error", Value: err.Error()}}
	}
	parts := strings.Split(text, ":")
	return []encoding.Attribute{{Key: parts[0], Value: parts[1]}}
}

// expand copies src into dst, adding the reversal of each line if it is
// distinct.
func expand(dst, src *multi.DirectedGraph) {
	it := src.Edges()
	for it.Next() {
		lit := it.Edge().(multi.Edge)
		for lit.Next() {
			l := lit.Line()
			r := l.ReversedLine()
			dst.SetLine(l)
			if l == r {
				continue
			}
			dst.SetLine(r)
		}
	}
}

func main() {
	const statements = `
_:wolf <tax:is-a> _:animal .
_:wolf <tax:is> "Wolf"^^<tax:common> .
_:wolf <tax:is> "Canis lupus"^^<tax:binomial> .
_:wolf <eco:eats> _:sheep .
_:sheep <tax:is-a> _:animal .
_:sheep <tax:is> "Sheep"^^<tax:common> .
_:sheep <tax:is> "Ovis aries"^^<tax:binomial> .
_:sheep <eco:eats> _:grass .
_:grass <tax:is-a> _:plant .
_:grass <tax:is> "Grass"^^<tax:common> .
_:grass <tax:is> "Lolium perenne"^^<tax:binomial> .
_:grass <tax:is> "Festuca rubra"^^<tax:binomial> .
_:grass <tax:is> "Poa pratensis"^^<tax:binomial> .
`

	// Decode the statement stream and insert the lines into a multigraph.
	g := multi.NewDirectedGraph()
	dec := rdf.NewDecoder(strings.NewReader(statements))
	for {
		l, err := dec.Unmarshal()
		if err != nil {
			break
		}

		// Wrap the line with a shim type to allow the RDF values
		// to be passed to the DOT marshaling routine.
		g.SetLine(foodLine{l})
	}

	h := multi.NewDirectedGraph()
	expand(h, g)

	// Marshal the graph into DOT.
	b, err := dot.MarshalMulti(h, "food web", "", "\t")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n\n", b)

}
Output:


digraph "food web" {
	// Node definitions.
	"_:wolf";
	"_:animal";
	"Wolf" [tax=common];
	"Canis lupus" [tax=binomial];
	"_:sheep";
	"Sheep" [tax=common];
	"Ovis aries" [tax=binomial];
	"_:grass";
	"_:plant";
	"Grass" [tax=common];
	"Lolium perenne" [tax=binomial];
	"Festuca rubra" [tax=binomial];
	"Poa pratensis" [tax=binomial];

	// Edge definitions.
	"_:wolf" -> "_:animal" [tax="is-a"];
	"_:wolf" -> "Wolf" [tax=is];
	"_:wolf" -> "Canis lupus" [tax=is];
	"_:wolf" -> "_:sheep" [eco=eats];
	"_:animal" -> "_:wolf" [tax=includes];
	"_:animal" -> "_:sheep" [tax=includes];
	"_:sheep" -> "_:wolf" [eco="eaten-by"];
	"_:sheep" -> "_:animal" [tax="is-a"];
	"_:sheep" -> "Sheep" [tax=is];
	"_:sheep" -> "Ovis aries" [tax=is];
	"_:sheep" -> "_:grass" [eco=eats];
	"_:grass" -> "_:sheep" [eco="eaten-by"];
	"_:grass" -> "_:plant" [tax="is-a"];
	"_:grass" -> "Grass" [tax=is];
	"_:grass" -> "Lolium perenne" [tax=is];
	"_:grass" -> "Festuca rubra" [tax=is];
	"_:grass" -> "Poa pratensis" [tax=is];
	"_:plant" -> "_:grass" [tax=includes];
}

func (*Statement) String

func (s *Statement) String() string

String returns the RDF 1.1 N-Quad formatted statement.

func (*Statement) To

func (s *Statement) To() graph.Node

To returns the object of the statement.

type Statements

type Statements struct {
	// contains filtered or unexported fields
}

Statements is an RDF statement iterator.

func (*Statements) Next

func (s *Statements) Next() bool

Next returns whether the iterator holds any additional statements.

func (*Statements) Statement

func (s *Statements) Statement() *Statement

Statement returns the current statement.

type Term

type Term struct {
	// Value is the text value of term.
	Value string

	// UID is the unique ID for the term
	// in a collection of RDF terms.
	UID int64
}

Term is an RDF term. It implements the graph.Node interface.

func NewBlankTerm

func NewBlankTerm(label string) (Term, error)

NewBlankTerm returns a Term based on the provided RDF blank node label. The label should not include the "_:" prefix. The returned Term will not have the UID set.

func NewIRITerm

func NewIRITerm(iri string) (Term, error)

NewIRITerm returns a Term based on the provided IRI which must be valid and include a scheme. The returned Term will not have the UID set.

func NewLiteralTerm

func NewLiteralTerm(text, qual string) (Term, error)

NewLiteralTerm returns a Term based on the literal text and an optional qualifier which may either be a "@"-prefixed language tag or a valid IRI. The text will be escaped if necessary and quoted, and if an IRI is given it will be escaped if necessary. The returned Term will not have the UID set.

func (Term) ID

func (t Term) ID() int64

ID returns the value of the Term's UID field.

func (Term) Parts

func (t Term) Parts() (text, qual string, kind Kind, err error)

Parts returns the parts of the term and the kind of the term. IRI node text is returned as a valid IRI with the quoting angle brackets removed and escape sequences interpreted, and blank nodes are stripped of the "_:" prefix. When the term is a literal, qual will either be empty, an unescaped IRI, or an RDF language tag prefixed with an @ symbol. The literal text is returned unquoted and unescaped.

Notes

Bugs

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL