turtle

This Golang package serves as a serializer and parser of the Turtle format used for representing RDF data. This package covers most features of the format's version 1.1.
This is a fork of (nvkp/turtle)[https://github.com/nvkp/turtle] that more comprehensively handles base, prefixes, and handles IRIs in a spec-compliant way. All URLs are expanded on parse, and made relative on marshal. Please see turtle.Config for a way of setting Base and Prefixes for your marshal routines.
Usage
To add this package as a dependency to you Golang module, run:
go get github.com/erikh/turtle
The API of the package follows the Golang's traditional pattern for serializing and parsing. The serializing operation happens through the turtle.Marshal(v interface{}) ([]byte, error) function. The function accepts the to-be-serialized data as an empty interface and returns the byte slice with the result and possible error value.
It is able to handle single struct, struct, a slice, an array or a pointer to all three. The fields of the structs passed to the function have to be annotated by tags defining which of the fields correspond to which part of the RDF triple.
var triple = struct {
Subject string `turtle:"subject"`
Predicate string `turtle:"predicate"`
Object string `turtle:"object"`
}{
Subject: "http://e.org/person/Mark_Twain",
Predicate: "http://e.org/relation/author",
Object: "http://e.org/books/Huckleberry_Finn",
}
b, err := turtle.Marshal(&triple)
fmt.Println(string(b)) // <http://e.org/person/Mark_Twain> <http://e.org/relation/author> <http://e.org/books/Huckleberry_Finn> .
As default the compact version of the Turtle format is used. The resulting Turtle triples are sorted alphabetically first by subjects, then by predicates and then by objects.
var triple = []struct {
Subject string `turtle:"subject"`
Predicate string `turtle:"predicate"`
Object string `turtle:"object"`
}{
{
Subject: "http://e.org/person/Mark_Twain",
Predicate: "http://e.org/relation/author",
Object: "http://e.org/books/Huckleberry_Finn",
},
{
Subject: "http://e.org/person/Mark_Twain",
Predicate: "http://e.org/relation/author",
Object: "http://e.org/books/Tom_Sawyer",
},
}
b, err := turtle.Marshal(&triple)
fmt.Println(string(b)) // <http://e.org/person/Mark_Twain> <http://e.org/relation/author> <http://e.org/books/Huckleberry_Finn>, <http://e.org/books/Tom_Sawyer> .
Parsing happens through the turtle.Unmarshal(data []byte, v interface{}) error function which accepts a byte slice of the turtle data and also a target as a pointer to a struct or to a slice/array of structs that have fields annotated by tags turtle defining which field of the struct corresponds to which part of the RDF triple.
var triple = struct {
Subject string `turtle:"subject"`
Predicate string `turtle:"predicate"`
Object string `turtle:"object"`
}{}
err := turtle.Unmarshal(
[]byte("<http://e.org/person/Mark_Twain> <http://e.org/relation/author> <http://e.org/books/Huckleberry_Finn> ."),
&triple,
)
fmt.Println(triple) // {http://e.org/person/Mark_Twain http://e.org/relation/author http://e.org/books/Huckleberry_Finn}
The turtle.Unmarshal function accepts the compact version of Turtle just as the N-triples version of the format where each row corresponds to a single triple. It reads @base and @prefix forms and extends the IRIs that are filled in the target structure with them. It ignores Turtle comments, labels and data types. The keyword a gets replaced by http://www.w3.org/1999/02/22-rdf-syntax-ns#type IRI. The function is able to handle multiline literals, literal floats, blank nodes, blank node lists and RDF collections.
If the turtle:"base" struct tag points at a string or turtle:"prefix" with map[string]string is provided, those fields will be filled in with the base and collection of prefixes respectively. This is per-struct and any future pragma encountered will only effect the following triples. These tags are ignored on marshal, in favor of a configured marshaler. See "Config" for more information.
var triples = []struct {
Subject string `turtle:"subject"`
Predicate string `turtle:"predicate"`
Object string `turtle:"object"`
Prefixes map[string]string `turtle:"prefix"`
Base string `turtle:"base"`
}{}
rdf := `
@base <http://e.org/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix rel: <http://www.perceive.net/schemas/relationship/> .
</green-goblin>
rel:enemyOf </spiderman> ;
a foaf:Person ;
foaf:name "Green Goblin" .
`
err := turtle.Unmarshal(
[]byte(rdf),
&triples,
)
Both turtle.Marshal(v interface{}) ([]byte, error) and turtle.Unmarshal(data []byte, v interface{}) error functions can handle two optional field tags datatype and label annotating the object literals. The struct's attributes with those field tags can either be pointers to string or string values.
Strings in objects are determined if they are IRIs, Literals, or Blank Nodes by their content. If a field for turtle:"objecttype" is provided, you can skip this step for Marshaling and ensure the right type is being used. For unmarshaling, it is detected at parsing time and injected into the struct, allowing for easy unmarshal/marshal loops.
The values for objecttype can be "literal", "iri", or "blank".
var triples = []struct {
Subject string `turtle:"subject"`
Predicate string `turtle:"predicate"`
Object string `turtle:"object"`
Label string `turtle:"label"`
DataType string `turtle:"datatype"`
ObjectType string `turtle:"objecttype"` // "literal" here
}{}
rdf := `
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix rel: <http://www.perceive.net/schemas/relationship/> .
<http://e.org/green-goblin>
rel:enemyOf <http://e.org/spiderman> ;
a foaf:Person ;
foaf:name "Green Goblin"^^xsd:string , "Zelený Goblin"@cs .
`
err := turtle.Unmarshal(
[]byte(rdf),
&triples,
)
If you want to resolve URLs automatically at parsing time, create a configured parser with the turtle.Config struct. The fields are as follows:
- Base: configure
@base without providing syntax
- Prefixes:
map[string]string, configure prefixes without providing syntax
Base and Prefixes operate exactly like if they were included in the document, and any encountered pragma in a parsed document will affect their representation during unmarshaling.
Example:
c := turtle.Config{
Base: "https://example.org/",
Prefixes: map[string]string{
"people": "https://example.org/people/types/"
},
}
triple := struct {
Subject string `turtle:"subject"`
Predicate string `turtle:"predicate"`
Object string `turtle:"object"`
}{
Subject: "/people/Mark_Twain",
Predicate: "a",
Object: "people:author",
}
data, _ := c.Marshal(triple)
// <https://example.org/people/Mark_Twain> <RDF IRI URL> <https://example.org/people/types/author> .
For unmarshaling, if @base or @prefix is encountered during parse, they will overwrite any configured options before further resolution. To be absolutely sure what base and prefixes you are using, unmarshal them too.
Example:
c := turtle.Config{
Base: "https://example.org/",
Prefixes: map[string]string{
"people": "https://example.org/people/types/"
},
}
triple := struct {
Base string `turtle:"base"`
Prefixes map[string]string `turtle:"prefix"`
Subject string `turtle:"subject"`
Predicate string `turtle:"predicate"`
Object string `turtle:"object"`
}{}
doc := `
@base <https://example2.org> .
</people/Mark_Twain> a people:author .
`
c.Unmarshal([]byte(doc), &triple)
// triple.Base == "https://example2.org"
Existing Alternatives
As mentioned prior
There is at least one Golang package available on Github that lets you parse and serialize Turtle data: github.com/deiu/rdf2go. Its API does not comply with the traditional way of parsing and serializing in Golang programs. It defines its own types appearing in the RDF domain as Triple, Graph, etc.
When a user needs a package that would parse and serialize Turtle data, it is fair to suppose that the user has already defined its own RDF data types as triple or graph. In that case for using the above mentioned package, user has to create a logic for converting its triple data types into the package's data types and adding them to the package's graph structure.
More "Golang way" that this package offers is to annotated the user's already defined structures and the package would read these annotations and behave accordingly.
Authors
License
MIT