369 lines
7.5 KiB
Go
369 lines
7.5 KiB
Go
|
|
package longdistance
|
||
|
|
|
||
|
|
import (
|
||
|
|
"sourcery.dny.nu/longdistance/internal/json"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Properties is a key-to-array-of-[Node] map.
|
||
|
|
//
|
||
|
|
// It's used to hold any property that's not a JSON-LD keyword.
|
||
|
|
type Properties map[string][]Node
|
||
|
|
|
||
|
|
// Node represents a node in a JSON-LD graph.
|
||
|
|
//
|
||
|
|
// Every supported JSON-LD keyword has a field of its own. All remaining
|
||
|
|
// properties are tracked on the Properties field.
|
||
|
|
type Node struct {
|
||
|
|
Direction string // @direction / KeywordDirection
|
||
|
|
Graph []Node // @graph / KeywordGraph
|
||
|
|
ID string // @id / KeywordID
|
||
|
|
Included []Node // @included / KeywordIncluded
|
||
|
|
Index string // @index / KeywordIndex
|
||
|
|
Language string // @language / KeywordLanguage
|
||
|
|
List []Node // @list / KeywordList
|
||
|
|
Reverse Properties // @reverse / KeywordReverse
|
||
|
|
Set []Node // @set / KeywordSet
|
||
|
|
Type []string // @type / KeywordType
|
||
|
|
Value json.RawMessage // @value / KeywordValue
|
||
|
|
|
||
|
|
Properties Properties // everything else
|
||
|
|
}
|
||
|
|
|
||
|
|
// Internal is a generic type that matches the internals of [Node].
|
||
|
|
//
|
||
|
|
// This can be used to convert to a [Node] from any type outside this package
|
||
|
|
// that happens to be a [Node] underneath.
|
||
|
|
type Internal interface {
|
||
|
|
~struct {
|
||
|
|
Direction string
|
||
|
|
Graph []Node
|
||
|
|
ID string
|
||
|
|
Included []Node
|
||
|
|
Index string
|
||
|
|
Language string
|
||
|
|
List []Node
|
||
|
|
Reverse Properties
|
||
|
|
Set []Node
|
||
|
|
Type []string
|
||
|
|
Value json.RawMessage
|
||
|
|
Properties Properties
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// PropertySet returns a [Set] with an entry for each property that is set on
|
||
|
|
// the [Node].
|
||
|
|
func (n *Node) PropertySet() map[string]struct{} {
|
||
|
|
if n == nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
res := make(map[string]struct{}, len(n.Properties)+2)
|
||
|
|
if n.Has(KeywordDirection) {
|
||
|
|
res[KeywordDirection] = struct{}{}
|
||
|
|
}
|
||
|
|
if n.Has(KeywordGraph) {
|
||
|
|
res[KeywordGraph] = struct{}{}
|
||
|
|
}
|
||
|
|
if n.Has(KeywordID) {
|
||
|
|
res[KeywordID] = struct{}{}
|
||
|
|
}
|
||
|
|
if n.Has(KeywordIncluded) {
|
||
|
|
res[KeywordIncluded] = struct{}{}
|
||
|
|
}
|
||
|
|
if n.Has(KeywordIndex) {
|
||
|
|
res[KeywordIndex] = struct{}{}
|
||
|
|
}
|
||
|
|
if n.Has(KeywordLanguage) {
|
||
|
|
res[KeywordLanguage] = struct{}{}
|
||
|
|
}
|
||
|
|
if n.Has(KeywordList) {
|
||
|
|
res[KeywordList] = struct{}{}
|
||
|
|
}
|
||
|
|
if n.Has(KeywordReverse) {
|
||
|
|
res[KeywordReverse] = struct{}{}
|
||
|
|
}
|
||
|
|
if n.Has(KeywordSet) {
|
||
|
|
res[KeywordSet] = struct{}{}
|
||
|
|
}
|
||
|
|
if n.Has(KeywordType) {
|
||
|
|
res[KeywordType] = struct{}{}
|
||
|
|
}
|
||
|
|
if n.Has(KeywordValue) {
|
||
|
|
res[KeywordValue] = struct{}{}
|
||
|
|
}
|
||
|
|
|
||
|
|
for p := range n.Properties {
|
||
|
|
res[p] = struct{}{}
|
||
|
|
}
|
||
|
|
|
||
|
|
return res
|
||
|
|
}
|
||
|
|
|
||
|
|
func (n *Node) propsWithout(props ...string) map[string]struct{} {
|
||
|
|
nprops := n.PropertySet()
|
||
|
|
for _, prop := range props {
|
||
|
|
delete(nprops, prop)
|
||
|
|
}
|
||
|
|
return nprops
|
||
|
|
}
|
||
|
|
|
||
|
|
func (n *Node) isNode() bool {
|
||
|
|
if n == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
return !n.Has(KeywordList) && !n.Has(KeywordValue) && !n.Has(KeywordSet)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Has returns if a node has the requested property.
|
||
|
|
//
|
||
|
|
// Properties must either be a JSON-LD keyword, or an expanded IRI.
|
||
|
|
func (n *Node) Has(prop string) bool {
|
||
|
|
if n == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
switch prop {
|
||
|
|
case KeywordID:
|
||
|
|
return n.ID != ""
|
||
|
|
case KeywordValue:
|
||
|
|
return n.Value != nil
|
||
|
|
case KeywordLanguage:
|
||
|
|
return n.Language != ""
|
||
|
|
case KeywordDirection:
|
||
|
|
return n.Direction != ""
|
||
|
|
case KeywordType:
|
||
|
|
return n.Type != nil
|
||
|
|
case KeywordList:
|
||
|
|
return n.List != nil
|
||
|
|
case KeywordSet:
|
||
|
|
return n.Set != nil
|
||
|
|
case KeywordGraph:
|
||
|
|
return n.Graph != nil
|
||
|
|
case KeywordIncluded:
|
||
|
|
return n.Included != nil
|
||
|
|
case KeywordIndex:
|
||
|
|
return n.Index != ""
|
||
|
|
case KeywordReverse:
|
||
|
|
return n.Reverse != nil
|
||
|
|
default:
|
||
|
|
for key := range n.Properties {
|
||
|
|
if prop == key {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsZero returns if this is the zero value of a [Node].
|
||
|
|
func (n *Node) IsZero() bool {
|
||
|
|
if n == nil {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
|
||
|
|
return len(n.PropertySet()) == 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsSubject checks if this node is a subject.
|
||
|
|
//
|
||
|
|
// This means:
|
||
|
|
// - It has an @id.
|
||
|
|
// - It may have an @type.
|
||
|
|
// - It has at least one other property.
|
||
|
|
func (n *Node) IsSubject() bool {
|
||
|
|
if n == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
if !n.Has(KeywordID) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
return len(n.propsWithout(KeywordID, KeywordIndex)) != 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsSubjectReference checks if this node is a subject reference.
|
||
|
|
//
|
||
|
|
// This means:
|
||
|
|
// - It has an @id.
|
||
|
|
// - It may have an @type.
|
||
|
|
// - It has no other properties.
|
||
|
|
func (n *Node) IsSubjectReference() bool {
|
||
|
|
if n == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
if !n.Has(KeywordID) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
return len(n.propsWithout(KeywordID, KeywordType)) == 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsList checks if this node is a list.
|
||
|
|
//
|
||
|
|
// This means:
|
||
|
|
// - It has an @list.
|
||
|
|
// - It has no other properties.
|
||
|
|
func (n *Node) IsList() bool {
|
||
|
|
if n == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
if !n.Has(KeywordList) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
return len(n.propsWithout(KeywordList, KeywordIndex)) == 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsValue checks if this is a value node.
|
||
|
|
//
|
||
|
|
// This means:
|
||
|
|
// - It has an @value.
|
||
|
|
// - It may have an @direction, @index, @langauge and @type.
|
||
|
|
// - It has no other properties.
|
||
|
|
//
|
||
|
|
// Additionally, it's invalid to have @type together with @language or
|
||
|
|
// @direction.
|
||
|
|
func (n *Node) IsValue() bool {
|
||
|
|
if n == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
if !n.Has(KeywordValue) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
return len(n.propsWithout(
|
||
|
|
KeywordValue,
|
||
|
|
KeywordDirection,
|
||
|
|
KeywordIndex,
|
||
|
|
KeywordLanguage,
|
||
|
|
KeywordType,
|
||
|
|
)) == 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsGraph returns if the object is a graph.
|
||
|
|
//
|
||
|
|
// This requires:
|
||
|
|
// - It must have an @graph.
|
||
|
|
// - It may have @id and @index.
|
||
|
|
// - It has no other properties.
|
||
|
|
func (n *Node) IsGraph() bool {
|
||
|
|
if n == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
if !n.Has(KeywordGraph) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
return len(n.propsWithout(KeywordID, KeywordIndex, KeywordGraph)) == 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsSimpleGraph returns if the object is a simple graph.
|
||
|
|
//
|
||
|
|
// This requires:
|
||
|
|
// - It must have an @graph.
|
||
|
|
// - It may have @index.
|
||
|
|
// - It has no other properties.
|
||
|
|
func (n *Node) IsSimpleGraph() bool {
|
||
|
|
if n == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
if !n.Has(KeywordGraph) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
return len(n.propsWithout(KeywordIndex, KeywordGraph)) == 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// MarshalJSON encodes to Expanded Document Form.
|
||
|
|
func (n *Node) MarshalJSON() ([]byte, error) {
|
||
|
|
result := map[string]any{}
|
||
|
|
|
||
|
|
if n.Has(KeywordID) {
|
||
|
|
result[KeywordID] = n.ID
|
||
|
|
}
|
||
|
|
|
||
|
|
if n.Has(KeywordIndex) {
|
||
|
|
result[KeywordIndex] = n.Index
|
||
|
|
}
|
||
|
|
|
||
|
|
if n.Has(KeywordType) {
|
||
|
|
var data any
|
||
|
|
if n.Value != nil && len(n.Type) == 1 {
|
||
|
|
data = n.Type[0]
|
||
|
|
} else {
|
||
|
|
data = n.Type
|
||
|
|
}
|
||
|
|
result[KeywordType] = data
|
||
|
|
}
|
||
|
|
|
||
|
|
if n.Has(KeywordValue) {
|
||
|
|
result[KeywordValue] = n.Value
|
||
|
|
}
|
||
|
|
|
||
|
|
if n.Has(KeywordLanguage) {
|
||
|
|
result[KeywordLanguage] = n.Language
|
||
|
|
}
|
||
|
|
|
||
|
|
if n.Has(KeywordDirection) {
|
||
|
|
result[KeywordDirection] = n.Direction
|
||
|
|
}
|
||
|
|
|
||
|
|
if n.Has(KeywordList) {
|
||
|
|
result[KeywordList] = n.List
|
||
|
|
}
|
||
|
|
|
||
|
|
if n.Has(KeywordGraph) {
|
||
|
|
result[KeywordGraph] = n.Graph
|
||
|
|
}
|
||
|
|
|
||
|
|
if n.Has(KeywordIncluded) {
|
||
|
|
result[KeywordIncluded] = n.Included
|
||
|
|
}
|
||
|
|
|
||
|
|
if n.Has(KeywordReverse) {
|
||
|
|
result[KeywordReverse] = n.Reverse
|
||
|
|
}
|
||
|
|
|
||
|
|
for k, v := range n.Properties {
|
||
|
|
result[k] = v
|
||
|
|
}
|
||
|
|
|
||
|
|
return json.Marshal(result)
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetNodes returns the nodes stored in property.
|
||
|
|
func (n *Node) GetNodes(property string) []Node {
|
||
|
|
switch property {
|
||
|
|
case KeywordGraph:
|
||
|
|
return n.Graph
|
||
|
|
case KeywordIncluded:
|
||
|
|
return n.Included
|
||
|
|
case KeywordList:
|
||
|
|
return n.List
|
||
|
|
case KeywordSet:
|
||
|
|
return n.Set
|
||
|
|
default:
|
||
|
|
if !n.Has(property) {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return n.Properties[property]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// AddNodes appends the nodes stored in property.
|
||
|
|
func (n *Node) AddNodes(property string, nodes ...Node) {
|
||
|
|
n.Properties[property] = append(n.Properties[property], nodes...)
|
||
|
|
}
|
||
|
|
|
||
|
|
// SetNodes overrides the nodes stored in property.
|
||
|
|
func (n *Node) SetNodes(property string, nodes ...Node) {
|
||
|
|
n.Properties[property] = nodes
|
||
|
|
}
|