activity/astool/rdf/jsonld.go

225 行
6.2 KiB
Go

package rdf
import (
"fmt"
)
const (
typeSpec = "@type"
typeActivityStreamsSpec = "type"
IdSpec = "@id"
IdActivityStreamsSpec = "id"
ContainerSpec = "@container"
IndexSpec = "@index"
// ActivityStreams specifically disallows the 'object' property on
// certain IntransitiveActivity and subtypes. There is no RDF mechanism
// to describe this. So this is a stupid hack, based on the assumption
// that no one -- W3C or otherwise -- will name a reserved word with a
// "@wtf_" prefix due to the reserved '@', the use of the unprofessional
// 'wtf', and a style-breaking underscore.
withoutPropertySpec = "@wtf_without_property"
)
// jsonLDNodes contains the well-known set of nodes as defined by the JSON-LD
// specification.
func jsonLDNodes(r *RDFRegistry) []RDFNode {
// Order matters -- we want to be able to distinguish the types of
// things without other nodes hijacking the flow.
return []RDFNode{
&AliasedDelegate{
Spec: "",
Alias: "",
Name: typeSpec,
Delegate: &typeLD{r: r},
},
&AliasedDelegate{
Spec: "",
Alias: "",
Name: typeActivityStreamsSpec,
Delegate: &typeLD{r: r},
},
&AliasedDelegate{
Spec: "",
Alias: "",
Name: IdSpec,
Delegate: &idLD{},
},
&AliasedDelegate{
Spec: "",
Alias: "",
Name: IdActivityStreamsSpec,
Delegate: &idLD{},
},
&AliasedDelegate{
Spec: "",
Alias: "",
Name: ContainerSpec,
Delegate: &ContainerLD{},
},
&AliasedDelegate{
Spec: "",
Alias: "",
Name: IndexSpec,
Delegate: &IndexLD{},
},
&AliasedDelegate{
Spec: "",
Alias: "",
Name: withoutPropertySpec,
Delegate: &withoutProperty{},
},
}
}
var _ RDFNode = &idLD{}
// idLD is an RDFNode for the 'id' property.
type idLD struct{}
// Enter returns an error.
func (i *idLD) Enter(key string, ctx *ParsingContext) (bool, error) {
return true, fmt.Errorf("id cannot be entered")
}
// Exit returns an error.
func (i *idLD) Exit(key string, ctx *ParsingContext) (bool, error) {
return true, fmt.Errorf("id cannot be exited")
}
// Apply sets the URI for the context's Current item.
func (i *idLD) Apply(key string, value interface{}, ctx *ParsingContext) (bool, error) {
if ctx.Current == nil {
return true, nil
} else if ider, ok := ctx.Current.(URISetter); !ok {
return true, fmt.Errorf("id apply called with non-URISetter")
} else if str, ok := value.(string); !ok {
return true, fmt.Errorf("id apply called with non-string value")
} else {
return true, ider.SetURI(str)
}
}
var _ RDFNode = &typeLD{}
// typeLD is an RDFNode for the 'type' property.
type typeLD struct {
r *RDFRegistry
}
// Enter does nothing.
func (t *typeLD) Enter(key string, ctx *ParsingContext) (bool, error) {
return true, nil
}
// Exit does nothing.
func (t *typeLD) Exit(key string, ctx *ParsingContext) (bool, error) {
return true, nil
}
// Apply attempts to get the RDFNode for the type and apply it.
func (t *typeLD) Apply(key string, value interface{}, ctx *ParsingContext) (bool, error) {
vs, ok := value.(string)
if !ok {
return true, fmt.Errorf("@type is not string")
}
n, e := t.r.getNode(vs)
if e != nil {
return true, e
}
return n.Apply(vs, nil, ctx)
}
var _ RDFNode = &ContainerLD{}
// ContainerLD is an RDFNode that delegates to an RDFNode but only at this
// next level.
type ContainerLD struct {
ContainsNode RDFNode
}
// Enter sets OnlyApplyThisNodeNextLevel on the ParsingContext.
//
// Returns an error if this is the second time Enter is called in a row.
func (c *ContainerLD) Enter(key string, ctx *ParsingContext) (bool, error) {
if ctx.OnlyApplyThisNodeNextLevel != nil {
return true, fmt.Errorf("@container parsing context exit already has non-nil node")
}
ctx.SetOnlyApplyThisNodeNextLevel(c.ContainsNode)
return true, nil
}
// Exit clears OnlyApplyThisNodeNextLevel on the ParsingContext.
//
// Returns an error if this is the second time Exit is called in a row.
func (c *ContainerLD) Exit(key string, ctx *ParsingContext) (bool, error) {
if ctx.OnlyApplyThisNodeNextLevel == nil {
return true, fmt.Errorf("@container parsing context exit already has nil node")
}
ctx.ResetOnlyAppliedThisNodeNextLevel()
return true, nil
}
// Apply does nothing.
func (c *ContainerLD) Apply(key string, value interface{}, ctx *ParsingContext) (bool, error) {
return true, nil
}
var _ RDFNode = &IndexLD{}
// IndexLD does nothing.
//
// It could try to manage human-defined indices, but the machine doesn't care.
type IndexLD struct{}
// Enter does nothing.
func (i *IndexLD) Enter(key string, ctx *ParsingContext) (bool, error) {
return true, nil
}
// Exit does nothing.
func (i *IndexLD) Exit(key string, ctx *ParsingContext) (bool, error) {
return true, nil
}
// Apply does nothing.
func (i *IndexLD) Apply(key string, value interface{}, ctx *ParsingContext) (bool, error) {
return true, nil
}
var _ RDFNode = &withoutProperty{}
// withoutProperty is a hacky-as-hell way to manage ActivityStream's concept of
// "WithoutProperty". It isn't a defined RDF relationship, so this is
// non-standard but required of the ActivityStreams Core or Extended Types spec.
type withoutProperty struct{}
// Enter pushes a VocabularyReference. It is expected further nodes will
// populate it with information before dxiting this node.
func (w *withoutProperty) Enter(key string, ctx *ParsingContext) (bool, error) {
ctx.Push()
ctx.Current = &VocabularyReference{}
return true, nil
}
// Exit pops a VocabularyReferences and sets DoesNotApplyTo on the parent
// VocabularyProperty on the stack.
func (w *withoutProperty) Exit(key string, ctx *ParsingContext) (bool, error) {
i := ctx.Current
ctx.Pop()
vr, ok := i.(*VocabularyReference)
if !ok {
return true, fmt.Errorf("hacky withoutProperty exit did not get *rdf.VocabularyReference")
}
vp, ok := ctx.Current.(*VocabularyProperty)
if !ok {
return true, fmt.Errorf("hacky withoutProperty exit Current is not *rdf.VocabularyProperty")
}
vp.DoesNotApplyTo = append(vp.DoesNotApplyTo, *vr)
return true, nil
}
// Apply returns an error.
func (w *withoutProperty) Apply(key string, value interface{}, ctx *ParsingContext) (bool, error) {
return true, fmt.Errorf("hacky withoutProperty cannot be applied")
}