Skip to content

Commit

Permalink
Add turtle, ntriples and nquads.
Browse files Browse the repository at this point in the history
  • Loading branch information
q-uint committed Aug 31, 2023
1 parent ecda242 commit 6af02b6
Show file tree
Hide file tree
Showing 539 changed files with 7,295 additions and 103 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
on: [ push ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.21.0'
- run: go test -v ./...
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.PHONY: test test-cover gen gen-ic fmt
.PHONY: test fmt

test:
go test -v -cover ./... --count=5
go test -v -cover ./...

fmt:
go mod tidy
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## References

- [RDF 1.1 Concepts and Abstract Syntax](https://www.w3.org/TR/2014/REC-rdf11-concepts-20140225/)
- [RDF 1.1 Turtle](https://www.w3.org/TR/2014/REC-turtle-20140225/)
- [RDF 1.1 N-Triples](https://www.w3.org/TR/2014/REC-n-triples-20140225/)
- [RDF 1.1 N-Quads](https://www.w3.org/TR/2014/REC-n-quads-20140225/)
- [RDF Test Cases](https://www.w3.org/TR/2014/NOTE-rdf11-testcases-20140225/)
62 changes: 62 additions & 0 deletions datatypes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package rdf

import "fmt"

type DataType string

// INFO on datatypes: https://www.w3.org/TR/xmlschema11-2/type-hierarchy-201104.longdesc.html
const (
XSD = "http://www.w3.org/2001/XMLSchema#"
XSDAnyType DataType = XSD + "anyType"
XSDAnyURI DataType = XSD + "anyURI"
XSDBase64Binary DataType = XSD + "base64Binary"
XSDBoolean DataType = XSD + "boolean"
XSDDate DataType = XSD + "date"
XDSDecimal DataType = XSD + "decimal"
XSDDouble DataType = XSD + "double"
XSDDuration DataType = XSD + "duration"
XSDFloat DataType = XSD + "float"
XSDString DataType = XSD + "string"
// TODO: add "other" build-in atomic types, e.g. dateTimeStamp etc.

XSDNS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
XSDNSString = XSDNS + "langString"
)

// NativeType returns the native type of the given value.
// Returns true if the value was converted, otherwise false.
func (d DataType) NativeType(value string) (any, bool, error) {
switch d {
case XSDAnyType, XSDString:
return value, true, nil
case XSDBoolean:
switch value {
case "true", "false":
return value[0] == 't', true, nil
}
return nil, false, fmt.Errorf("invalid boolean value: %q", value)
default:
return value, false, nil
}
}

// Validate returns true if the given value is valid for the datatype.
func (d DataType) Validate(v any, acceptString bool) bool {
if _, ok := v.(string); acceptString && ok {
// TODO: validate string against the datatype.
return true
}

switch d {
case XSDAnyType:
return true
case XSDString:
_, ok := v.(string)
return ok
case XSDBoolean:
_, ok := v.(bool)
return ok
default:
return false
}
}
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module github.com/0x51-dev/rdf

go 1.20
go 1.21.0

require github.com/0x51-dev/upeg v0.0.0-20230714211724-9f58040921d7
require github.com/0x51-dev/upeg v0.0.0-20230829161504-bdf1e5e3b220

require github.com/0x51-dev/ri v0.0.0-20230708135038-029eea38546f
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
github.com/0x51-dev/upeg v0.0.0-20230714211724-9f58040921d7 h1:WuGofvGedJe7v53MPNiympoKKydmVkeijzQYtNy0WZc=
github.com/0x51-dev/upeg v0.0.0-20230714211724-9f58040921d7/go.mod h1:ts9/Zafxb9W9drZFTmQNMR7kLOyHyBw37NuowyiDork=
github.com/0x51-dev/ri v0.0.0-20230708135038-029eea38546f h1:6bWTNS1c7TqLdQEruMwLvDQ7Jn+vJniE/dqeTmOenVA=
github.com/0x51-dev/ri v0.0.0-20230708135038-029eea38546f/go.mod h1:jw6tYgXeAqwcKnvjTOI6rfGEhLj3R5PSMUbRlKL2DvM=
github.com/0x51-dev/upeg v0.0.0-20230829161504-bdf1e5e3b220 h1:Agg2TMCsToB6SOGEHNa9EAJEG32Ej9pV+g6lRTlaQxg=
github.com/0x51-dev/upeg v0.0.0-20230829161504-bdf1e5e3b220/go.mod h1:ts9/Zafxb9W9drZFTmQNMR7kLOyHyBw37NuowyiDork=
106 changes: 106 additions & 0 deletions internal/testsuite/manifest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package testsuite

import (
"fmt"
ttl "github.com/0x51-dev/rdf/turtle"
"github.com/0x51-dev/rdf/turtle/grammar/ir"
)

type ApprovalType string

const (
Approved ApprovalType = "rdft:Approved"
Proposed ApprovalType = "rdft:Proposed"
Rejected ApprovalType = "rdft:Rejected"
)

type Manifest struct {
Keys []string
Entries map[string]*Test
}

func LoadManifest(raw string) (*Manifest, error) {
doc, err := ttl.ParseDocument(raw)
if err != nil {
return nil, err
}
sm, err := doc.SubjectMap()
if err != nil {
return nil, err
}
manifest, ok := sm[""]
if !ok {
return nil, fmt.Errorf("manifest: no root")
}
pom, err := manifest.PredicateObjectMap()
if err != nil {
return nil, err
}
mfEntries, ok := pom["mf:entries"]
if !ok {
return nil, fmt.Errorf("manifest: no entries")
}
var keys []string
entries := make(map[string]*Test)
switch t := mfEntries[0].(type) {
case ir.Collection:
for _, entry := range t {
var name string
switch t := entry.(type) {
case *ir.IRI:
name = t.Value
default:
return nil, fmt.Errorf("manifest: entry name not an IRI")
}
keys = append(keys, name)
entries[name], err = NewTest(sm[name])
if err != nil {
return nil, err
}
}
default:
return nil, fmt.Errorf("manifest: entries not a collection")
}
return &Manifest{Keys: keys, Entries: entries}, nil
}

type Test struct {
Type string
Name string
Comment string
Approval ApprovalType
Action string
}

func NewTest(triple ir.Triple) (*Test, error) {
pom, err := triple.PredicateObjectMap()
if err != nil {
return nil, err
}
typ, ok := pom["rdf:type"]
if !ok {
// Alternate syntax used in NQuads tests.
a, ok := pom["a"]
if !ok {
return nil, fmt.Errorf("test: no type")
}
typ = a
}
name, ok := pom["mf:name"]
if !ok {
return nil, fmt.Errorf("test: no name")
}
comment := pom["rdfs:comment"]
approval := pom["rdft:approval"]
action, ok := pom["mf:action"]
if !ok {
return nil, fmt.Errorf("test: no action")
}
return &Test{
Type: typ.String(),
Name: (name[0].(*ir.StringLiteral)).Value,
Comment: comment.String(),
Approval: ApprovalType(approval.String()),
Action: (action[0].(*ir.IRI)).Value,
}, nil
}
18 changes: 18 additions & 0 deletions manifest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package rdf_test

import (
_ "embed"
"github.com/0x51-dev/rdf/internal/testsuite"
"testing"
)

var (
//go:embed ntriples/testdata/suite/manifest.ttl
ntriples string
)

func TestLoadManifest(t *testing.T) {
if _, err := testsuite.LoadManifest(ntriples); err != nil {
t.Fatal(err)
}
}
113 changes: 113 additions & 0 deletions node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package rdf

import (
"fmt"
"strings"
)

// BlankNode identifiers are local identifiers.
type BlankNode struct {
Attribute string
}

func (b *BlankNode) toObject(_ bool) (map[string]any, error) {
return map[string]any{
"@id": b.Attribute,
}, nil
}

type IRIReference struct {
Value string
}

func (r *IRIReference) toObject(_ bool) (map[string]any, error) {
return map[string]any{
"@id": r.Value,
}, nil
}

// Literal is used to represent values such as strings, numbers, and dates.
type Literal struct {
// Value is a Unicode string.
Value string
// Datatype is an IRI identifying a datatype that determines how the lexical form maps to a literal value.
Datatype DataType
// Language can only be present if Datatype is XSDNSString.
Language string
}

func (l *Literal) toObject(nativeTypes bool) (map[string]any, error) {
if l.Language != "" {
if l.Datatype != "" && l.Datatype != XSDNSString {
return nil, fmt.Errorf("invalid datatype for language literal: %s", l.Datatype)
}
return map[string]any{
"@value": l.Value,
"@language": l.Language,
}, nil
}
if l.Datatype != XSDString {
if nativeTypes {
v, _, err := l.Datatype.NativeType(l.Value)
if err != nil {
return nil, err
}
return map[string]any{
"@value": v,
"@datatype": l.Datatype,
}, nil
}
return map[string]any{
"@value": l.Value,
"@datatype": l.Datatype,
}, nil
}
return map[string]any{
"@value": l.Value,
}, nil
}

type Node interface {
toObject(nativeTypes bool) (map[string]any, error)
}

func FromObject(obj any, acceptString bool) (Node, error) {
var id string
switch obj := obj.(type) {
case map[string]any:
if v, ok := obj["@value"]; ok {
var literal = &Literal{
Value: fmt.Sprintf("%v", v),
Datatype: XSDString,
}
if datatype, ok := obj["@datatype"]; ok {
s, ok := datatype.(string)
if !ok {
return nil, fmt.Errorf("invalid @datatype attribute: %T", datatype)
}
literal.Datatype = DataType(s)
}
if !literal.Datatype.Validate(v, acceptString) {
return nil, fmt.Errorf("invalid @value for datatype %s: %v", literal.Datatype, v)
}
return literal, nil
}

if v, ok := obj["@id"]; ok {
id, ok = v.(string)
if !ok {
return nil, fmt.Errorf("invalid @id attribute: %T", v)
}
} else {
return nil, fmt.Errorf("missing @id attribute")
}
case string:
id = obj
default:
return nil, fmt.Errorf("unknown object type: %T", obj)
}
if strings.HasPrefix(id, "_:") {
return &BlankNode{Attribute: id}, nil
}
return &IRIReference{Value: id}, nil
}
Loading

0 comments on commit 6af02b6

Please sign in to comment.