326 行
8.7 KiB
Go
326 行
8.7 KiB
Go
package rdf
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
ALIAS_DELIMITER = ":"
|
|
HTTP = "http"
|
|
HTTPS = "https"
|
|
ID = "@id"
|
|
)
|
|
|
|
// IsKeyApplicable returns true if the key has a spec or alias prefix and the
|
|
// property is equal to the desired name.
|
|
//
|
|
// If 'alias' is an empty string, it is ignored.
|
|
func IsKeyApplicable(key, spec, alias, name string) bool {
|
|
if key == spec+name {
|
|
return true
|
|
} else if len(alias) > 0 {
|
|
strs := strings.Split(key, ALIAS_DELIMITER)
|
|
if len(strs) > 1 && strs[0] != HTTP && strs[0] != HTTPS {
|
|
return strs[0] == alias && strs[1] == name
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// SplitAlias splits a possibly-aliased string, without splitting on the colon
|
|
// if it is part of the http or https spec.
|
|
func SplitAlias(s string) []string {
|
|
strs := strings.Split(s, ALIAS_DELIMITER)
|
|
if len(strs) == 1 {
|
|
return strs
|
|
} else if strs[0] == HTTP || strs[0] == HTTPS {
|
|
return []string{s}
|
|
} else {
|
|
return strs
|
|
}
|
|
}
|
|
|
|
// ToHttpAndHttps converts a URI to both its http and https versions.
|
|
func ToHttpAndHttps(s string) (http, https string, err error) {
|
|
// Trailing fragments are not preserved by url.Parse, so we
|
|
// need to do proper bookkeeping and preserve it if present.
|
|
hasFragment := s[len(s)-1] == '#'
|
|
var specUri *url.URL
|
|
specUri, err = url.Parse(s)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
// HTTP
|
|
httpScheme := *specUri
|
|
httpScheme.Scheme = HTTP
|
|
http = httpScheme.String()
|
|
// HTTPS
|
|
httpsScheme := *specUri
|
|
httpsScheme.Scheme = HTTPS
|
|
https = httpsScheme.String()
|
|
if hasFragment {
|
|
http += "#"
|
|
https += "#"
|
|
}
|
|
return
|
|
}
|
|
|
|
// joinAlias combines a string and prepends an RDF alias to it.
|
|
func joinAlias(alias, s string) string {
|
|
return fmt.Sprintf("%s%s%s", alias, ALIAS_DELIMITER, s)
|
|
}
|
|
|
|
// 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 {
|
|
// SpecURI refers to the URI location of this ontology.
|
|
SpecURI() string
|
|
|
|
// The Load methods deal with determining how best to apply an ontology
|
|
// based on the context specified by the data. This is before the data
|
|
// is actually processed.
|
|
|
|
// Load loads the entire ontology.
|
|
Load() ([]RDFNode, error)
|
|
// LoadAsAlias loads the entire ontology with a specific alias.
|
|
LoadAsAlias(s string) ([]RDFNode, error)
|
|
// LoadSpecificAsAlias loads a specific element of the ontology by
|
|
// being able to handle the specific alias as its name instead.
|
|
LoadSpecificAsAlias(alias, name string) ([]RDFNode, error)
|
|
// LoadElement loads a specific element of the ontology based on the
|
|
// object definition.
|
|
LoadElement(name string, payload map[string]interface{}) ([]RDFNode, error)
|
|
|
|
// The Get methods deal with determining how best to apply an ontology
|
|
// during processing. This is a result of certain nodes having highly
|
|
// contextual effects.
|
|
|
|
// GetByName returns an RDFNode associated with the given name. Note
|
|
// that the name may either be fully-qualified (in the case it was not
|
|
// aliased) or it may be just the element name (in the case it was
|
|
// aliased).
|
|
GetByName(name string) (RDFNode, error)
|
|
}
|
|
|
|
// aliasedNode represents a context element that has a special reserved alias.
|
|
type aliasedNode struct {
|
|
Alias string
|
|
Nodes []RDFNode
|
|
}
|
|
|
|
// RDFRegistry 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
|
|
}
|
|
|
|
// NewRDFRegistry returns a new RDFRegistry.
|
|
func NewRDFRegistry() *RDFRegistry {
|
|
return &RDFRegistry{
|
|
ontologies: make(map[string]Ontology),
|
|
aliases: make(map[string]string),
|
|
aliasedNodes: make(map[string]aliasedNode),
|
|
}
|
|
}
|
|
|
|
// clone creates a new RDFRegistry keeping only the ontologies.
|
|
func (r *RDFRegistry) clone() *RDFRegistry {
|
|
c := NewRDFRegistry()
|
|
for k, v := range r.ontologies {
|
|
c.ontologies[k] = v
|
|
}
|
|
return c
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// AddOntology adds an RDF ontology to the registry.
|
|
func (r *RDFRegistry) AddOntology(o Ontology) error {
|
|
if r.ontologies == nil {
|
|
r.ontologies = make(map[string]Ontology, 1)
|
|
}
|
|
specString := o.SpecURI()
|
|
httpSpec, httpsSpec, err := ToHttpAndHttps(specString)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, ok := r.ontologies[httpSpec]; ok {
|
|
return fmt.Errorf("ontology already registered for %q", httpSpec)
|
|
}
|
|
if _, ok := r.ontologies[httpsSpec]; ok {
|
|
return fmt.Errorf("ontology already registered for %q", httpsSpec)
|
|
}
|
|
r.ontologies[httpSpec] = o
|
|
r.ontologies[httpsSpec] = o
|
|
return nil
|
|
}
|
|
|
|
// reset clears the registry in preparation for loading another JSONLD context.
|
|
func (r *RDFRegistry) reset() {
|
|
r.aliases = make(map[string]string)
|
|
r.aliasedNodes = make(map[string]aliasedNode)
|
|
}
|
|
|
|
// getFor gets RDFKeyers based on a context's string.
|
|
//
|
|
// Package public.
|
|
func (r *RDFRegistry) getFor(s string) (n []RDFNode, e error) {
|
|
ontology, ok := r.ontologies[s]
|
|
if !ok {
|
|
e = fmt.Errorf("no ontology for %s", s)
|
|
return
|
|
}
|
|
return ontology.Load()
|
|
}
|
|
|
|
// getForAliased gets RDFKeyers based on a context's string.
|
|
//
|
|
// Private to this file.
|
|
func (r *RDFRegistry) getForAliased(alias, s string) (n []RDFNode, e error) {
|
|
ontology, ok := r.ontologies[s]
|
|
if !ok {
|
|
e = fmt.Errorf("no ontology for %s", s)
|
|
return
|
|
}
|
|
return ontology.LoadAsAlias(alias)
|
|
}
|
|
|
|
// getAliased gets RDFKeyers based on a context string and its
|
|
// alias.
|
|
//
|
|
// Package public.
|
|
func (r *RDFRegistry) getAliased(alias, s string) (n []RDFNode, e error) {
|
|
strs := SplitAlias(s)
|
|
if len(strs) == 1 {
|
|
if e = r.setAlias(alias, s); e != nil {
|
|
return
|
|
}
|
|
return r.getForAliased(alias, s)
|
|
} else if len(strs) == 2 {
|
|
var o Ontology
|
|
o, e = r.getOntology(strs[0])
|
|
if e != nil {
|
|
return
|
|
}
|
|
n, e = o.LoadSpecificAsAlias(alias, strs[1])
|
|
return
|
|
} else {
|
|
e = fmt.Errorf("too many delimiters in %s", s)
|
|
return
|
|
}
|
|
}
|
|
|
|
// getAliasedObject gets RDFKeyers based on a context object and
|
|
// its alias and definition.
|
|
//
|
|
// Package public.
|
|
func (r *RDFRegistry) getAliasedObject(alias string, object map[string]interface{}) (n []RDFNode, e error) {
|
|
raw, ok := object[ID]
|
|
if !ok {
|
|
e = fmt.Errorf("aliased object does not have %s value", ID)
|
|
return
|
|
}
|
|
if element, ok := raw.(string); !ok {
|
|
e = fmt.Errorf("element in getAliasedObject must be a string")
|
|
return
|
|
} else {
|
|
strs := SplitAlias(element)
|
|
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(alias, object)
|
|
}
|
|
if e != nil {
|
|
return
|
|
}
|
|
e = r.setAliasedNode(alias, n)
|
|
return
|
|
}
|
|
}
|
|
|
|
// getNode fetches a node based on a string. It may be aliased or not.
|
|
//
|
|
// Package public.
|
|
func (r *RDFRegistry) getNode(s string) (n RDFNode, e error) {
|
|
strs := SplitAlias(s)
|
|
if len(strs) == 2 {
|
|
if ontName, ok := r.aliases[strs[0]]; !ok {
|
|
e = fmt.Errorf("no alias to ontology for %s", strs[0])
|
|
return
|
|
} else if ontology, ok := r.ontologies[ontName]; !ok {
|
|
e = fmt.Errorf("no ontology named %s for alias %s", ontName, strs[0])
|
|
return
|
|
} else {
|
|
n, e = ontology.GetByName(strs[1])
|
|
return
|
|
}
|
|
} else if len(strs) == 1 {
|
|
for _, ontology := range r.ontologies {
|
|
if strings.HasPrefix(s, ontology.SpecURI()) {
|
|
n, e = ontology.GetByName(s)
|
|
return
|
|
}
|
|
}
|
|
e = fmt.Errorf("getNode could not find ontology for %s", s)
|
|
return
|
|
} else {
|
|
e = fmt.Errorf("getNode given unhandled node name: %s", s)
|
|
return
|
|
}
|
|
}
|
|
|
|
// resolveAlias turns an alias into its full qualifier for the ontology.
|
|
//
|
|
// If passed in a valid URI, it returns what was passed in.
|
|
func (r *RDFRegistry) ResolveAlias(alias string) (url string, e error) {
|
|
if _, ok := r.ontologies[alias]; ok {
|
|
url = alias
|
|
return
|
|
}
|
|
var ok bool
|
|
if url, ok = r.aliases[alias]; !ok {
|
|
e = fmt.Errorf("registry cannot resolve alias %q", alias)
|
|
}
|
|
return
|
|
}
|