package owl import ( "fmt" "github.com/go-fed/activity/astool/rdf" "strings" ) const ( owlSpec = "http://www.w3.org/2002/07/owl#" membersSpec = "members" disjointWithSpec = "disjointWith" unionOfSpec = "unionOf" importsSpec = "imports" ontologySpec = "Ontology" classSpec = "Class" objectPropertySpec = "ObjectProperty" functionalPropertySpec = "FunctionalProperty" ) // OWLOntology is an Ontology for OWL2. type OWLOntology struct { alias string } // SpecURI returns the URI of the OWL specification. func (o *OWLOntology) SpecURI() string { return owlSpec } // Load this ontology without an alias. func (o *OWLOntology) Load() ([]rdf.RDFNode, error) { return o.LoadAsAlias("") } // LoadAsAlias loads the ontology with the alias. func (o *OWLOntology) LoadAsAlias(s string) ([]rdf.RDFNode, error) { o.alias = s return []rdf.RDFNode{ &rdf.AliasedDelegate{ Spec: owlSpec, Alias: s, Name: membersSpec, Delegate: &members{}, }, &rdf.AliasedDelegate{ Spec: owlSpec, Alias: s, Name: disjointWithSpec, Delegate: &disjointWith{}, }, &rdf.AliasedDelegate{ Spec: owlSpec, Alias: s, Name: unionOfSpec, Delegate: &unionOf{}, }, &rdf.AliasedDelegate{ Spec: owlSpec, Alias: s, Name: importsSpec, Delegate: &imports{}, }, &rdf.AliasedDelegate{ Spec: owlSpec, Alias: s, Name: ontologySpec, Delegate: &ontology{}, }, &rdf.AliasedDelegate{ Spec: owlSpec, Alias: s, Name: classSpec, Delegate: &class{}, }, &rdf.AliasedDelegate{ Spec: owlSpec, Alias: s, Name: objectPropertySpec, Delegate: &objectProperty{}, }, &rdf.AliasedDelegate{ Spec: owlSpec, Alias: s, Name: functionalPropertySpec, Delegate: &functionalProperty{}, }, }, nil } // LoadSpecificAsAlias loads a specific ontology definition as an alias. func (o *OWLOntology) LoadSpecificAsAlias(alias, name string) ([]rdf.RDFNode, error) { switch name { case membersSpec: return []rdf.RDFNode{ &rdf.AliasedDelegate{ Spec: "", Alias: "", Name: alias, Delegate: &members{}, }, }, nil case disjointWithSpec: return []rdf.RDFNode{ &rdf.AliasedDelegate{ Spec: "", Alias: "", Name: alias, Delegate: &disjointWith{}, }, }, nil case unionOfSpec: return []rdf.RDFNode{ &rdf.AliasedDelegate{ Spec: "", Alias: "", Name: alias, Delegate: &unionOf{}, }, }, nil case importsSpec: return []rdf.RDFNode{ &rdf.AliasedDelegate{ Spec: "", Alias: "", Name: alias, Delegate: &imports{}, }, }, nil case ontologySpec: return []rdf.RDFNode{ &rdf.AliasedDelegate{ Spec: "", Alias: "", Name: alias, Delegate: &ontology{}, }, }, nil case classSpec: return []rdf.RDFNode{ &rdf.AliasedDelegate{ Spec: "", Alias: "", Name: alias, Delegate: &class{}, }, }, nil case objectPropertySpec: return []rdf.RDFNode{ &rdf.AliasedDelegate{ Spec: "", Alias: "", Name: alias, Delegate: &objectProperty{}, }, }, nil case functionalPropertySpec: return []rdf.RDFNode{ &rdf.AliasedDelegate{ Spec: "", Alias: "", Name: alias, Delegate: &functionalProperty{}, }, }, nil } return nil, fmt.Errorf("owl ontology cannot find %q to alias to %q", name, alias) } // LoadElement allows loading nodes to enable contexts containing a container // with an index. func (o *OWLOntology) LoadElement(name string, payload map[string]interface{}) ([]rdf.RDFNode, error) { // First, detect if an idValue exists var idValue interface{} var ok bool idValue, ok = payload[rdf.IdSpec] if !ok { idValue, ok = payload[rdf.IdActivityStreamsSpec] } if !ok { return nil, nil } vStr, ok := idValue.(string) if !ok { return nil, nil } // Now that we have a string idValue, handle the import use case if !rdf.IsKeyApplicable(vStr, owlSpec, o.alias, importsSpec) { return nil, nil } node := &rdf.AliasedDelegate{ Spec: "", Alias: "", Name: name, // Need to set Delegate, based on below logic } for k, v := range payload { if k == rdf.IdSpec || k == rdf.IdActivityStreamsSpec { continue } switch k { case rdf.ContainerSpec: container := &rdf.ContainerLD{} node.Delegate = container // Ugly, maybe move out to its own function when needed if cValStr, ok := v.(string); !ok { return nil, fmt.Errorf("unhandled owl import @container to non-string type: %T", v) } else { switch cValStr { case rdf.IndexSpec: container.ContainsNode = &rdf.IndexLD{} default: return nil, fmt.Errorf("unhandled owl import @container to string type %s", cValStr) } } default: return nil, fmt.Errorf("unhandled owl import use case: %s", k) } } return []rdf.RDFNode{node}, nil } // GetByName returns a bare node. func (o *OWLOntology) GetByName(name string) (rdf.RDFNode, error) { name = strings.TrimPrefix(name, o.SpecURI()) switch name { case membersSpec: return &members{}, nil case disjointWithSpec: return &disjointWith{}, nil case unionOfSpec: return &unionOf{}, nil case importsSpec: return &imports{}, nil case ontologySpec: return &ontology{}, nil case classSpec: return &class{}, nil case objectPropertySpec: return &objectProperty{}, nil case functionalPropertySpec: return &functionalProperty{}, nil } return nil, fmt.Errorf("owl ontology could not find node for name %s", name) } var _ rdf.RDFNode = &members{} // members represents owl:members. type members struct { pushed bool } // Enter does nothing but returns an error if the context is not reset. func (m *members) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { if !ctx.IsReset() { ctx.Push() m.pushed = true } return true, nil } // Exit adds a Vocabulary Type, Property or Value to Result. func (m *members) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { // Finish adding the Current item to the resulting vocabulary if ctx.Current == nil { return true, fmt.Errorf("owl members exiting with nil Current") } switch v := ctx.Current.(type) { case *rdf.VocabularyType: if err := ctx.Result.Vocab.SetType(ctx.Name, v); err != nil { return true, err } case *rdf.VocabularyProperty: if err := ctx.Result.Vocab.SetProperty(ctx.Name, v); err != nil { return true, err } case *rdf.VocabularyValue: if err := ctx.Result.Vocab.SetValue(ctx.Name, v); err != nil { return true, err } default: return true, fmt.Errorf("owl members exiting with unhandled type: %T", ctx.Current) } if m.pushed { ctx.Pop() } return true, nil } // Apply returns an error. func (m *members) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl members cannot be applied") } var _ rdf.RDFNode = &disjointWith{} // disjointWith represents owl:disjointWith. type disjointWith struct{} // Enter ensures the Current is a Type, then pushes a Reference. func (d *disjointWith) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { // Push the Current type aside, to build a Reference. if ctx.Current == nil { return true, fmt.Errorf("owl disjointWith enter given a nil Current") } else if _, ok := ctx.Current.(*rdf.VocabularyType); !ok { return true, fmt.Errorf("owl disjointWith enter not given a *rdf.VocabularyType") } ctx.Push() ctx.Current = &rdf.VocabularyReference{} return true, nil } // Exit pops the Reference and adds it to the Type's DisjointWith. func (d *disjointWith) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { // Pop the Reference, put into the type. ref, ok := ctx.Current.(*rdf.VocabularyReference) if !ok { return true, fmt.Errorf("owl disjointWith exit not given a *rdf.VocabularyReference") } ctx.Pop() vType, ok := ctx.Current.(*rdf.VocabularyType) if !ok { return true, fmt.Errorf("owl disjointWith exit not given a *rdf.VocabularyType") } vType.DisjointWith = append(vType.DisjointWith, *ref) return true, nil } // Apply returns an error. func (d *disjointWith) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl disjointWith cannot be applied") } var _ rdf.RDFNode = &unionOf{} // unionOf represents owl:unionOf. type unionOf struct { entered bool } // Enter pushes a single Reference. func (u *unionOf) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { u.entered = true ctx.Push() ctx.Current = &rdf.VocabularyReference{} return true, nil } // Exit pops a Reference and appends it to Current, which is a slice of // References. func (u *unionOf) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { u.entered = false if ctx.Current == nil { return true, fmt.Errorf("owl unionOf exit given nil Current") } i := ctx.Current ctx.Pop() ref, ok := i.(*rdf.VocabularyReference) if !ok { return true, fmt.Errorf("owl unionOf exit not given *rdf.VocabularyReference") } arr, ok := ctx.Current.([]rdf.VocabularyReference) if !ok { return true, fmt.Errorf("owl unionOf exit's previous Current not given []rdf.VocabularyReference") } ctx.Current = append(arr, *ref) return true, nil } // Apply will either apply a value onto a current Reference (if it was entered // due to being a JSON array), or will append a new reference to Current (which // is a slice of references). func (u *unionOf) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { s, ok := value.(string) if !ok { return true, fmt.Errorf("owl unionOf apply given non-string value") } strs := rdf.SplitAlias(s) ref := &rdf.VocabularyReference{} if len(strs) == 1 { ref.Name = strs[0] } else if len(strs) == 2 { ref.Name = strs[1] ref.Vocab = strs[0] } else { return true, fmt.Errorf("owl unionOf apply bad SplitAlias") } if u.entered { in, ok := ctx.Current.(*rdf.VocabularyReference) if !ok { return true, fmt.Errorf("owl unionOf apply's Current not given *rdf.VocabularyReference: %T", ctx.Current) } in.Name = ref.Name in.Vocab = ref.Vocab } else { arr, ok := ctx.Current.([]rdf.VocabularyReference) if !ok { return true, fmt.Errorf("owl unionOf apply's Current not given []rdf.VocabularyReference: %T", ctx.Current) } ctx.Current = append(arr, *ref) } return true, nil } var _ rdf.RDFNode = &imports{} // imports does nothing but returns errors. It should instead be handled by // special cases in an Ontology's LoadElement. // // Overall, this is a pain to implement. If these errors are seen, then I am // about to have a really not-fun day. type imports struct{} // Enter returns an error. func (i *imports) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl imports cannot be entered") } // Exit returns an error. func (i *imports) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl imports cannot be entered") } // Apply returns an error. func (i *imports) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl imports cannot be entered") } var _ rdf.RDFNode = &ontology{} // ontology does nothing. type ontology struct{} // Enter returns an error. func (o *ontology) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl ontology cannot be entered") } // Exit returns an error. func (o *ontology) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl ontology cannot be exited") } // Apply does nothing. func (o *ontology) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { ctx.Current = &ctx.Result.Vocab return true, nil } var _ rdf.RDFNode = &class{} // class prepares a new Type on Current, unless Reference has already been // prepared. type class struct{} // Enter returns an error. func (c *class) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl class cannot be entered") } // Exit returns an error. func (c *class) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl class cannot be exited") } // Apply sets a Type on Current, unless a Reference is already set. func (c *class) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { // Prepare a new VocabularyType in the context, unless it is a // reference already prepared. if ctx.IsReset() { ctx.Current = &rdf.VocabularyType{} } else if _, ok := ctx.Current.(*rdf.VocabularyReference); ok { return true, nil } else if _, ok := ctx.Current.([]rdf.VocabularyReference); ok { return true, nil } else { return true, fmt.Errorf("owl class applied with non-reset ctx and not a vocab reference: %T", ctx.Current) } return true, nil } var _ rdf.RDFNode = &objectProperty{} // objectProperty is owl:objectProperty type objectProperty struct{} // Enter returns an error. func (o *objectProperty) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl objectProperty cannot be entered") } // Exit returns an error. func (o *objectProperty) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl objectProperty cannot be exited") } // Apply sets Current to be a Property, unless it is already a Property. func (o *objectProperty) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { // Prepare a new VocabularyProperty in the context. If one already // exists, skip. if _, ok := ctx.Current.(*rdf.VocabularyProperty); ok { return true, nil } else if !ctx.IsReset() { return true, fmt.Errorf("owl objectProperty applied with non-reset ParsingContext") } ctx.Current = &rdf.VocabularyProperty{} return true, nil } var _ rdf.RDFNode = &functionalProperty{} // functionalProperty represents owl:functionalProperty type functionalProperty struct{} // Enter returns an error. func (f *functionalProperty) Enter(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl functionalProperty cannot be entered") } // Exit returns an error. func (f *functionalProperty) Exit(key string, ctx *rdf.ParsingContext) (bool, error) { return true, fmt.Errorf("owl functionalProperty cannot be exited") } // Apply sets the Current Property's Functional to true. // // Returns an error if Current is not a Property. func (f *functionalProperty) Apply(key string, value interface{}, ctx *rdf.ParsingContext) (bool, error) { if ctx.Current == nil { return true, fmt.Errorf("owl functionalProperty given nil Current in context") } prop, ok := ctx.Current.(*rdf.VocabularyProperty) if !ok { return true, fmt.Errorf("owl functionalProperty given Current that is not *rdf.VocabularyProperty") } prop.Functional = true return true, nil }