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.
このコミットが含まれているのは:
コミット
8795587007
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
読み込み中…
新しいイシューから参照