packages feed

hydra-0.15.0: src/main/haskell/Hydra/Demos/Shacl/ShaclRdf.hs

-- | Serialize a SHACL ShapesGraph to RDF triples using the SHACL vocabulary.
--
-- Converts Hydra's SHACL model types (ShapesGraph, Definition, Shape,
-- NodeShape, PropertyShape, etc.) into RDF triples using the sh: namespace.

module Hydra.Demos.Shacl.ShaclRdf (
  shapesGraphToTriples,
  shapesGraphToNtriples,
) where

import qualified Hydra.Rdf.Syntax as Rdf
import qualified Hydra.Shacl.Model as Shacl
import qualified Hydra.Rdf.Serde as Serde
import qualified Hydra.Rdf.Utils as RdfUtils

import qualified Data.Set as S


-- | SHACL namespace
sh :: String -> Rdf.Iri
sh local = Rdf.Iri ("http://www.w3.org/ns/shacl#" ++ local)

-- | RDF namespace
rdf :: String -> Rdf.Iri
rdf local = Rdf.Iri ("http://www.w3.org/1999/02/22-rdf-syntax-ns#" ++ local)

-- | XSD namespace
xsd :: String -> Rdf.Iri
xsd local = Rdf.Iri ("http://www.w3.org/2001/XMLSchema#" ++ local)

-- | Create an IRI node
iriNode :: Rdf.Iri -> Rdf.Node
iriNode = Rdf.NodeIri

-- | Create an IRI resource
iriResource :: Rdf.Iri -> Rdf.Resource
iriResource = Rdf.ResourceIri

-- | Create a triple from IRI subject, IRI predicate, and IRI object
tripleIII :: Rdf.Iri -> Rdf.Iri -> Rdf.Iri -> Rdf.Triple
tripleIII s p o = Rdf.Triple (iriResource s) p (iriNode o)

-- | Create a triple from IRI subject, IRI predicate, and node object
tripleIIN :: Rdf.Iri -> Rdf.Iri -> Rdf.Node -> Rdf.Triple
tripleIIN s p o = Rdf.Triple (iriResource s) p o

-- | Create a triple from resource subject, IRI predicate, and node object
tripleRIN :: Rdf.Resource -> Rdf.Iri -> Rdf.Node -> Rdf.Triple
tripleRIN s p o = Rdf.Triple s p o

-- | Create a triple from resource subject, IRI predicate, and IRI object
tripleRII :: Rdf.Resource -> Rdf.Iri -> Rdf.Iri -> Rdf.Triple
tripleRII s p o = Rdf.Triple s p (iriNode o)

-- | Create an integer literal node
integerNode :: Integer -> Rdf.Node
integerNode n = Rdf.NodeLiteral $ Rdf.Literal {
  Rdf.literalLexicalForm = show n,
  Rdf.literalDatatypeIri = xsd "integer",
  Rdf.literalLanguageTag = Nothing }

-- | Create a boolean literal node
booleanNode :: Bool -> Rdf.Node
booleanNode b = Rdf.NodeLiteral $ Rdf.Literal {
  Rdf.literalLexicalForm = if b then "true" else "false",
  Rdf.literalDatatypeIri = xsd "boolean",
  Rdf.literalLanguageTag = Nothing }

-- | Create a string literal node
stringNode :: String -> Rdf.Node
stringNode s = Rdf.NodeLiteral $ Rdf.Literal {
  Rdf.literalLexicalForm = s,
  Rdf.literalDatatypeIri = xsd "string",
  Rdf.literalLanguageTag = Nothing }

-- | Convert a ShapesGraph to RDF triples
shapesGraphToTriples :: Shacl.ShapesGraph -> [Rdf.Triple]
shapesGraphToTriples (Shacl.ShapesGraph defs) =
  concatMap definitionToTriples (S.toList defs)

-- | Convert a ShapesGraph to N-Triples string
shapesGraphToNtriples :: Shacl.ShapesGraph -> String
shapesGraphToNtriples sg =
  Serde.rdfGraphToNtriples $ RdfUtils.descriptionsToGraph descs
  where
    triples = shapesGraphToTriples sg
    descs = map tripleToDescription triples

-- | Convert a single triple to a description (one triple per description)
tripleToDescription :: Rdf.Triple -> Rdf.Description
tripleToDescription t@(Rdf.Triple subj _ _) =
  Rdf.Description {
    Rdf.descriptionSubject = resourceToNode subj,
    Rdf.descriptionGraph = Rdf.Graph (S.singleton t) }

-- | Convert a resource to a node
resourceToNode :: Rdf.Resource -> Rdf.Node
resourceToNode (Rdf.ResourceIri i) = Rdf.NodeIri i
resourceToNode (Rdf.ResourceBnode b) = Rdf.NodeBnode b

-- | Serialize a Definition<Shape> to triples
definitionToTriples :: Shacl.Definition Shacl.Shape -> [Rdf.Triple]
definitionToTriples (Shacl.Definition iri shape) =
  case shape of
    Shacl.ShapeNode ns -> nodeShapeToTriples iri ns
    Shacl.ShapeProperty ps -> propertyShapeToTriples (iriResource iri) ps

-- | Serialize a NodeShape to triples
nodeShapeToTriples :: Rdf.Iri -> Shacl.NodeShape -> [Rdf.Triple]
nodeShapeToTriples iri (Shacl.NodeShape common) =
  [ tripleIII iri (rdf "type") (sh "NodeShape"),
    tripleIII iri (sh "targetClass") iri ]
  ++ commonPropertiesToTriples (iriResource iri) common

-- | Serialize a PropertyShape to triples, given a subject resource (may be blank node)
propertyShapeToTriples :: Rdf.Resource -> Shacl.PropertyShape -> [Rdf.Triple]
propertyShapeToTriples subj ps =
  [ tripleRII subj (rdf "type") (sh "PropertyShape"),
    tripleRII subj (sh "path") (Shacl.propertyShapePath ps) ]
  ++ commonPropertiesToTriples subj (Shacl.propertyShapeCommon ps)
  ++ concatMap (propertyShapeConstraintToTriples subj) (S.toList $ Shacl.propertyShapeConstraints ps)
  ++ maybe [] (\n -> [tripleRIN subj (sh "order") (integerNode n)]) (Shacl.propertyShapeOrder ps)

-- | Serialize CommonProperties to triples
commonPropertiesToTriples :: Rdf.Resource -> Shacl.CommonProperties -> [Rdf.Triple]
commonPropertiesToTriples subj cp =
  concatMap (commonConstraintToTriples subj) (S.toList $ Shacl.commonPropertiesConstraints cp)
  ++ maybe [] (\b -> [tripleRIN subj (sh "deactivated") (booleanNode b)]) (Shacl.commonPropertiesDeactivated cp)
  -- Note: targetClass, targetNode, targetObjectsOf, targetSubjectsOf are typically empty
  -- for shapes generated by the Hydra SHACL coder. The RDF model's RdfsClass and Property
  -- types are stand-ins (not IRIs), so we skip them if non-empty.
  ++ concatMap (\n -> [tripleRIN subj (sh "targetNode") (iriOrLiteralToNode n)]) (S.toList $ Shacl.commonPropertiesTargetNode cp)

-- | Serialize a CommonConstraint to triples
commonConstraintToTriples :: Rdf.Resource -> Shacl.CommonConstraint -> [Rdf.Triple]
commonConstraintToTriples subj cc = case cc of
  Shacl.CommonConstraintDatatype iri ->
    [tripleRII subj (sh "datatype") iri]
  Shacl.CommonConstraintClass _classes ->
    [] -- RdfsClass is a stand-in type (wraps ()), not usable as an IRI
  Shacl.CommonConstraintNodeKind nk ->
    [tripleRII subj (sh "nodeKind") (nodeKindToIri nk)]
  Shacl.CommonConstraintProperty refs ->
    concatMap (propertyRefToTriples subj) (S.toList refs)
  Shacl.CommonConstraintMaxInclusive lit ->
    [tripleRIN subj (sh "maxInclusive") (Rdf.NodeLiteral lit)]
  Shacl.CommonConstraintMinInclusive lit ->
    [tripleRIN subj (sh "minInclusive") (Rdf.NodeLiteral lit)]
  Shacl.CommonConstraintMaxExclusive lit ->
    [tripleRIN subj (sh "maxExclusive") (Rdf.NodeLiteral lit)]
  Shacl.CommonConstraintMinExclusive lit ->
    [tripleRIN subj (sh "minExclusive") (Rdf.NodeLiteral lit)]
  Shacl.CommonConstraintMaxLength n ->
    [tripleRIN subj (sh "maxLength") (integerNode n)]
  Shacl.CommonConstraintMinLength n ->
    [tripleRIN subj (sh "minLength") (integerNode n)]
  Shacl.CommonConstraintPattern (Shacl.Pattern regex flags) ->
    [tripleRIN subj (sh "pattern") (stringNode regex)]
    ++ maybe [] (\f -> [tripleRIN subj (sh "flags") (stringNode f)]) flags
  Shacl.CommonConstraintClosed (Shacl.Closed isClosed _ignored) ->
    [tripleRIN subj (sh "closed") (booleanNode isClosed)]
  Shacl.CommonConstraintHasValue nodes ->
    concatMap (\n -> [tripleRIN subj (sh "hasValue") n]) (S.toList nodes)
  Shacl.CommonConstraintIn nodes ->
    -- sh:in is an RDF list; simplified here as individual triples
    concatMap (\n -> [tripleRIN subj (sh "in") n]) nodes
  Shacl.CommonConstraintNode refs ->
    concatMap (\ref -> case ref of
      Shacl.ReferenceNamed iri -> [tripleRII subj (sh "node") iri]
      _ -> []) (S.toList refs)
  Shacl.CommonConstraintNot refs ->
    concatMap (\ref -> case ref of
      Shacl.ReferenceNamed iri -> [tripleRII subj (sh "not") iri]
      _ -> []) (S.toList refs)
  Shacl.CommonConstraintAnd refs ->
    concatMap (\ref -> case ref of
      Shacl.ReferenceNamed iri -> [tripleRII subj (sh "and") iri]
      _ -> []) (S.toList refs)
  Shacl.CommonConstraintOr refs ->
    concatMap (\ref -> case ref of
      Shacl.ReferenceNamed iri -> [tripleRII subj (sh "or") iri]
      _ -> []) (S.toList refs)
  Shacl.CommonConstraintXone refs ->
    concatMap (\ref -> case ref of
      Shacl.ReferenceNamed iri -> [tripleRII subj (sh "xone") iri]
      _ -> []) (S.toList refs)
  Shacl.CommonConstraintEquals _props ->
    [] -- Property is a stand-in type, not usable as an IRI
  Shacl.CommonConstraintDisjoint _props ->
    [] -- Property is a stand-in type, not usable as an IRI
  Shacl.CommonConstraintLanguageIn _tags ->
    [] -- Language-in requires RDF list encoding; omit for now

-- | Serialize a property shape reference (may be inline or named)
propertyRefToTriples :: Rdf.Resource -> Shacl.Reference Shacl.PropertyShape -> [Rdf.Triple]
propertyRefToTriples subj ref = case ref of
  Shacl.ReferenceNamed iri ->
    [tripleRII subj (sh "property") iri]
  Shacl.ReferenceDefinition (Shacl.Definition iri ps) ->
    [tripleRII subj (sh "property") iri]
    ++ propertyShapeToTriples (iriResource iri) ps
  Shacl.ReferenceAnonymous _ps ->
    -- Anonymous property shapes would need blank node allocation; skip for now
    []

-- | Serialize a PropertyShapeConstraint to triples
propertyShapeConstraintToTriples :: Rdf.Resource -> Shacl.PropertyShapeConstraint -> [Rdf.Triple]
propertyShapeConstraintToTriples subj psc = case psc of
  Shacl.PropertyShapeConstraintMinCount n ->
    [tripleRIN subj (sh "minCount") (integerNode n)]
  Shacl.PropertyShapeConstraintMaxCount n ->
    [tripleRIN subj (sh "maxCount") (integerNode n)]
  Shacl.PropertyShapeConstraintLessThan _props ->
    [] -- Property is a stand-in type, not usable as an IRI
  Shacl.PropertyShapeConstraintLessThanOrEquals _props ->
    [] -- Property is a stand-in type, not usable as an IRI
  Shacl.PropertyShapeConstraintUniqueLang b ->
    [tripleRIN subj (sh "uniqueLang") (booleanNode b)]
  Shacl.PropertyShapeConstraintQualifiedValueShape _ ->
    [] -- Complex; omit for now

-- | Convert a NodeKind to its SHACL IRI
nodeKindToIri :: Shacl.NodeKind -> Rdf.Iri
nodeKindToIri nk = case nk of
  Shacl.NodeKindBlankNode -> sh "BlankNode"
  Shacl.NodeKindIri -> sh "IRI"
  Shacl.NodeKindLiteral -> sh "Literal"
  Shacl.NodeKindBlankNodeOrIri -> sh "BlankNodeOrIRI"
  Shacl.NodeKindBlankNodeOrLiteral -> sh "BlankNodeOrLiteral"
  Shacl.NodeKindIriOrLiteral -> sh "IRIOrLiteral"

-- | Convert an IriOrLiteral to an RDF Node
iriOrLiteralToNode :: Rdf.IriOrLiteral -> Rdf.Node
iriOrLiteralToNode (Rdf.IriOrLiteralIri iri) = Rdf.NodeIri iri
iriOrLiteralToNode (Rdf.IriOrLiteralLiteral lit) = Rdf.NodeLiteral lit