Experimental: Add RDF package for JSONLD context definitions.

This package will be the frontend for reading the JSONLD context
descriptions that specify ActivityStreams vocabularies. This will allow
ingesting publicly hosted or manually-created vocabularies and
generating an internal representation for later code generation.
このコミットが含まれているのは:
Cory Slep 2018-11-17 16:33:42 +01:00
コミット 8795587007
2個のファイルの変更290行の追加0行の削除

106
tools/exp/rdf/parse.go ノーマルファイル
ファイルの表示

@ -0,0 +1,106 @@
package rdf
import (
"fmt"
)
const (
JSON_LD_CONTEXT = "@context"
)
type JSONLD map[string]interface{}
type RDFGetter interface {
// GetFor gets RDFNodes based on a context's string.
GetFor(s string) ([]RDFNode, error)
// GetAliased gets based on a context string and its alias.
GetAliased(alias, s string) ([]RDFNode, error)
// GetAliasedObject gets based on a context object and its alias and
// definition.
GetAliasedObject(alias string, object map[string]interface{}) ([]RDFNode, error)
}
type ParseContext interface{}
type RDFNode interface {
Apply(key string, value interface{}, ctx ParseContext) (bool, error)
}
// ParseJSONLDContext implements a super basic JSON-LD @context parsing
// algorithm in order to build a tree that can parse the rest of the document.
func ParseJSONLDContext(rdfGetter RDFGetter, input JSONLD) (nodes []RDFNode, err error) {
i, ok := input[JSON_LD_CONTEXT]
if !ok {
err = fmt.Errorf("no @context in input")
return
}
if inArray, ok := i.([]interface{}); ok {
// @context is an array
for _, iVal := range inArray {
if valMap, ok := iVal.(map[string]interface{}); ok {
// Element is a JSON Object (dictionary)
for alias, val := range valMap {
if s, ok := val.(string); ok {
var n []RDFNode
n, err = rdfGetter.GetAliased(alias, s)
if err != nil {
return
}
nodes = append(nodes, n...)
} else if aliasedMap, ok := val.(map[string]interface{}); ok {
var n []RDFNode
n, err = rdfGetter.GetAliasedObject(alias, aliasedMap)
if err != nil {
return
}
nodes = append(nodes, n...)
} else {
err = fmt.Errorf("@context value in dict in array is neither a dict nor a string")
return
}
}
} else if s, ok := iVal.(string); ok {
// Element is a single value
var n []RDFNode
n, err = rdfGetter.GetFor(s)
if err != nil {
return
}
nodes = append(nodes, n...)
} else {
err = fmt.Errorf("@context value in array is neither a dict nor a string")
return
}
}
} else if inMap, ok := i.(map[string]interface{}); ok {
// @context is a JSON object (dictionary)
for alias, iVal := range inMap {
if s, ok := iVal.(string); ok {
var n []RDFNode
n, err = rdfGetter.GetAliased(alias, s)
if err != nil {
return
}
nodes = append(nodes, n...)
} else if aliasedMap, ok := iVal.(map[string]interface{}); ok {
var n []RDFNode
n, err = rdfGetter.GetAliasedObject(alias, aliasedMap)
if err != nil {
return
}
nodes = append(nodes, n...)
} else {
err = fmt.Errorf("@context value in dict is neither a dict nor a string")
return
}
}
} else {
// @context is a single value
s, ok := i.(string)
if !ok {
err = fmt.Errorf("single @context value is not a string")
}
return rdfGetter.GetFor(s)
}
return
}

184
tools/exp/rdf/rdf.go ノーマルファイル
ファイルの表示

@ -0,0 +1,184 @@
package rdf
import (
"fmt"
"strings"
"sync"
)
const (
ALIAS_DELIMITER = ":"
ID = "@id"
)
// Ontology returns different RDF "actions" or "handlers" that are able to
// interpret the schema definitions as actions upon a set of data, specific
// for this ontology.
type Ontology interface {
// String representation of this ontology.
String() string
// Load loads the entire ontology.
Load() ([]RDFNode, error)
// Load loads the entire ontology with a specific alias.
LoadAsAlias(s string) ([]RDFNode, error)
// LoadElement loads a specific element of the ontology by name. The
// payload may be nil.
LoadElement(name string, payload map[string]interface{}) ([]RDFNode, error)
}
// aliasedNode represents a context element that has a special reserved alias.
type aliasedNode struct {
Alias string
Nodes []RDFNode
}
// RDFRegistry implements RDFGetter and manages the different ontologies needed
// to determine the generated Go code.
type RDFRegistry struct {
ontologies map[string]Ontology
aliases map[string]string
aliasedNodes map[string]aliasedNode
mu sync.Mutex
}
// setAlias sets an alias for a string.
func (r *RDFRegistry) setAlias(alias, s string) error {
if _, ok := r.aliases[alias]; ok {
return fmt.Errorf("already have alias for %s", alias)
}
r.aliases[alias] = s
return nil
}
// setAliasedNode sets an alias for a node.
func (r *RDFRegistry) setAliasedNode(alias string, nodes []RDFNode) error {
if _, ok := r.aliasedNodes[alias]; ok {
return fmt.Errorf("already have aliased node for %s", alias)
}
r.aliasedNodes[alias] = aliasedNode{
Alias: alias,
Nodes: nodes,
}
return nil
}
// getOngology resolves an alias to a particular Ontology.
func (r *RDFRegistry) getOntology(alias string) (Ontology, error) {
if ontologyName, ok := r.aliases[alias]; !ok {
return nil, fmt.Errorf("missing alias %q", alias)
} else if ontology, ok := r.ontologies[ontologyName]; !ok {
return nil, fmt.Errorf("alias %q resolved but missing ontology with name %q", alias, ontologyName)
} else {
return ontology, nil
}
}
// loadElement will handle the aliasing of an ontology and retrieve the nodes
// required for a specific element within that ontology.
func (r *RDFRegistry) loadElement(alias, element string, payload map[string]interface{}) (n []RDFNode, e error) {
if ontName, ok := r.aliases[alias]; !ok {
e = fmt.Errorf("no alias to ontology for %s", alias)
return
} else if ontology, ok := r.ontologies[ontName]; !ok {
e = fmt.Errorf("no ontology named %s for alias %s", ontName, alias)
return
} else {
n, e = ontology.LoadElement(element, payload)
return
}
}
// AddOntology adds an RDF ontology to the registry.
func (r *RDFRegistry) AddOntology(s string, o Ontology) error {
r.mu.Lock()
defer r.mu.Unlock()
if r.ontologies == nil {
r.ontologies = make(map[string]Ontology, 1)
}
if _, ok := r.ontologies[s]; ok {
return fmt.Errorf("ontology already registered for %q", s)
}
r.ontologies[s] = o
return nil
}
// GetFor gets RDFKeyers and RDFValuers based on a context's string.
//
// Implements RDFGetter.
func (r *RDFRegistry) GetFor(s string) (n []RDFNode, e error) {
r.mu.Lock()
defer r.mu.Unlock()
ontology, ok := r.ontologies[s]
if !ok {
e = fmt.Errorf("no ontology for %s", s)
return
}
return ontology.Load()
}
// GetAliased gets RDFKeyers and RDFValuers based on a context string and its
// alias.
//
// Implements RDFGetter.
func (r *RDFRegistry) GetAliased(alias, s string) (n []RDFNode, e error) {
r.mu.Lock()
defer r.mu.Unlock()
strs := strings.Split(s, ALIAS_DELIMITER)
if len(strs) == 1 {
if e = r.setAlias(alias, s); e != nil {
return
}
return r.GetFor(s)
} else if len(strs) == 2 {
var o Ontology
o, e = r.getOntology(strs[0])
if e != nil {
return
}
n, e = o.LoadElement(strs[1], nil)
return
} else {
e = fmt.Errorf("too many delimiters in %s", s)
return
}
}
// GetAliasedObject gets RDFKeyers and RDFValuers based on a context object and
// its alias and definition.
//
// Implements RDFGetter.
func (r *RDFRegistry) GetAliasedObject(alias string, object map[string]interface{}) (n []RDFNode, e error) {
r.mu.Lock()
defer r.mu.Unlock()
var iObj interface{} = object
if obj, ok := iObj.(map[string]string); !ok {
e = fmt.Errorf("object in GetAliasedObject must be a map[string]string")
return
} else {
element, ok := obj[ID]
if !ok {
e = fmt.Errorf("aliased object does not have %s value", ID)
return
}
var nodes []RDFNode
strs := strings.Split(element, ALIAS_DELIMITER)
if len(strs) == 1 {
n, e = r.GetFor(strs[0])
} else if len(strs) == 2 {
var o Ontology
o, e = r.getOntology(strs[0])
if e != nil {
return
}
n, e = o.LoadElement(strs[1], object)
return
}
if e != nil {
return
}
if e = r.setAliasedNode(alias, nodes); e != nil {
return
}
return
}
}