package rdf import ( "fmt" "github.com/dave/jennifer/jen" "github.com/go-fed/activity/astool/codegen" "net/url" "strings" ) const ( rdfName = "RDF" rdfSpec = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" langstringSpec = "langString" propertySpec = "Property" ) // SerializeValueFunction is a helper for creating a value's Serialize function. func SerializeValueFunction(pkg, valueName string, concreteType jen.Code, impl []jen.Code) *codegen.Function { name := fmt.Sprintf("Serialize%s", strings.Title(valueName)) return codegen.NewCommentedFunction( pkg, name, []jen.Code{jen.Id(codegen.This()).Add(concreteType)}, []jen.Code{jen.Interface(), jen.Error()}, impl, fmt.Sprintf("%s converts a %s value to an interface representation suitable for marshalling into a text or binary format.", name, valueName)) } // DeserializeValueFunction is a helper for creating a value's Deserialize // function. func DeserializeValueFunction(pkg, valueName string, concreteType jen.Code, impl []jen.Code) *codegen.Function { name := fmt.Sprintf("Deserialize%s", strings.Title(valueName)) return codegen.NewCommentedFunction( pkg, name, []jen.Code{jen.Id(codegen.This()).Interface()}, []jen.Code{concreteType, jen.Error()}, impl, fmt.Sprintf("%s creates %s value from an interface representation that has been unmarshalled from a text or binary format.", name, valueName)) } // LessFunction is a helper for creating a value's Less function. func LessFunction(pkg, valueName string, concreteType jen.Code, impl []jen.Code) *codegen.Function { name := fmt.Sprintf("Less%s", strings.Title(valueName)) return codegen.NewCommentedFunction( pkg, name, []jen.Code{jen.List(jen.Id("lhs"), jen.Id("rhs")).Add(concreteType)}, []jen.Code{jen.Bool()}, impl, fmt.Sprintf("%s returns true if the left %s value is less than the right value.", name, valueName)) } var _ Ontology = &RDFOntology{} // RDFOntology is an Ontology for the RDF namespace. type RDFOntology struct { Package string alias string } // SpecURI returns the RDF URI spec. func (o *RDFOntology) SpecURI() string { return rdfSpec } // Load loads the ontology with no alias set. func (o *RDFOntology) Load() ([]RDFNode, error) { return o.LoadAsAlias("") } // LoadAsAlias loads the ontology with an alias. func (o *RDFOntology) LoadAsAlias(s string) ([]RDFNode, error) { o.alias = s return []RDFNode{ &AliasedDelegate{ Spec: rdfSpec, Alias: s, Name: langstringSpec, Delegate: &langstring{pkg: o.Package, alias: o.alias}, }, &AliasedDelegate{ Spec: rdfSpec, Alias: s, Name: propertySpec, Delegate: &property{}, }, }, nil } // LoadSpecificAsAlias loads a specific RDFNode with the given alias. func (o *RDFOntology) LoadSpecificAsAlias(alias, name string) ([]RDFNode, error) { switch name { case langstringSpec: return []RDFNode{ &AliasedDelegate{ Spec: "", Alias: "", Name: alias, Delegate: &langstring{pkg: o.Package, alias: o.alias}, }, }, nil case propertySpec: return []RDFNode{ &AliasedDelegate{ Spec: "", Alias: "", Name: alias, Delegate: &property{}, }, }, nil } return nil, fmt.Errorf("rdf ontology cannot find %q to make alias %q", name, alias) } // LoadElement does nothing. func (o *RDFOntology) LoadElement(name string, payload map[string]interface{}) ([]RDFNode, error) { return nil, nil } // GetByName returns a raw, unguarded node by name. func (o *RDFOntology) GetByName(name string) (RDFNode, error) { name = strings.TrimPrefix(name, o.SpecURI()) switch name { case langstringSpec: return &langstring{pkg: o.Package, alias: o.alias}, nil case propertySpec: return &property{}, nil } return nil, fmt.Errorf("rdf ontology could not find node for name %s", name) } var _ RDFNode = &langstring{} // langstring is an RDF node representing the langstring value. type langstring struct { alias string pkg string } // Enter returns an error. func (l *langstring) Enter(key string, ctx *ParsingContext) (bool, error) { return true, fmt.Errorf("rdf langstring cannot be entered") } // Exit returns an error. func (l *langstring) Exit(key string, ctx *ParsingContext) (bool, error) { return true, fmt.Errorf("rdf langstring cannot be exited") } // Apply sets the langstring value in the context as a referenced spec. func (l *langstring) Apply(key string, value interface{}, ctx *ParsingContext) (bool, error) { for k, p := range ctx.Result.Vocab.Properties { for i, ref := range p.Range { if ref.Name == langstringSpec && ref.Vocab == l.alias { p.NaturalLanguageMap = true ctx.Result.Vocab.Properties[k] = p p.Range = append(p.Range[:i], p.Range[i+1:]...) break } } } u, e := url.Parse(rdfSpec + langstringSpec) if e != nil { return true, e } var vocab *Vocabulary vocab, e = ctx.GetResultReferenceWithDefaults(rdfSpec, rdfName) if e != nil { return true, e } e = vocab.SetValue(langstringSpec, &VocabularyValue{ Name: langstringSpec, URI: u, DefinitionType: jen.Map(jen.String()).String(), Zero: "make(map[string]string)", IsNilable: true, SerializeFn: SerializeValueFunction( l.pkg, langstringSpec, jen.Map(jen.String()).String(), []jen.Code{ jen.Return( jen.Id(codegen.This()), jen.Nil(), ), }), DeserializeFn: DeserializeValueFunction( l.pkg, langstringSpec, jen.Map(jen.String()).String(), []jen.Code{ jen.If( jen.List( jen.Id("m"), jen.Id("ok"), ).Op(":=").Id(codegen.This()).Assert(jen.Map(jen.String()).Interface()), jen.Id("ok"), ).Block( jen.Id("r").Op(":=").Make(jen.Map(jen.String()).String()), jen.For( jen.List( jen.Id("k"), jen.Id("v"), ).Op(":=").Range().Id("m"), ).Block( jen.If( jen.List( jen.Id("s"), jen.Id("ok"), ).Op(":=").Id("v").Assert(jen.String()), jen.Id("ok"), ).Block( jen.Id("r").Index(jen.Id("k")).Op("=").Id("s"), ).Else().Block( jen.Return( jen.Nil(), jen.Qual("fmt", "Errorf").Call( jen.Lit("value %v cannot be interpreted as a string for rdf:langString"), jen.Id("v"), ), ), ), ), jen.Return( jen.Id("r"), jen.Nil(), ), ).Else().Block( jen.Return( jen.Nil(), jen.Qual("fmt", "Errorf").Call( jen.Lit("%v cannot be interpreted as a map[string]interface{} for rdf:langString"), jen.Id(codegen.This()), ), ), ), }), LessFn: LessFunction( l.pkg, langstringSpec, jen.Map(jen.String()).String(), []jen.Code{ jen.Var().Id("lk").Index().String(), jen.Var().Id("rk").Index().String(), jen.For( jen.List( jen.Id("k"), ).Op(":=").Range().Id("lhs"), ).Block( jen.Id("lk").Op("=").Append( jen.Id("lk"), jen.Id("k"), ), ), jen.For( jen.List( jen.Id("k"), ).Op(":=").Range().Id("rhs"), ).Block( jen.Id("rk").Op("=").Append( jen.Id("rk"), jen.Id("k"), ), ), jen.Qual("sort", "Sort").Call( jen.Qual("sort", "StringSlice").Call(jen.Id("lk")), ), jen.Qual("sort", "Sort").Call( jen.Qual("sort", "StringSlice").Call(jen.Id("rk")), ), jen.For( jen.Id("i").Op(":=").Lit(0), jen.Id("i").Op("<").Len(jen.Id("lk")).Op("&&").Id("i").Op("<").Len(jen.Id("rk")), jen.Id("i").Op("++"), ).Block( jen.If( jen.Id("lk").Index(jen.Id("i")).Op("<").Id("rk").Index(jen.Id("i")), ).Block( jen.Return(jen.True()), ).Else().If( jen.Id("rk").Index(jen.Id("i")).Op("<").Id("lk").Index(jen.Id("i")), ).Block( jen.Return(jen.False()), ).Else().If( jen.Id("lhs").Index(jen.Id("lk").Index(jen.Id("i"))).Op("<").Id("rhs").Index(jen.Id("rk").Index(jen.Id("i"))), ).Block( jen.Return(jen.True()), ).Else().If( jen.Id("rhs").Index(jen.Id("rk").Index(jen.Id("i"))).Op("<").Id("lhs").Index(jen.Id("lk").Index(jen.Id("i"))), ).Block( jen.Return(jen.False()), ), ), jen.If( jen.Len(jen.Id("lk")).Op("<").Len(jen.Id("rk")), ).Block( jen.Return(jen.True()), ).Else().Block( jen.Return(jen.False()), ), }), }) return true, e } var _ RDFNode = &property{} // property is an RDFNode that sets a VocabularyProperty as the current. type property struct{} // Enter returns an error. func (p *property) Enter(key string, ctx *ParsingContext) (bool, error) { return true, fmt.Errorf("rdf property cannot be entered") } // Exit returns an error. func (p *property) Exit(key string, ctx *ParsingContext) (bool, error) { return true, fmt.Errorf("rdf property cannot be exited") } // Apply sets the current context to be a VocabularyProperty, if it is not // already. If the context isn't reset, an error is returned due to another node // not having cleaned up properly. func (p *property) Apply(key string, value interface{}, ctx *ParsingContext) (bool, error) { // Prepare a new VocabularyProperty in the context. If one already // exists, skip. if _, ok := ctx.Current.(*VocabularyProperty); ok { return true, nil } else if !ctx.IsReset() { return true, fmt.Errorf("rdf property applied with non-reset ParsingContext") } ctx.Current = &VocabularyProperty{} return true, nil }