├── .travis.yml ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── graph.go ├── graph_test.go ├── mime.go ├── term.go ├── term_test.go ├── triple.go └── triple_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 1.8.1 4 | 5 | before_install: 6 | - go get github.com/mattn/goveralls 7 | script: 8 | - go test -v -covermode=count -coverprofile=coverage.out 9 | - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci 10 | 11 | notifications: 12 | webhooks: 13 | on_success: change # options: [always|never|change] default: always 14 | on_failure: always # options: [always|never|change] default: always 15 | on_start: false # default: false 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andrei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rdf2go 2 | 3 | [![Build Status](https://api.travis-ci.org/deiu/rdf2go.svg?branch=master)](https://travis-ci.org/deiu/rdf2go) 4 | [![Coverage Status](https://coveralls.io/repos/github/deiu/rdf2go/badge.svg?branch=master)](https://coveralls.io/github/deiu/rdf2go?branch=master) 5 | 6 | Native golang parser/serializer from/to Turtle and JSON-LD. 7 | 8 | # Installation 9 | 10 | Just go get it! 11 | 12 | `go get -u github.com/deiu/rdf2go` 13 | 14 | # Example usage 15 | 16 | ## Working with graphs 17 | 18 | ```golang 19 | // Set a base URI 20 | baseUri := "https://example.org/foo" 21 | 22 | // Create a new graph 23 | g := (baseUri) 24 | 25 | // Add a few triples to the graph 26 | triple1 := NewTriple(NewResource("a"), NewResource("b"), NewResource("c")) 27 | g.Add(triple1) 28 | triple2 := NewTriple(NewResource("a"), NewResource("d"), NewResource("e")) 29 | g.Add(triple2) 30 | 31 | // Get length of Graph (nr of triples) 32 | g.Len() // -> 2 33 | 34 | // Dump graph contents to NTriples 35 | out := g.String() 36 | // . 37 | // . 38 | 39 | // Delete a triple 40 | g.Remove(triple2) 41 | ``` 42 | 43 | ## Looking up triples from the graph 44 | 45 | ### Returning a single match 46 | 47 | The `g.One()` method returns the first triple that matches against any (or all) of Subject, Predicate, Object patterns. 48 | 49 | ```golang 50 | // Create a new graph 51 | g := NewGraph("https://example.org") 52 | 53 | // Add a few triples 54 | g.Add(NewTriple(NewResource("a"), NewResource("b"), NewResource("c"))) 55 | 56 | // Look up one triple matching the given subject 57 | triple := g.One(NewResource("a"), nil, nil) // -> . 58 | 59 | // Look up one triple matching the given predicate 60 | triple = g.One(nil, NewResource("b"), nil) // -> . 61 | 62 | // Look up one triple matching the given object 63 | triple = g.One(nil, nil, NewResource("c")) // -> . 64 | 65 | // Look up one triple matching the given subject and predicate 66 | triple = g.One(NewResource("a"), NewResource("b"), nil) // -> . 67 | 68 | // Look up one triple matching the a bad predicate 69 | triple = g.One(nil, NewResource("z"), nil) // -> nil 70 | ``` 71 | 72 | ### Returning a list of matches 73 | 74 | Similar to `g.One()`, `g.All()` returns all triples that match the given pattern. 75 | 76 | ```golang 77 | // Create a new graph 78 | g := NewGraph("https://example.org") 79 | 80 | // Add a few triples 81 | g.Add(NewTriple(NewResource("a"), NewResource("b"), NewResource("c"))) 82 | g.Add(NewTriple(NewResource("a"), NewResource("b"), NewResource("d"))) 83 | 84 | // Look up one triple matching the given subject 85 | triples := g.All(nil, nil, NewResource("c")) // 86 | for _, triple := range triples { 87 | triple.String() 88 | } 89 | // Returns a single triple that matches object : 90 | // . 91 | 92 | triples = g.All(nil, NewResource("b"), nil) 93 | for _, triple := range triples { 94 | triple.String() 95 | } 96 | // Returns all triples that match subject : 97 | // . 98 | // . 99 | ``` 100 | 101 | ## Different types of terms (resources) 102 | 103 | ### IRIs 104 | 105 | ```golang 106 | // Create a new IRI 107 | iri := NewResource("https://example.org") 108 | iri.String() // -> 109 | ``` 110 | 111 | ### Literals 112 | 113 | ```golang 114 | // Create a new simple Literal 115 | lit := NewLiteral("hello world") 116 | lit.String() // -> "hello word" 117 | 118 | // Create a new Literal with language tag 119 | lit := NewLiteralWithLanguage("hello world", "en") 120 | lit.String() // -> "hello word"@en 121 | 122 | // Create a new Literal with a data type 123 | lit := NewLiteralWithDatatype("newTypeVal", NewResource("https://datatype.com")) 124 | lit.String() // -> "newTypeVal"^^ 125 | ``` 126 | 127 | ### Blank Nodes 128 | 129 | ```golang 130 | // Create a new Blank Node with a given ID 131 | bn := NewBlankNode(9) 132 | bn.String() // -> "_:n9" 133 | 134 | // Create an anonymous Blank Node with a random ID 135 | abn := NewAnonNode() 136 | abn.String() // -> "_:n192853" 137 | ``` 138 | 139 | 140 | ## Parsing data 141 | 142 | The parser takes an `io.Reader` as first parameter, and the string containing the mime type as the second parameter. 143 | 144 | Currently, the supported parsing formats are Turtle (with mime type `text/turtle`) and JSON-LD (with mime type `application/ld+json`). 145 | 146 | ### Parsing Turtle from an io.Reader 147 | 148 | ```golang 149 | // Set a base URI 150 | baseUri := "https://example.org/foo" 151 | 152 | // Create a new graph 153 | g := NewGraph(baseUri) 154 | 155 | // r is of type io.Reader 156 | g.Parse(r, "text/turtle") 157 | ``` 158 | 159 | ### Parsing JSON-LD from an io.Reader 160 | 161 | ```golang 162 | // Set a base URI 163 | baseUri := "https://example.org/foo" 164 | 165 | // Create a new graph 166 | g := NewGraph(baseUri) 167 | 168 | // r is an io.Reader 169 | g.Parse(r, "application/ld+json") 170 | ``` 171 | 172 | ### Parsing either Turtle or JSON-LD from a URI on the Web 173 | 174 | In this case you don't have to specify the mime type, as the internal http client will try to content negotiate to either Turtle or JSON-LD. An error will be returned if it fails. 175 | 176 | **Note:** The `NewGraph()` function accepts an optional parameter called `skipVerify` that is used to tell the internal http client whether or not to ignore bad/self-signed server side certificates. By default, it will not check if you omit this parameter, or if you set it to `true`. 177 | 178 | ```golang 179 | // Set a base URI 180 | uri := "https://example.org/foo" 181 | 182 | // Check remote server certificate to see if it's valid 183 | // (don't skip verification) 184 | skipVerify := false 185 | 186 | // Create a new graph. You can also omit the skipVerify parameter 187 | // and accept invalid certificates (e.g. self-signed) 188 | g := NewGraph(uri, skipVerify) 189 | 190 | err := g.LoadURI(uri) 191 | if err != nil { 192 | // deal with the error 193 | } 194 | ``` 195 | 196 | 197 | ## Serializing data 198 | 199 | 200 | The serializer takes an `io.Writer` as first parameter, and the string containing the mime type as the second parameter. 201 | 202 | Currently, the supported serialization formats are Turtle (with mime type `text/turtle`) and JSON-LD (with mime type `application/ld+json`). 203 | 204 | 205 | ### Serializing to Turtle 206 | 207 | ```golang 208 | // Set a base URI 209 | baseUri := "https://example.org/foo" 210 | 211 | // Create a new graph 212 | g := NewGraph(baseUri) 213 | 214 | triple := NewTriple(NewResource("a"), NewResource("b"), NewResource("c")) 215 | g.Add(triple) 216 | 217 | // w is of type io.Writer 218 | g.Serialize(w, "text/turtle") 219 | ``` 220 | 221 | ### Serializing to JSON-LD 222 | 223 | ```golang 224 | // Set a base URI 225 | baseUri := "https://example.org/foo" 226 | 227 | // Create a new graph 228 | g := NewGraph(baseUri) 229 | 230 | triple := NewTriple(NewResource("a"), NewResource("b"), NewResource("c")) 231 | g.Add(triple) 232 | 233 | // w is of type io.Writer 234 | g.Serialize(w, "application/ld+json") 235 | ``` 236 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/deiu/rdf2go 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/deiu/gon3 v0.0.0-20241212124032-93153c038193 7 | github.com/linkeddata/gojsonld v0.0.0-20170418210642-4f5db6791326 8 | github.com/stretchr/testify v1.8.2 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/pmezard/go-difflib v1.0.0 // indirect 14 | github.com/rychipman/easylex v0.0.0-20160129204217-49ee7767142f // indirect 15 | gopkg.in/yaml.v3 v3.0.1 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/deiu/gon3 v0.0.0-20241212124032-93153c038193 h1:EQBdXSCO7r+0KQE/pN6v+RAH7p6+yz+6pbCfHh+ETME= 5 | github.com/deiu/gon3 v0.0.0-20241212124032-93153c038193/go.mod h1:EdezkFZtCJELxMo+YIX5B5i5ofz9U+n+xSxWku6mOS0= 6 | github.com/linkeddata/gojsonld v0.0.0-20170418210642-4f5db6791326 h1:YP3lfXXYiQV5MKeUqVnxRP5uuMQTLPx+PGYm1UBoU98= 7 | github.com/linkeddata/gojsonld v0.0.0-20170418210642-4f5db6791326/go.mod h1:nfqkuSNlsk1bvti/oa7TThx4KmRMBmSxf3okHI9wp3E= 8 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 10 | github.com/rychipman/easylex v0.0.0-20160129204217-49ee7767142f h1:L2/fBPABieQnQzfV40k2Zw7IcvZbt0CN5TgwUl8zDCs= 11 | github.com/rychipman/easylex v0.0.0-20160129204217-49ee7767142f/go.mod h1:MZ2GRTcqmve6EoSbErWgCR+Ash4p8Gc5esHe8MDErss= 12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 13 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 14 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 15 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 16 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 17 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 18 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 19 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 21 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 22 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 23 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 24 | -------------------------------------------------------------------------------- /graph.go: -------------------------------------------------------------------------------- 1 | package rdf2go 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | 12 | rdf "github.com/deiu/gon3" 13 | jsonld "github.com/linkeddata/gojsonld" 14 | ) 15 | 16 | // Graph structure 17 | type Graph struct { 18 | triples map[*Triple]bool 19 | httpClient *http.Client 20 | uri string 21 | term Term 22 | } 23 | 24 | // NewHttpClient creates an http.Client to be used for parsing resources 25 | // directly from the Web 26 | func NewHttpClient(skip bool) *http.Client { 27 | return &http.Client{ 28 | Transport: &http.Transport{ 29 | TLSClientConfig: &tls.Config{ 30 | InsecureSkipVerify: skip, 31 | }, 32 | }, 33 | } 34 | } 35 | 36 | // NewGraph creates a Graph object 37 | func NewGraph(uri string, skipVerify ...bool) *Graph { 38 | skip := false 39 | if len(skipVerify) > 0 { 40 | skip = skipVerify[0] 41 | } 42 | g := &Graph{ 43 | triples: make(map[*Triple]bool), 44 | httpClient: NewHttpClient(skip), 45 | uri: uri, 46 | term: NewResource(uri), 47 | } 48 | return g 49 | } 50 | 51 | // Len returns the length of the graph as number of triples in the graph 52 | func (g *Graph) Len() int { 53 | return len(g.triples) 54 | } 55 | 56 | // Term returns a Graph Term object 57 | func (g *Graph) Term() Term { 58 | return g.term 59 | } 60 | 61 | // URI returns a Graph URI object 62 | func (g *Graph) URI() string { 63 | return g.uri 64 | } 65 | 66 | // One returns one triple based on a triple pattern of S, P, O objects 67 | func (g *Graph) One(s Term, p Term, o Term) *Triple { 68 | for triple := range g.IterTriples() { 69 | if s != nil { 70 | if p != nil { 71 | if o != nil { 72 | if triple.Subject.Equal(s) && triple.Predicate.Equal(p) && triple.Object.Equal(o) { 73 | return triple 74 | } 75 | } else { 76 | if triple.Subject.Equal(s) && triple.Predicate.Equal(p) { 77 | return triple 78 | } 79 | } 80 | } else { 81 | if triple.Subject.Equal(s) { 82 | return triple 83 | } 84 | } 85 | } else if p != nil { 86 | if o != nil { 87 | if triple.Predicate.Equal(p) && triple.Object.Equal(o) { 88 | return triple 89 | } 90 | } else { 91 | if triple.Predicate.Equal(p) { 92 | return triple 93 | } 94 | } 95 | } else if o != nil { 96 | if triple.Object.Equal(o) { 97 | return triple 98 | } 99 | } else { 100 | return triple 101 | } 102 | } 103 | return nil 104 | } 105 | 106 | // IterTriples provides a channel containing all the triples in the graph. 107 | // Note that the returned channel is already closed. 108 | func (g *Graph) IterTriples() (ch chan *Triple) { 109 | // This function returns a channel rather than a slice for backwards compatibility. 110 | // It does not use a goroutine to populate the channel because that can trigger Go's 'concurrent map misuse' 111 | // detector, and would have little performance benefit. 112 | ch = make(chan *Triple, len(g.triples)) 113 | for triple := range g.triples { 114 | ch <- triple 115 | } 116 | close(ch) 117 | return ch 118 | } 119 | 120 | // Add is used to add a Triple object to the graph 121 | func (g *Graph) Add(t *Triple) { 122 | g.triples[t] = true 123 | } 124 | 125 | // AddTriple is used to add a triple made of individual S, P, O objects 126 | func (g *Graph) AddTriple(s Term, p Term, o Term) { 127 | g.triples[NewTriple(s, p, o)] = true 128 | } 129 | 130 | // Remove is used to remove a Triple object 131 | func (g *Graph) Remove(t *Triple) { 132 | delete(g.triples, t) 133 | } 134 | 135 | // All is used to return all triples that match a given pattern of S, P, O objects 136 | func (g *Graph) All(s Term, p Term, o Term) []*Triple { 137 | var triples []*Triple 138 | for triple := range g.IterTriples() { 139 | if s != nil { 140 | if p != nil { 141 | if o != nil { 142 | if triple.Subject.Equal(s) && triple.Predicate.Equal(p) && triple.Object.Equal(o) { 143 | triples = append(triples, triple) 144 | } 145 | } else { 146 | if triple.Subject.Equal(s) && triple.Predicate.Equal(p) { 147 | triples = append(triples, triple) 148 | } 149 | } 150 | } else { 151 | if triple.Subject.Equal(s) { 152 | triples = append(triples, triple) 153 | } 154 | } 155 | } else if p != nil { 156 | if o != nil { 157 | if triple.Predicate.Equal(p) && triple.Object.Equal(o) { 158 | triples = append(triples, triple) 159 | } 160 | } else { 161 | if triple.Predicate.Equal(p) { 162 | triples = append(triples, triple) 163 | } 164 | } 165 | } else if o != nil { 166 | if triple.Object.Equal(o) { 167 | triples = append(triples, triple) 168 | } 169 | } 170 | } 171 | return triples 172 | } 173 | 174 | // Merge is used to add all the triples form another graph to this one 175 | func (g *Graph) Merge(toMerge *Graph) { 176 | for triple := range toMerge.IterTriples() { 177 | g.Add(triple) 178 | } 179 | } 180 | 181 | // Parse is used to parse RDF data from a reader, using the provided mime type 182 | func (g *Graph) Parse(reader io.Reader, mime string) error { 183 | parserName := mimeParser[mime] 184 | if len(parserName) == 0 { 185 | parserName = "guess" 186 | } 187 | if parserName == "jsonld" { 188 | buf := new(bytes.Buffer) 189 | buf.ReadFrom(reader) 190 | jsonData, err := jsonld.ReadJSON(buf.Bytes()) 191 | if err != nil { 192 | return err 193 | } 194 | options := &jsonld.Options{} 195 | options.Base = "" 196 | options.ProduceGeneralizedRdf = false 197 | dataSet, err := jsonld.ToRDF(jsonData, options) 198 | if err != nil { 199 | return err 200 | } 201 | for t := range dataSet.IterTriples() { 202 | g.AddTriple(jterm2term(t.Subject), jterm2term(t.Predicate), jterm2term(t.Object)) 203 | } 204 | 205 | } else if parserName == "turtle" { 206 | parser, err := rdf.NewParser(g.uri).Parse(reader) 207 | if err != nil { 208 | return err 209 | } 210 | for s := range parser.IterTriples() { 211 | g.AddTriple(rdf2term(s.Subject), rdf2term(s.Predicate), rdf2term(s.Object)) 212 | } 213 | } else { 214 | return errors.New(parserName + " is not supported by the parser") 215 | } 216 | return nil 217 | } 218 | 219 | // LoadURI is used to load RDF data from a specific URI 220 | func (g *Graph) LoadURI(uri string) error { 221 | doc := defrag(uri) 222 | q, err := http.NewRequest("GET", doc, nil) 223 | if err != nil { 224 | return err 225 | } 226 | if len(g.uri) == 0 { 227 | g.uri = doc 228 | } 229 | q.Header.Set("Accept", "text/turtle;q=1,application/ld+json;q=0.5") 230 | r, err := g.httpClient.Do(q) 231 | if err != nil { 232 | return err 233 | } 234 | if r != nil { 235 | defer r.Body.Close() 236 | if r.StatusCode == 200 { 237 | g.Parse(r.Body, r.Header.Get("Content-Type")) 238 | } else { 239 | return fmt.Errorf("Could not fetch graph from %s - HTTP %d", uri, r.StatusCode) 240 | } 241 | } 242 | return nil 243 | } 244 | 245 | // String is used to serialize the graph object using NTriples 246 | func (g *Graph) String() string { 247 | var toString string 248 | for triple := range g.IterTriples() { 249 | toString += triple.String() + "\n" 250 | } 251 | return toString 252 | } 253 | 254 | // Serialize is used to serialize a graph based on a given mime type 255 | func (g *Graph) Serialize(w io.Writer, mime string) error { 256 | serializerName := mimeSerializer[mime] 257 | if serializerName == "jsonld" { 258 | return g.serializeJSONLD(w) 259 | } 260 | // just return Turtle by default 261 | return g.serializeTurtle(w) 262 | } 263 | 264 | // @TODO improve streaming 265 | func (g *Graph) serializeTurtle(w io.Writer) error { 266 | var err error 267 | 268 | triplesBySubject := make(map[string][]*Triple) 269 | 270 | for triple := range g.IterTriples() { 271 | s := encodeTerm(triple.Subject) 272 | triplesBySubject[s] = append(triplesBySubject[s], triple) 273 | } 274 | 275 | for subject, triples := range triplesBySubject { 276 | _, err = fmt.Fprintf(w, "%s\n", subject) 277 | if err != nil { 278 | return err 279 | } 280 | 281 | for key, triple := range triples { 282 | p := encodeTerm(triple.Predicate) 283 | o := encodeTerm(triple.Object) 284 | 285 | if key == len(triples)-1 { 286 | _, err = fmt.Fprintf(w, " %s %s .", p, o) 287 | if err != nil { 288 | return err 289 | } 290 | break 291 | } 292 | _, err = fmt.Fprintf(w, " %s %s ;\n", p, o) 293 | if err != nil { 294 | return err 295 | } 296 | } 297 | 298 | } 299 | 300 | return nil 301 | } 302 | 303 | // func (g *Graph) serializeJSONLD(w io.Writer) error { 304 | // d := jsonld.NewDataset() 305 | // triples := []*jsonld.Triple{} 306 | 307 | // for triple := range g.IterTriples() { 308 | // jTriple := jsonld.NewTriple(term2jterm(triple.Subject), term2jterm(triple.Predicate), term2jterm(triple.Object)) 309 | // triples = append(triples, jTriple) 310 | // } 311 | 312 | // d.Graphs[g.URI()] = triples 313 | // opts := jsonld.NewOptions(g.URI()) 314 | // opts.UseNativeTypes = false 315 | // opts.UseRdfType = true 316 | // serializedJSON := jsonld.FromRDF(d, opts) 317 | // jsonOut, err := json.MarshalIndent(serializedJSON, "", " ") 318 | // if err != nil { 319 | // return err 320 | // } 321 | 322 | // _, err = fmt.Fprintf(w, "%s", jsonOut) 323 | // return err 324 | // } 325 | 326 | func (g *Graph) serializeJSONLD(w io.Writer) error { 327 | r := []map[string]interface{}{} 328 | for elt := range g.IterTriples() { 329 | var one map[string]interface{} 330 | switch elt.Subject.(type) { 331 | case *BlankNode: 332 | one = map[string]interface{}{ 333 | "@id": elt.Subject.(*BlankNode).String(), 334 | } 335 | default: 336 | one = map[string]interface{}{ 337 | "@id": elt.Subject.(*Resource).URI, 338 | } 339 | } 340 | switch t := elt.Object.(type) { 341 | case *Resource: 342 | one[elt.Predicate.(*Resource).URI] = []map[string]string{ 343 | { 344 | "@id": t.URI, 345 | }, 346 | } 347 | break 348 | case *Literal: 349 | v := map[string]string{ 350 | "@value": t.Value, 351 | } 352 | if t.Datatype != nil && len(t.Datatype.String()) > 0 { 353 | v["@type"] = debrack(t.Datatype.String()) 354 | } 355 | if len(t.Language) > 0 { 356 | v["@language"] = t.Language 357 | } 358 | one[elt.Predicate.(*Resource).URI] = []map[string]string{v} 359 | } 360 | r = append(r, one) 361 | } 362 | bytes, err := json.Marshal(r) 363 | if err != nil { 364 | return err 365 | } 366 | fmt.Fprintf(w, string(bytes)) 367 | return nil 368 | } 369 | -------------------------------------------------------------------------------- /graph_test.go: -------------------------------------------------------------------------------- 1 | package rdf2go 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "net/http/httptest" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | var ( 14 | testServer *httptest.Server 15 | 16 | testUri = "https://example.org" 17 | simpleTurtle = "@prefix foaf: .\n<#me> a foaf:Person ;\nfoaf:name \"Test\" ." 18 | ) 19 | 20 | func init() { 21 | testServer = httptest.NewServer(MockServer()) 22 | testServer.URL = strings.Replace(testServer.URL, "127.0.0.1", "localhost", 1) 23 | } 24 | 25 | func MockServer() http.Handler { 26 | // Create new handler 27 | handler := http.NewServeMux() 28 | handler.Handle("/foo", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 29 | w.Header().Add("Content-Type", "text/turtle") 30 | w.WriteHeader(200) 31 | w.Write([]byte(simpleTurtle)) 32 | return 33 | })) 34 | return handler 35 | } 36 | 37 | func TestNewGraph(t *testing.T) { 38 | g := NewGraph(testUri) 39 | assert.Equal(t, testUri, g.URI()) 40 | assert.Equal(t, 0, g.Len()) 41 | assert.Equal(t, NewResource(testUri), g.Term()) 42 | } 43 | 44 | func TestGraphString(t *testing.T) { 45 | triple := NewTriple(NewResource("a"), NewResource("b"), NewResource("c")) 46 | g := NewGraph(testUri) 47 | g.Add(triple) 48 | assert.Equal(t, " .\n", g.String()) 49 | } 50 | 51 | func TestGraphAdd(t *testing.T) { 52 | triple := NewTriple(NewResource("a"), NewResource("b"), NewResource("c")) 53 | g := NewGraph(testUri) 54 | g.Add(triple) 55 | assert.Equal(t, 1, g.Len()) 56 | g.Remove(triple) 57 | assert.Equal(t, 0, g.Len()) 58 | } 59 | 60 | func TestGraphResourceTerms(t *testing.T) { 61 | t1 := NewResource(testUri) 62 | assert.True(t, t1.Equal(rdf2term(term2rdf(t1)))) 63 | assert.True(t, t1.Equal(jterm2term(term2jterm(t1)))) 64 | } 65 | 66 | func TestGraphLiteralTerms(t *testing.T) { 67 | t1 := NewLiteralWithDatatype("value", NewResource(testUri)) 68 | assert.True(t, t1.Equal(rdf2term(term2rdf(t1)))) 69 | assert.True(t, t1.Equal(jterm2term(term2jterm(t1)))) 70 | 71 | t2 := NewLiteralWithLanguage("value", "en") 72 | assert.True(t, t2.Equal(rdf2term(term2rdf(t2)))) 73 | assert.True(t, t2.Equal(jterm2term(term2jterm(t2)))) 74 | 75 | t3 := NewLiteral("value") 76 | assert.True(t, t3.Equal(rdf2term(term2rdf(t3)))) 77 | assert.True(t, t3.Equal(jterm2term(term2jterm(t3)))) 78 | } 79 | 80 | func TestGraphBlankNodeTerms(t *testing.T) { 81 | t1 := NewBlankNode("n1") 82 | assert.True(t, t1.Equal(rdf2term(term2rdf(t1)))) 83 | assert.True(t, t1.Equal(jterm2term(term2jterm(t1)))) 84 | } 85 | 86 | func TestGraphOne(t *testing.T) { 87 | g := NewGraph(testUri) 88 | 89 | assert.Nil(t, g.One(NewResource("a"), nil, nil)) 90 | 91 | triple := NewTriple(NewResource("a"), NewResource("foo#b"), NewResource("c")) 92 | g.Add(triple) 93 | 94 | assert.True(t, triple.Equal(g.One(NewResource("a"), NewResource("foo#b"), NewResource("c")))) 95 | assert.True(t, triple.Equal(g.One(NewResource("a"), NewResource("foo#b"), nil))) 96 | assert.True(t, triple.Equal(g.One(NewResource("a"), nil, nil))) 97 | 98 | assert.True(t, triple.Equal(g.One(nil, NewResource("foo#b"), NewResource("c")))) 99 | assert.True(t, triple.Equal(g.One(nil, nil, NewResource("c")))) 100 | assert.True(t, triple.Equal(g.One(nil, NewResource("foo#b"), nil))) 101 | 102 | assert.True(t, triple.Equal(g.One(nil, nil, nil))) 103 | } 104 | 105 | func TestGraphAll(t *testing.T) { 106 | g := NewGraph(testUri) 107 | 108 | assert.Empty(t, g.All(nil, nil, nil)) 109 | 110 | g.AddTriple(NewResource("a"), NewResource("b"), NewResource("c")) 111 | g.AddTriple(NewResource("a"), NewResource("b"), NewResource("d")) 112 | g.AddTriple(NewResource("a"), NewResource("f"), NewLiteral("h")) 113 | g.AddTriple(NewResource("g"), NewResource("b2"), NewResource("e")) 114 | g.AddTriple(NewResource("g"), NewResource("b2"), NewResource("c")) 115 | 116 | assert.Equal(t, 0, len(g.All(nil, nil, nil))) 117 | assert.Equal(t, 3, len(g.All(NewResource("a"), nil, nil))) 118 | assert.Equal(t, 2, len(g.All(nil, NewResource("b"), nil))) 119 | assert.Equal(t, 1, len(g.All(nil, nil, NewResource("d")))) 120 | assert.Equal(t, 2, len(g.All(nil, nil, NewResource("c")))) 121 | assert.Equal(t, 1, len(g.All(NewResource("a"), NewResource("b"), NewResource("c")))) 122 | assert.Equal(t, 1, len(g.All(NewResource("a"), NewResource("f"), nil))) 123 | assert.Equal(t, 1, len(g.All(nil, NewResource("f"), NewLiteral("h")))) 124 | } 125 | 126 | func TestGraphLoadURI(t *testing.T) { 127 | uri := testServer.URL + "/foo#me" 128 | g := NewGraph(uri) 129 | err := g.LoadURI(uri) 130 | assert.NoError(t, err) 131 | assert.Equal(t, 2, g.Len()) 132 | } 133 | 134 | func TestGraphLoadURIFail(t *testing.T) { 135 | uri := testServer.URL + "/fail" 136 | g := NewGraph(uri) 137 | g.uri = "" 138 | err := g.LoadURI(uri) 139 | assert.Error(t, err) 140 | } 141 | 142 | func TestGraphLoadURINoSkip(t *testing.T) { 143 | uri := testServer.URL + "/foo#me" 144 | g := NewGraph(uri, false) 145 | err := g.LoadURI(uri) 146 | assert.NoError(t, err) 147 | assert.Equal(t, 2, g.Len()) 148 | } 149 | 150 | func TestParseFail(t *testing.T) { 151 | g := NewGraph(testUri) 152 | g.Parse(strings.NewReader(simpleTurtle), "text/plain") 153 | assert.Equal(t, 0, g.Len()) 154 | } 155 | 156 | func TestParseTurtle(t *testing.T) { 157 | g := NewGraph(testUri) 158 | g.Parse(strings.NewReader(simpleTurtle), "text/turtle") 159 | assert.Equal(t, 2, g.Len()) 160 | assert.NotNil(t, g.One(NewResource(testUri+"#me"), NewResource("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), NewResource("http://xmlns.com/foaf/0.1/Person"))) 161 | assert.NotNil(t, g.One(NewResource(testUri+"#me"), NewResource("http://xmlns.com/foaf/0.1/name"), NewLiteral("Test"))) 162 | 163 | prefixTurtle := "@prefix test: .\n<#me> test:foo \"Test\" ." 164 | g = NewGraph(testUri) 165 | g.Parse(strings.NewReader(prefixTurtle), "text/turtle") 166 | assert.Equal(t, 1, g.Len()) 167 | assert.NotNil(t, g.One(NewResource(testUri+"#me"), NewResource("http://example.org/test#foo"), NewLiteral("Test"))) 168 | } 169 | 170 | func TestSerializeTurtle(t *testing.T) { 171 | triple1 := NewTriple(NewResource("a"), NewResource("b"), NewResource("c")) 172 | g := NewGraph(testUri) 173 | g.Add(triple1) 174 | 175 | b := new(bytes.Buffer) 176 | g.Serialize(b, "text/turtle") 177 | assert.Equal(t, "\n .", b.String()) 178 | 179 | triple2 := NewTriple(NewResource("a"), NewResource("b"), NewResource("d")) 180 | g.Add(triple2) 181 | 182 | b = new(bytes.Buffer) 183 | g.Serialize(b, "text/turtle") 184 | toParse := strings.NewReader(b.String()) 185 | g2 := NewGraph(testUri) 186 | g2.Parse(toParse, "text/turtle") 187 | assert.Equal(t, 2, g2.Len()) 188 | } 189 | 190 | func TestParseJSONLD(t *testing.T) { 191 | data := "{ \"@id\": \"http://example.org/#me\", \"http://xmlns.com/foaf/0.1/name\": \"Test\" }" 192 | r := strings.NewReader(data) 193 | g := NewGraph(testUri) 194 | g.Parse(r, "application/ld+json") 195 | assert.Equal(t, 1, g.Len()) 196 | } 197 | 198 | func TestSerializeJSONLD(t *testing.T) { 199 | g := NewGraph(testUri) 200 | g.Parse(strings.NewReader(simpleTurtle), "text/turtle") 201 | g.Add(NewTriple(NewResource(testUri+"#me"), NewResource("http://xmlns.com/foaf/0.1/nick"), NewLiteralWithLanguage("test", "en"))) 202 | g.Add(NewTriple(NewBlankNode("n9"), NewResource("http://xmlns.com/foaf/0.1/name"), NewLiteralWithLanguage("test", "en"))) 203 | assert.Equal(t, 4, g.Len()) 204 | 205 | var b bytes.Buffer 206 | g.Serialize(&b, "application/ld+json") 207 | toParse := strings.NewReader(b.String()) 208 | g2 := NewGraph(testUri) 209 | g2.Parse(toParse, "application/ld+json") 210 | assert.Equal(t, 4, g2.Len()) 211 | } 212 | 213 | func TestGraphMerge(t *testing.T) { 214 | g := NewGraph(testUri) 215 | g2 := NewGraph(testUri) 216 | 217 | g.AddTriple(NewResource("a"), NewResource("b"), NewResource("c")) 218 | g.AddTriple(NewResource("a"), NewResource("b"), NewResource("d")) 219 | g.AddTriple(NewResource("a"), NewResource("f"), NewLiteral("h")) 220 | assert.Equal(t,3,g.Len()) 221 | g2.AddTriple(NewResource("g"), NewResource("b2"), NewResource("e")) 222 | g2.AddTriple(NewResource("g"), NewResource("b2"), NewResource("c")) 223 | assert.Equal(t,2,g2.Len()) 224 | 225 | g.Merge(g2) 226 | 227 | assert.Equal(t,5,g.Len()) 228 | assert.NotEqual(t,nil,g.One(NewResource("a"),NewResource("b"),NewResource("c"))) 229 | assert.NotEqual(t,nil,g.One(NewResource("a"),NewResource("b"),NewResource("d"))) 230 | assert.NotEqual(t,nil,g.One(NewResource("a"),NewResource("f"),NewResource("h"))) 231 | assert.NotEqual(t,nil,g.One(NewResource("g"),NewResource("b2"),NewResource("e"))) 232 | assert.NotEqual(t,nil,g.One(NewResource("g"),NewResource("b2"),NewResource("c"))) 233 | } 234 | -------------------------------------------------------------------------------- /mime.go: -------------------------------------------------------------------------------- 1 | package rdf2go 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var mimeParser = map[string]string{ 8 | "text/turtle": "turtle", 9 | "application/ld+json": "jsonld", 10 | "application/sparql-update": "internal", 11 | } 12 | 13 | var mimeSerializer = map[string]string{ 14 | "application/ld+json": "jsonld", 15 | "text/html": "internal", 16 | } 17 | 18 | var mimeRdfExt = map[string]string{ 19 | ".ttl": "text/turtle", 20 | ".n3": "text/n3", 21 | ".rdf": "application/rdf+xml", 22 | ".jsonld": "application/ld+json", 23 | } 24 | 25 | var rdfExtensions = []string{ 26 | ".ttl", 27 | ".n3", 28 | ".rdf", 29 | ".jsonld", 30 | } 31 | 32 | var ( 33 | serializerMimes = []string{} 34 | validMimeType = regexp.MustCompile(`^\w+/\w+$`) 35 | ) 36 | -------------------------------------------------------------------------------- /term.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Kier Davis 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 5 | associated documentation files (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 7 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial 11 | portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 14 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES 16 | OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 17 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | */ 19 | 20 | package rdf2go 21 | 22 | import ( 23 | "fmt" 24 | "math/rand" 25 | "strings" 26 | 27 | rdf "github.com/deiu/gon3" 28 | jsonld "github.com/linkeddata/gojsonld" 29 | ) 30 | 31 | // A Term is the value of a subject, predicate or object i.e. a IRI reference, blank node or 32 | // literal. 33 | type Term interface { 34 | // Method String should return the NTriples representation of this term. 35 | String() string 36 | 37 | // Method RawValue should return the raw value of this term. 38 | RawValue() string 39 | 40 | // Method Equal should return whether this term is equal to another. 41 | Equal(Term) bool 42 | } 43 | 44 | // Resource is an URI / IRI reference. 45 | type Resource struct { 46 | URI string 47 | } 48 | 49 | // NewResource returns a new resource object. 50 | func NewResource(uri string) (term Term) { 51 | return Term(&Resource{URI: uri}) 52 | } 53 | 54 | // String returns the NTriples representation of this resource. 55 | func (term Resource) String() (str string) { 56 | return fmt.Sprintf("<%s>", term.URI) 57 | } 58 | 59 | // RawValue returns the string value of the a resource without brackets. 60 | func (term Resource) RawValue() (str string) { 61 | return term.URI 62 | } 63 | 64 | // Equal returns whether this resource is equal to another. 65 | func (term Resource) Equal(other Term) bool { 66 | if spec, ok := other.(*Resource); ok { 67 | return term.URI == spec.URI 68 | } 69 | 70 | return false 71 | } 72 | 73 | // Literal is a textual value, with an associated language or datatype. 74 | type Literal struct { 75 | Value string 76 | Language string 77 | Datatype Term 78 | } 79 | 80 | // NewLiteral returns a new literal with the given value. 81 | func NewLiteral(value string) (term Term) { 82 | return Term(&Literal{Value: value}) 83 | } 84 | 85 | // NewLiteralWithLanguage returns a new literal with the given value and language. 86 | func NewLiteralWithLanguage(value string, language string) (term Term) { 87 | return Term(&Literal{Value: value, Language: language}) 88 | } 89 | 90 | // NewLiteralWithDatatype returns a new literal with the given value and datatype. 91 | func NewLiteralWithDatatype(value string, datatype Term) (term Term) { 92 | return Term(&Literal{Value: value, Datatype: datatype}) 93 | } 94 | 95 | // String returns the NTriples representation of this literal. 96 | func (term Literal) String() string { 97 | str := term.Value 98 | str = strings.Replace(str, "\\", "\\\\", -1) 99 | str = strings.Replace(str, "\"", "\\\"", -1) 100 | str = strings.Replace(str, "\n", "\\n", -1) 101 | str = strings.Replace(str, "\r", "\\r", -1) 102 | str = strings.Replace(str, "\t", "\\t", -1) 103 | 104 | str = fmt.Sprintf("\"%s\"", str) 105 | 106 | // if term.Language != "" { 107 | str += atLang(term.Language) 108 | // } else 109 | if term.Datatype != nil { 110 | str += "^^" + term.Datatype.String() 111 | } 112 | 113 | return str 114 | } 115 | 116 | func (term Literal) RawValue() string { 117 | return term.Value 118 | } 119 | 120 | // Equal returns whether this literal is equivalent to another. 121 | func (term Literal) Equal(other Term) bool { 122 | spec, ok := other.(*Literal) 123 | if !ok { 124 | return false 125 | } 126 | 127 | if term.Value != spec.Value { 128 | return false 129 | } 130 | 131 | if term.Language != spec.Language { 132 | return false 133 | } 134 | 135 | if (term.Datatype == nil && spec.Datatype != nil) || (term.Datatype != nil && spec.Datatype == nil) { 136 | return false 137 | } 138 | 139 | if term.Datatype != nil && spec.Datatype != nil && !term.Datatype.Equal(spec.Datatype) { 140 | return false 141 | } 142 | 143 | return true 144 | } 145 | 146 | // BlankNode is an RDF blank node i.e. an unqualified URI/IRI. 147 | type BlankNode struct { 148 | ID string 149 | } 150 | 151 | // NewBlankNode returns a new blank node with the given ID. 152 | func NewBlankNode(id string) (term Term) { 153 | return Term(&BlankNode{ID: id}) 154 | } 155 | 156 | // NewAnonNode returns a new blank node with a pseudo-randomly generated ID. 157 | func NewAnonNode() (term Term) { 158 | return Term(&BlankNode{ID: fmt.Sprint("n", rand.Int())}) 159 | } 160 | 161 | // String returns the NTriples representation of the blank node. 162 | func (term BlankNode) String() string { 163 | return "_:" + term.ID 164 | } 165 | 166 | func (term BlankNode) RawValue() string { 167 | return term.ID 168 | } 169 | 170 | // Equal returns whether this blank node is equivalent to another. 171 | func (term BlankNode) Equal(other Term) bool { 172 | if spec, ok := other.(*BlankNode); ok { 173 | return term.ID == spec.ID 174 | } 175 | 176 | return false 177 | } 178 | 179 | func term2rdf(t Term) rdf.Term { 180 | switch t := t.(type) { 181 | case *BlankNode: 182 | id := t.RawValue() 183 | node := rdf.NewBlankNode(id) 184 | return node 185 | case *Resource: 186 | node := rdf.NewIRI(t.RawValue()) 187 | return node 188 | case *Literal: 189 | if t.Datatype != nil { 190 | iri := rdf.NewIRI(t.Datatype.(*Resource).URI) 191 | return rdf.NewLiteralWithDataType(t.Value, iri) 192 | } 193 | if len(t.Language) > 0 { 194 | node := rdf.NewLiteralWithLanguage(t.Value, t.Language) 195 | return node 196 | } 197 | node := rdf.NewLiteral(t.Value) 198 | return node 199 | } 200 | return nil 201 | } 202 | 203 | func rdf2term(term rdf.Term) Term { 204 | switch term := term.(type) { 205 | case *rdf.BlankNode: 206 | // id := fmt.Sprint(term.Id) 207 | return NewBlankNode(term.RawValue()) 208 | case *rdf.Literal: 209 | if len(term.LanguageTag) > 0 { 210 | return NewLiteralWithLanguage(term.LexicalForm, term.LanguageTag) 211 | } 212 | if term.DatatypeIRI != nil && len(term.DatatypeIRI.String()) > 0 { 213 | return NewLiteralWithDatatype(term.LexicalForm, NewResource(debrack(term.DatatypeIRI.String()))) 214 | } 215 | return NewLiteral(term.RawValue()) 216 | case *rdf.IRI: 217 | return NewResource(term.RawValue()) 218 | } 219 | return nil 220 | } 221 | 222 | func jterm2term(term jsonld.Term) Term { 223 | switch term := term.(type) { 224 | case *jsonld.BlankNode: 225 | // id, _ := strconv.Atoi(term.RawValue()) 226 | return NewBlankNode(term.RawValue()) 227 | case *jsonld.Literal: 228 | if len(term.Language) > 0 { 229 | return NewLiteralWithLanguage(term.RawValue(), term.Language) 230 | } 231 | if term.Datatype != nil && len(term.Datatype.String()) > 0 { 232 | return NewLiteralWithDatatype(term.Value, NewResource(term.Datatype.RawValue())) 233 | } 234 | return NewLiteral(term.Value) 235 | case *jsonld.Resource: 236 | return NewResource(term.RawValue()) 237 | } 238 | return nil 239 | } 240 | 241 | func term2jterm(term Term) jsonld.Term { 242 | switch term := term.(type) { 243 | case *BlankNode: 244 | return jsonld.NewBlankNode(term.RawValue()) 245 | case *Literal: 246 | if len(term.Language) > 0 { 247 | return jsonld.NewLiteralWithLanguage(term.Value, term.Language) 248 | } 249 | if term.Datatype != nil && len(term.Datatype.String()) > 0 { 250 | return jsonld.NewLiteralWithDatatype(term.Value, jsonld.NewResource(debrack(term.Datatype.String()))) 251 | } 252 | return jsonld.NewLiteral(term.Value) 253 | case *Resource: 254 | return jsonld.NewResource(term.RawValue()) 255 | } 256 | return nil 257 | } 258 | 259 | func encodeTerm(iterm Term) string { 260 | switch term := iterm.(type) { 261 | case *Resource: 262 | return fmt.Sprintf("<%s>", term.URI) 263 | case *Literal: 264 | return term.String() 265 | case *BlankNode: 266 | return term.String() 267 | } 268 | 269 | return "" 270 | } 271 | 272 | func atLang(lang string) string { 273 | if len(lang) > 0 { 274 | if strings.HasPrefix(lang, "@") { 275 | return lang 276 | } 277 | return "@" + lang 278 | } 279 | return "" 280 | } 281 | 282 | // splitPrefix takes a given URI and splits it into a base URI and a local name 283 | func splitPrefix(uri string) (base string, name string) { 284 | index := strings.LastIndex(uri, "#") + 1 285 | 286 | if index > 0 { 287 | return uri[:index], uri[index:] 288 | } 289 | 290 | index = strings.LastIndex(uri, "/") + 1 291 | 292 | if index > 0 { 293 | return uri[:index], uri[index:] 294 | } 295 | 296 | return "", uri 297 | } 298 | 299 | func brack(s string) string { 300 | if len(s) > 0 && s[0] == '<' { 301 | return s 302 | } 303 | if len(s) > 0 && s[len(s)-1] == '>' { 304 | return s 305 | } 306 | return "<" + s + ">" 307 | } 308 | 309 | func debrack(s string) string { 310 | if len(s) < 2 { 311 | return s 312 | } 313 | if s[0] != '<' { 314 | return s 315 | } 316 | if s[len(s)-1] != '>' { 317 | return s 318 | } 319 | return s[1 : len(s)-1] 320 | } 321 | 322 | func defrag(s string) string { 323 | lst := strings.Split(s, "#") 324 | if len(lst) != 2 { 325 | return s 326 | } 327 | return lst[0] 328 | } 329 | -------------------------------------------------------------------------------- /term_test.go: -------------------------------------------------------------------------------- 1 | package rdf2go 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type fakeTerm struct { 10 | URI string 11 | } 12 | 13 | func (tt *fakeTerm) Equal(term Term) bool { 14 | return false 15 | } 16 | 17 | func (tt *fakeTerm) String() string { 18 | return "" 19 | } 20 | 21 | func (tt *fakeTerm) RawValue() string { 22 | return "" 23 | } 24 | 25 | func TestTermResourceEqual(t *testing.T) { 26 | t1 := NewResource(testUri) 27 | assert.True(t, t1.Equal(NewResource(testUri))) 28 | assert.False(t, t1.Equal(NewLiteral("test1"))) 29 | } 30 | 31 | func TestTermResourceWithHash(t *testing.T) { 32 | rURI := "http://example.com/foo#i" 33 | r1 := NewResource(rURI) 34 | assert.Equal(t, rURI, r1.RawValue()) 35 | } 36 | 37 | func TestTermLiteral(t *testing.T) { 38 | str := "value" 39 | t1 := NewLiteral(str) 40 | assert.Equal(t, "\""+str+"\"", t1.String()) 41 | assert.Equal(t, str, t1.RawValue()) 42 | } 43 | 44 | func TestTermLiteralEqual(t *testing.T) { 45 | t1 := NewLiteralWithLanguage("test1", "en") 46 | assert.False(t, t1.Equal(NewResource(testUri))) 47 | 48 | assert.True(t, t1.Equal(NewLiteralWithLanguage("test1", "en"))) 49 | assert.False(t, t1.Equal(NewLiteralWithLanguage("test2", "en"))) 50 | 51 | assert.True(t, t1.Equal(NewLiteralWithLanguage("test1", "en"))) 52 | assert.False(t, t1.Equal(NewLiteralWithLanguage("test1", "fr"))) 53 | 54 | t1 = NewLiteralWithDatatype("test1", NewResource("http://www.w3.org/2001/XMLSchema#string")) 55 | assert.False(t, t1.Equal(NewLiteral("test1"))) 56 | assert.True(t, t1.Equal(NewLiteralWithDatatype("test1", NewResource("http://www.w3.org/2001/XMLSchema#string")))) 57 | assert.False(t, t1.Equal(NewLiteralWithDatatype("test1", NewResource("http://www.w3.org/2001/XMLSchema#int")))) 58 | } 59 | 60 | func TestTermNewLiteralWithLanguage(t *testing.T) { 61 | s := NewLiteralWithLanguage("test", "en") 62 | assert.Equal(t, "\"test\"@en", s.String()) 63 | } 64 | 65 | func TestTermNewLiteralWithDatatype(t *testing.T) { 66 | s := NewLiteralWithDatatype("test", NewResource("http://www.w3.org/2001/XMLSchema#string")) 67 | assert.Equal(t, "\"test\"^^", s.String()) 68 | } 69 | 70 | func TestTermNewBlankNode(t *testing.T) { 71 | id := NewBlankNode("n1") 72 | assert.Equal(t, "_:n1", id.String()) 73 | assert.Equal(t, "n1", id.RawValue()) 74 | } 75 | 76 | func TestTermNewAnonNode(t *testing.T) { 77 | id := NewAnonNode() 78 | assert.True(t, len(id.String()) > 3) 79 | assert.True(t, id.String()[:3] == "_:n") 80 | } 81 | 82 | func TestTermBNodeEqual(t *testing.T) { 83 | id1 := NewBlankNode("n1") 84 | id2 := NewBlankNode("n1") 85 | assert.True(t, id1.Equal(id2)) 86 | id3 := NewBlankNode("n2") 87 | assert.False(t, id1.Equal(id3)) 88 | assert.False(t, id1.Equal(NewResource(testUri))) 89 | } 90 | 91 | func TestTermNils(t *testing.T) { 92 | t1 := Term(&fakeTerm{URI: testUri}) 93 | assert.Nil(t, term2rdf(t1)) 94 | assert.Nil(t, term2jterm(t1)) 95 | } 96 | 97 | func TestAtLang(t *testing.T) { 98 | assert.Equal(t, "@en", atLang("en")) 99 | assert.Equal(t, "@en", atLang("@en")) 100 | assert.Equal(t, "@e", atLang("e")) 101 | assert.Equal(t, "", atLang("")) 102 | } 103 | 104 | func TestEncodeTerm(t *testing.T) { 105 | iterm := NewResource(testUri) 106 | assert.Equal(t, iterm.String(), encodeTerm(iterm)) 107 | iterm = NewBlankNode("n1") 108 | assert.Equal(t, iterm.String(), encodeTerm(iterm)) 109 | iterm = NewLiteral("value") 110 | assert.Equal(t, iterm.String(), encodeTerm(iterm)) 111 | iterm = Term(&fakeTerm{URI: testUri}) 112 | assert.Equal(t, "", encodeTerm(iterm)) 113 | } 114 | 115 | func TestSplitPrefix(t *testing.T) { 116 | hashUri := testUri + "#me" 117 | base, name := splitPrefix(hashUri) 118 | assert.Equal(t, testUri+"#", base) 119 | assert.Equal(t, "me", name) 120 | 121 | slashUri := testUri + "/foaf" 122 | base, name = splitPrefix(slashUri) 123 | assert.Equal(t, testUri+"/", base) 124 | assert.Equal(t, "foaf", name) 125 | 126 | badUri := "test" 127 | base, name = splitPrefix(badUri) 128 | assert.Equal(t, "", base) 129 | assert.Equal(t, badUri, name) 130 | 131 | } 132 | 133 | func TestRDFBrack(t *testing.T) { 134 | assert.Equal(t, "", brack("test")) 135 | assert.Equal(t, "", brack("test>")) 137 | } 138 | 139 | func TestRDFDebrack(t *testing.T) { 140 | assert.Equal(t, "a", debrack("a")) 141 | assert.Equal(t, "test", debrack("")) 142 | assert.Equal(t, "", debrack("test>")) 144 | } 145 | 146 | func TestDefrag(t *testing.T) { 147 | assert.Equal(t, "test", defrag("test")) 148 | assert.Equal(t, "test", defrag("test#me")) 149 | } 150 | -------------------------------------------------------------------------------- /triple.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Kier Davis 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 5 | associated documentation files (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 7 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial 11 | portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 14 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES 16 | OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 17 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | */ 19 | 20 | package rdf2go 21 | 22 | import ( 23 | "fmt" 24 | ) 25 | 26 | // Triple contains a subject, a predicate and an object term. 27 | type Triple struct { 28 | Subject Term 29 | Predicate Term 30 | Object Term 31 | } 32 | 33 | // NewTriple returns a new triple with the given subject, predicate and object. 34 | func NewTriple(subject Term, predicate Term, object Term) (triple *Triple) { 35 | return &Triple{ 36 | Subject: subject, 37 | Predicate: predicate, 38 | Object: object, 39 | } 40 | } 41 | 42 | // String returns the NTriples representation of this triple. 43 | func (triple Triple) String() (str string) { 44 | subjStr := "nil" 45 | if triple.Subject != nil { 46 | subjStr = triple.Subject.String() 47 | } 48 | 49 | predStr := "nil" 50 | if triple.Predicate != nil { 51 | predStr = triple.Predicate.String() 52 | } 53 | 54 | objStr := "nil" 55 | if triple.Object != nil { 56 | objStr = triple.Object.String() 57 | } 58 | 59 | return fmt.Sprintf("%s %s %s .", subjStr, predStr, objStr) 60 | } 61 | 62 | // Equal returns this triple is equivalent to the argument. 63 | func (triple Triple) Equal(other *Triple) bool { 64 | return triple.Subject.Equal(other.Subject) && 65 | triple.Predicate.Equal(other.Predicate) && 66 | triple.Object.Equal(other.Object) 67 | } 68 | -------------------------------------------------------------------------------- /triple_test.go: -------------------------------------------------------------------------------- 1 | package rdf2go 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | var one = NewTriple(NewResource("a"), NewResource("b"), NewResource("c")) 9 | 10 | func TestTripleEquals(t *testing.T) { 11 | assert.True(t, one.Equal(NewTriple(NewResource("a"), NewResource("b"), NewResource("c")))) 12 | } 13 | 14 | func TestTripleString(t *testing.T) { 15 | assert.Equal(t, " .", one.String()) 16 | } 17 | --------------------------------------------------------------------------------