From e43cf7ca9c89d8861211835e681e5294c6834ad2 Mon Sep 17 00:00:00 2001 From: Jose Labra Date: Mon, 20 Aug 2018 09:51:47 +0200 Subject: [PATCH] Added support for owl:imports in SHACL --- .../es/weso/shacl/converter/Shacl2ShEx.scala | 6 +- .../es/weso/shex/converter/ShEx2Shacl.scala | 50 ++++--- .../scala/es/weso/schema/ShaclexSchema.scala | 17 ++- .../main/scala/es/weso/shacl/Component.scala | 12 +- .../scala/es/weso/shacl/PropertyGroup.scala | 7 + .../shacl/{ShapeRef.scala => RefNode.scala} | 2 +- .../scala/es/weso/shacl/SHACLPrefixes.scala | 12 +- .../src/main/scala/es/weso/shacl/Schema.scala | 40 +++--- .../src/main/scala/es/weso/shacl/Shape.scala | 43 ++++-- .../es/weso/shacl/converter/RDF2Shacl.scala | 129 ++++++++++++++---- .../es/weso/shacl/converter/Shacl2RDF.scala | 55 ++++++-- .../weso/shacl/report/ValidationResult.scala | 12 +- .../es/weso/shacl/validator/Attempt.scala | 4 +- .../es/weso/shacl/validator/AttemptInfo.scala | 4 +- .../es/weso/shacl/validator/Evidence.scala | 4 +- .../es/weso/shacl/validator/Validator.scala | 46 +++---- .../test/resources/shacl/imports/hasName.ttl | 12 ++ .../test/resources/shacl/imports/import.ttl | 16 +++ .../es/weso/shacl/AbstractSyntaxTest.scala | 9 +- .../scala/es/weso/shacl/RDF2ShaclTest.scala | 2 +- .../scala/es/weso/shacl/SiblingsTest.scala | 10 +- .../scala/es/weso/shacl/ValidatorTest.scala | 2 +- .../es/weso/shacl/manifest/RDF2Manifest.scala | 2 +- .../report/ReportGeneratorCompatTest.scala | 18 ++- .../shacl/validator/SHACLCheckerTest.scala | 4 +- .../weso/shacl/validator/ShaclCoreTest.scala | 8 +- .../report/ReportGeneratorCompatTest.scala | 2 +- .../src/main/scala/es/weso/rdf/Prefixes.scala | 2 + .../main/scala/es/weso/rdf/RDFBuilder.scala | 4 + .../main/scala/es/weso/rdf/RDFReader.scala | 15 +- .../scala/es/weso/rdf/nodes/RDFNode.scala | 11 +- .../scala/es/weso/rdf/parser/RDFParser.scala | 34 +++-- .../es/weso/rdf/rdf4j/RDFAsRDF4jModel.scala | 55 +++++++- .../scala/es/weso/rdf/jena/Endpoint.scala | 42 +++--- .../es/weso/rdf/jena/RDFAsJenaModel.scala | 66 ++++++++- .../scala/es/weso/rdf/jena/RDFFromWeb.scala | 36 +++-- .../src/test/resources/application.properties | 8 +- .../src/test/resources/rdf/imported.ttl | 6 + .../srdfJena/src/test/resources/rdf/m1.ttl | 5 + .../srdfJena/src/test/resources/rdf/m2.ttl | 5 + .../src/test/resources/rdf/merged.ttl | 5 + .../src/test/resources/rdf/testImport.ttl | 8 ++ .../test/resources/rdf/testImportWithLoop.ttl | 8 ++ .../scala/es/weso/rdf/jena/ImportsTest.scala | 88 ++++++++++++ .../es/weso/rdf/jena/RDFAsJenaModelTest.scala | 5 +- notes/0.1.03.md | 16 +++ version.sbt | 2 +- 47 files changed, 734 insertions(+), 215 deletions(-) create mode 100644 modules/shacl/src/main/scala/es/weso/shacl/PropertyGroup.scala rename modules/shacl/src/main/scala/es/weso/shacl/{ShapeRef.scala => RefNode.scala} (63%) create mode 100644 modules/shacl/src/test/resources/shacl/imports/hasName.ttl create mode 100644 modules/shacl/src/test/resources/shacl/imports/import.ttl create mode 100644 modules/srdfJena/src/test/resources/rdf/imported.ttl create mode 100644 modules/srdfJena/src/test/resources/rdf/m1.ttl create mode 100644 modules/srdfJena/src/test/resources/rdf/m2.ttl create mode 100644 modules/srdfJena/src/test/resources/rdf/merged.ttl create mode 100644 modules/srdfJena/src/test/resources/rdf/testImport.ttl create mode 100644 modules/srdfJena/src/test/resources/rdf/testImportWithLoop.ttl create mode 100644 modules/srdfJena/src/test/scala/es/weso/rdf/jena/ImportsTest.scala create mode 100644 notes/0.1.03.md diff --git a/modules/converter/src/main/scala/es/weso/shacl/converter/Shacl2ShEx.scala b/modules/converter/src/main/scala/es/weso/shacl/converter/Shacl2ShEx.scala index d3185267..d4cb5c79 100644 --- a/modules/converter/src/main/scala/es/weso/shacl/converter/Shacl2ShEx.scala +++ b/modules/converter/src/main/scala/es/weso/shacl/converter/Shacl2ShEx.scala @@ -90,7 +90,7 @@ object Shacl2ShEx { private def cnvComponents(cs: List[shacl.Component]): Result[List[shex.ShapeExpr]] = sequence(cs.map(cnvComponent(_))) - private def cnvPropertyShapes(ps: List[shacl.ShapeRef], + private def cnvPropertyShapes(ps: List[shacl.RefNode], schema: shacl.Schema ): Result[List[shex.ShapeExpr]] = sequence(ps.map(cnvPropertyShapeRef(_, schema))) @@ -138,7 +138,7 @@ object Shacl2ShEx { _ <- modify(_.addLabelTripleExpr(lbl,te)) } yield (()) - private def cnvPropertyShapeRef(sref: shacl.ShapeRef, + private def cnvPropertyShapeRef(sref: shacl.RefNode, schema: shacl.Schema ): Result[shex.ShapeExpr] = for { @@ -252,7 +252,7 @@ object Shacl2ShEx { } } - private def getShape(sref: shacl.ShapeRef, + private def getShape(sref: shacl.RefNode, schema: shacl.Schema ): Result[shacl.Shape] = { fromEither(schema.shape(sref.id)) diff --git a/modules/converter/src/main/scala/es/weso/shex/converter/ShEx2Shacl.scala b/modules/converter/src/main/scala/es/weso/shex/converter/ShEx2Shacl.scala index 2e3cdc32..3936ad86 100644 --- a/modules/converter/src/main/scala/es/weso/shex/converter/ShEx2Shacl.scala +++ b/modules/converter/src/main/scala/es/weso/shex/converter/ShEx2Shacl.scala @@ -19,9 +19,9 @@ object ShEx2Shacl { } private type Err = String - private type ShapesMap = Map[shacl.ShapeRef, shacl.Shape] - private type ShapeExprsMap = Map[shex.ShapeExpr, shacl.ShapeRef] - private type TripleExprsMap = Map[shex.TripleExpr, shacl.ShapeRef] + private type ShapesMap = Map[shacl.RefNode, shacl.Shape] + private type ShapeExprsMap = Map[shex.ShapeExpr, shacl.RefNode] + private type TripleExprsMap = Map[shex.TripleExpr, shacl.RefNode] private case class State(shapesMap: ShapesMap, shapeExprsMap: ShapeExprsMap, @@ -29,11 +29,11 @@ object ShEx2Shacl { counter: Int ) { - def addShapeRefShape(sref: ShapeRef, s: Shape): State = + def addShapeRefShape(sref: RefNode, s: Shape): State = this.copy(shapesMap = shapesMap.updated(sref,s)) - def newShapeExprRef(se: shex.ShapeExpr): (State, shacl.ShapeRef) = { - val id = ShapeRef(BNode("B" + counter)) + def newShapeExprRef(se: shex.ShapeExpr): (State, shacl.RefNode) = { + val id = RefNode(BNode("B" + counter)) (this.copy( shapeExprsMap = shapeExprsMap.updated(se, id), counter = counter + 1 @@ -41,8 +41,8 @@ object ShEx2Shacl { ) } - def newTripleExprRef(te: shex.TripleExpr): (State, shacl.ShapeRef) = { - val id = ShapeRef(BNode("B" + counter)) + def newTripleExprRef(te: shex.TripleExpr): (State, shacl.RefNode) = { + val id = RefNode(BNode("B" + counter)) (this.copy( tripleExprsMap = tripleExprsMap.updated(te, id), counter = counter + 1 @@ -78,7 +78,7 @@ object ShEx2Shacl { private def setState(s: State): Result[Unit] = EitherT.liftF(StateT.set(s)) - private def addShapeRefShape(ref: ShapeRef, shape: Shape): Result[Unit] = + private def addShapeRefShape(ref: RefNode, shape: Shape): Result[Unit] = EitherT.liftF(StateT.modify(_.addShapeRefShape(ref,shape))) private def getShapesMap: Result[ShapesMap] = @@ -88,18 +88,24 @@ object ShEx2Shacl { private def getSchema(schema: shex.Schema): Result[shacl.Schema] = for { _ <- getShaclShapes(schema) smap <- getShapesMap - } yield shacl.Schema(pm = schema.prefixMap, shapesMap = smap) + } yield shacl.Schema( + pm = schema.prefixMap, + imports = List(), + entailments = List(), + shapesMap = smap, + propertyGroups = Map() + ) /* private def cnvPrefixMap(pm: PrefixMap): Map[String, IRI] = { pm.pm.map { case (prefix, value) => (prefix.str, value) } } */ - private def getShaclShapes(schema: shex.Schema): Result[Map[shacl.ShapeRef, shacl.Shape]] = { + private def getShaclShapes(schema: shex.Schema): Result[Map[shacl.RefNode, shacl.Shape]] = { val shexShapes: List[shex.ShapeExpr] = schema.shapes.getOrElse(List()) sequence(shexShapes.map(s => cnvShapeRefShape(s,schema))).map(_.toMap) } - private type ShapeRefShape = (shacl.ShapeRef, shacl.Shape) + private type ShapeRefShape = (shacl.RefNode, shacl.Shape) private def cnvShapeRefShape(s: shex.ShapeExpr, schema: shex.Schema @@ -156,7 +162,7 @@ object ShEx2Shacl { id: RDFNode ): Result[shacl.Shape] = for { ps <- shape.expression match { - case None => ok(List[(shacl.PropertyShape,shacl.ShapeRef)]()) + case None => ok(List[(shacl.PropertyShape,shacl.RefNode)]()) case Some(te) => cnvTripleExpr(te, schema, id) } } yield { @@ -165,7 +171,7 @@ object ShEx2Shacl { private def outCast[A,B >: A](r:Result[A]): Result[B] = r.map(x => x) - private def cnvTripleExpr(te: shex.TripleExpr, schema: shex.Schema, id: RDFNode): Result[List[(shacl.PropertyShape, shacl.ShapeRef)]] = { + private def cnvTripleExpr(te: shex.TripleExpr, schema: shex.Schema, id: RDFNode): Result[List[(shacl.PropertyShape, shacl.RefNode)]] = { te match { case e: shex.EachOf => err(s"cnvTripleExpr: Not implemented EachOf $e conversion yet") case e: shex.OneOf => err(s"cnvTripleExpr: Not implemented OneOf $e conversion yet") @@ -178,7 +184,7 @@ object ShEx2Shacl { private def cnvTripleConstraint(tc: shex.TripleConstraint, schema: shex.Schema, id: RDFNode - ): Result[(shacl.PropertyShape, shacl.ShapeRef)] = { + ): Result[(shacl.PropertyShape, shacl.RefNode)] = { if (tc.negated) err(s"cnvTripleConstraint: Not implemented negated") else { @@ -218,7 +224,7 @@ object ShEx2Shacl { ps: PropertyShape = Shape.emptyPropertyShape(id, path).copy( components = components ) - _ <- addShapeRefShape(ShapeRef(id), ps) + _ <- addShapeRefShape(RefNode(id), ps) } yield ps rs } @@ -246,9 +252,9 @@ object ShEx2Shacl { private def getShapeExprsMap: Result[ShapeExprsMap] = getState.map(_.shapeExprsMap) private def getTripleExprsMap: Result[TripleExprsMap] = getState.map(_.tripleExprsMap) - private def getShapeExprId(se: shex.ShapeExpr): Result[ShapeRef] = se.id match { - case Some(shex.IRILabel(iri)) => ok(ShapeRef(iri)) - case Some(shex.BNodeLabel(bnode)) => ok(ShapeRef(bnode)) + private def getShapeExprId(se: shex.ShapeExpr): Result[RefNode] = se.id match { + case Some(shex.IRILabel(iri)) => ok(RefNode(iri)) + case Some(shex.BNodeLabel(bnode)) => ok(RefNode(bnode)) case None => for { shapeExprsMap <- getShapeExprsMap id <- shapeExprsMap.get(se) match { @@ -262,9 +268,9 @@ object ShEx2Shacl { } yield id } - private def getTripleExprId(te: shex.TripleExpr): Result[ShapeRef] = te.id match { - case Some(shex.IRILabel(iri)) => ok(ShapeRef(iri)) - case Some(shex.BNodeLabel(bnode)) => ok(ShapeRef(bnode)) + private def getTripleExprId(te: shex.TripleExpr): Result[RefNode] = te.id match { + case Some(shex.IRILabel(iri)) => ok(RefNode(iri)) + case Some(shex.BNodeLabel(bnode)) => ok(RefNode(bnode)) case None => for { tripleExprsMap <- getTripleExprsMap id <- tripleExprsMap.get(te) match { diff --git a/modules/schema/src/main/scala/es/weso/schema/ShaclexSchema.scala b/modules/schema/src/main/scala/es/weso/schema/ShaclexSchema.scala index 4b9bd1d8..d100171c 100644 --- a/modules/schema/src/main/scala/es/weso/schema/ShaclexSchema.scala +++ b/modules/schema/src/main/scala/es/weso/schema/ShaclexSchema.scala @@ -4,6 +4,7 @@ import cats.implicits._ import es.weso.rdf._ import es.weso.rdf.nodes._ import es.weso.rdf.jena.RDFAsJenaModel +import es.weso.shacl.SHACLPrefixes.owl_imports import es.weso.shacl.{Schema => ShaclSchema, _} // import es.weso.shacl._ import es.weso.shacl.converter.{RDF2Shacl, Shacl2ShEx} @@ -98,13 +99,21 @@ case class ShaclexSchema(schema: ShaclSchema) extends Schema { override def fromString(cs: CharSequence, format: String, base: Option[String]): Either[String, Schema] = { for { rdf <- RDFAsJenaModel.fromChars(cs, format, base) - schema <- RDF2Shacl.getShacl(rdf) + schema <- RDF2Shacl.getShacl(rdf, true) } yield ShaclexSchema(schema) } - override def fromRDF(rdf: RDFReader): Either[String, Schema] = for { - schemaShacl <- RDF2Shacl.getShacl(rdf) - } yield ShaclexSchema(schemaShacl) + override def fromRDF(rdf: RDFReader): Either[String, Schema] = rdf.asRDFBuilder match { + case Left(_) => rdf.triplesWithPredicate(owl_imports).size match { + case 0 => RDF2Shacl.getShaclFromRDFReader(rdf).map(ShaclexSchema(_)) + case _ => Left(s"Nor supported owl:imports for this kind of RDF model") + } + case Right(rdfBuilder) => + for { + schemaShacl <- RDF2Shacl.getShacl(rdfBuilder, true) + } yield ShaclexSchema(schemaShacl) + } + override def serialize(format: String): Either[String, String] = { val builder: RDFBuilder = RDFAsJenaModel.empty diff --git a/modules/shacl/src/main/scala/es/weso/shacl/Component.scala b/modules/shacl/src/main/scala/es/weso/shacl/Component.scala index ad9865fd..f0c18329 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/Component.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/Component.scala @@ -60,22 +60,22 @@ case class LessThan(p: IRI) extends Component { case class LessThanOrEquals(p: IRI) extends Component { override val name: String = "lessThanOrEquals" } -case class Or(shapes: List[ShapeRef]) extends Component { +case class Or(shapes: List[RefNode]) extends Component { override val name: String = "or" } -case class And(shapes: List[ShapeRef]) extends Component { +case class And(shapes: List[RefNode]) extends Component { override val name: String = "and" } -case class Not(shape: ShapeRef) extends Component { +case class Not(shape: RefNode) extends Component { override val name: String = "not" } -case class Xone(shapes: List[ShapeRef]) extends Component { +case class Xone(shapes: List[RefNode]) extends Component { override val name: String = "xone" } case class Closed(isClosed: Boolean, ignoredProperties: List[IRI]) extends Component { override val name: String = "closed" } -case class NodeComponent(shape: ShapeRef) extends Component { +case class NodeComponent(shape: RefNode) extends Component { override val name: String = "node" } case class HasValue(value: Value) extends Component { @@ -87,7 +87,7 @@ case class In(list: List[Value]) extends Component { // TODO: Change representation to include optional parent shape case class QualifiedValueShape( - shape: ShapeRef, + shape: RefNode, qualifiedMinCount: Option[Int], qualifiedMaxCount: Option[Int], qualifiedValueShapesDisjoint: Option[Boolean]) extends Component { diff --git a/modules/shacl/src/main/scala/es/weso/shacl/PropertyGroup.scala b/modules/shacl/src/main/scala/es/weso/shacl/PropertyGroup.scala new file mode 100644 index 00000000..910b9fb3 --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/PropertyGroup.scala @@ -0,0 +1,7 @@ +package es.weso.shacl +import es.weso.rdf.nodes.{DecimalLiteral, RDFNode} + +case class PropertyGroup( + order: Option[DecimalLiteral], + label: Set[RDFNode] + ) diff --git a/modules/shacl/src/main/scala/es/weso/shacl/ShapeRef.scala b/modules/shacl/src/main/scala/es/weso/shacl/RefNode.scala similarity index 63% rename from modules/shacl/src/main/scala/es/weso/shacl/ShapeRef.scala rename to modules/shacl/src/main/scala/es/weso/shacl/RefNode.scala index e8f7c620..4c482bbb 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/ShapeRef.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/RefNode.scala @@ -2,6 +2,6 @@ package es.weso.shacl import es.weso.rdf.nodes.RDFNode -case class ShapeRef(id: RDFNode) extends AnyVal { +case class RefNode(id: RDFNode) extends AnyVal { def showId = id.toString } diff --git a/modules/shacl/src/main/scala/es/weso/shacl/SHACLPrefixes.scala b/modules/shacl/src/main/scala/es/weso/shacl/SHACLPrefixes.scala index e6b34ac8..83514a0a 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/SHACLPrefixes.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/SHACLPrefixes.scala @@ -34,8 +34,10 @@ object SHACLPrefixes { lazy val sh_description: IRI = sh + "description" lazy val sh_disjoint: IRI = sh + "disjoint" lazy val sh_equals: IRI = sh + "equals" + lazy val sh_entailment: IRI = sh + "entailment" lazy val sh_flags: IRI = sh + "flags" lazy val sh_focusNode: IRI = sh + "focusNode" + lazy val sh_group: IRI = sh + "group" lazy val sh_hasValue: IRI = sh + "hasValue" lazy val sh_ignoredProperties: IRI = sh + "ignoredProperties" lazy val sh_in: IRI = sh + "in" @@ -51,11 +53,12 @@ object SHACLPrefixes { lazy val sh_minLength: IRI = sh + "minLength" lazy val sh_maxLength: IRI = sh + "maxLength" lazy val sh_message: IRI = sh + "message" - lazy val sh_nodeKind: IRI = sh + "nodeKind" lazy val sh_name: IRI = sh + "name" + lazy val sh_nodeKind: IRI = sh + "nodeKind" lazy val sh_node: IRI = sh + "node" lazy val sh_not: IRI = sh + "not" lazy val sh_or: IRI = sh + "or" + lazy val sh_order: IRI = sh + "order" lazy val sh_path: IRI = sh + "path" lazy val sh_pattern: IRI = sh + "pattern" lazy val sh_property: IRI = sh + "property" @@ -67,6 +70,7 @@ object SHACLPrefixes { lazy val sh_resultPath: IRI = sh + "resultPath" lazy val sh_resultSeverity: IRI = sh + "resultSeverity" lazy val sh_resultMessage: IRI = sh + "resultMessage" + lazy val sh_shapesGraph: IRI = sh + "shapesGraph" lazy val sh_severity: IRI = sh + "severity" lazy val sh_sourceConstraintComponent: IRI = sh + "sourceConstraintComponent" lazy val sh_sourceShape: IRI = sh + "sourceShape" @@ -79,11 +83,15 @@ object SHACLPrefixes { lazy val sh_uniqueLang: IRI = sh + "uniqueLang" lazy val sh_xone: IRI = sh + "xone" + lazy val owl_imports: IRI = owl + "imports" + lazy val defaultPrefixMap = PrefixMap( Map( Prefix("sh") -> sh, Prefix("rdf") -> rdf, Prefix("xsd") -> xsd, - Prefix("rdfs") -> rdfs)) + Prefix("rdfs") -> rdfs, + Prefix("owl") -> owl + )) } diff --git a/modules/shacl/src/main/scala/es/weso/shacl/Schema.scala b/modules/shacl/src/main/scala/es/weso/shacl/Schema.scala index 46b5bd6e..c1a4d12b 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/Schema.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/Schema.scala @@ -7,14 +7,17 @@ import es.weso.shacl.converter.Shacl2RDF import scala.util.{Either, Left, Right} import sext._ -case class Schema( - pm: PrefixMap, - shapesMap: Map[ShapeRef, Shape]) { +case class Schema(pm: PrefixMap, + imports: List[IRI], + entailments: List[IRI], + shapesMap: Map[RefNode, Shape], + propertyGroups: Map[RefNode, PropertyGroup] + ) { lazy val shapes: Seq[Shape] = shapesMap.toSeq.map(_._2) - lazy val shapeRefs: Seq[ShapeRef] = + lazy val shapeRefs: Seq[RefNode] = shapesMap.keys.toSeq /** @@ -22,12 +25,12 @@ case class Schema( * @param node IRI that identifies a shape */ def shape(node: RDFNode): Either[String, Shape] = - shapesMap.get(ShapeRef(node)) match { + shapesMap.get(RefNode(node)) match { case None => Left(s"Not found $node in Schema") case Some(shape) => Right(shape) } - private[shacl] def siblingQualifiedShapes(s: ShapeRef): List[ShapeRef] = { + private[shacl] def siblingQualifiedShapes(s: RefNode): List[RefNode] = { val parentShapes: List[Shape] = parents(s). map(shapesMap.get(_)). @@ -39,14 +42,14 @@ case class Schema( collectQualifiedValueShapes(qualifiedPropertyShapes) } - private def collectQualifiedValueShapes(ls: List[ShapeRef]): List[ShapeRef] = { - val zero: List[ShapeRef] = List() - def comb(xs: List[ShapeRef], x: ShapeRef): List[ShapeRef] = + private def collectQualifiedValueShapes(ls: List[RefNode]): List[RefNode] = { + val zero: List[RefNode] = List() + def comb(xs: List[RefNode], x: RefNode): List[RefNode] = qualifiedShapes(x) ++ xs ls.foldLeft(zero)(comb) } - private def qualifiedShapes(p: ShapeRef): List[ShapeRef] = shapesMap.get(p) match { + private def qualifiedShapes(p: RefNode): List[RefNode] = shapesMap.get(p) match { case None => List() case Some(shape) => shape.components.collect { case x: QualifiedValueShape => x.shape }.toList @@ -54,15 +57,15 @@ case class Schema( /* Find shape x such that x sh:property p */ - private[shacl] def parents(p: ShapeRef): List[ShapeRef] = { + private[shacl] def parents(p: RefNode): List[RefNode] = { shapesWithPropertyShape(this.shapeRefs, p) } - private def shapesWithPropertyShape(ls: Seq[ShapeRef], p: ShapeRef): List[ShapeRef] = { + private def shapesWithPropertyShape(ls: Seq[RefNode], p: RefNode): List[RefNode] = { ls.filter(hasPropertyShape(_, p)).toList } - private def hasPropertyShape(s: ShapeRef, p: ShapeRef): Boolean = { + private def hasPropertyShape(s: RefNode, p: RefNode): Boolean = { shapesMap.get(s) match { case None => false // TODO: Maybe raise an error case Some(shape) => @@ -88,8 +91,8 @@ case class Schema( * @return a list of pairs (n,s) where n is the IRI of a node * and s is the IRI of a shape */ - def targetNodeDeclarations: Seq[(RDFNode, IRI)] = { - targetNodeShapes.collect { case (node, shape) if shape.id.isIRI => (node, shape.id.toIRI) } + def targetNodeDeclarations: Seq[(RDFNode, RDFNode)] = { + targetNodeShapes.map {case (node, shape) => (node, shape.id) } } def serialize(format: String = "TURTLE", builder: RDFBuilder): Either[String, String] = { @@ -102,6 +105,7 @@ case class Schema( } } } + } // Companion iriObjects @@ -109,5 +113,9 @@ object Schema { val empty = Schema( pm = SHACLPrefixes.defaultPrefixMap, - shapesMap = Map[ShapeRef, Shape]()) + imports = List(), + entailments = List(), + shapesMap = Map(), + propertyGroups = Map() + ) } diff --git a/modules/shacl/src/main/scala/es/weso/shacl/Shape.scala b/modules/shacl/src/main/scala/es/weso/shacl/Shape.scala index 81416f12..bea4436a 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/Shape.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/Shape.scala @@ -4,15 +4,20 @@ import es.weso.rdf.nodes._ import es.weso.rdf.path.SHACLPath import es.weso.shacl.report.Severity - sealed abstract class Shape { def id: RDFNode def targets: Seq[Target] def components: Seq[Component] - def propertyShapes: Seq[ShapeRef] + def propertyShapes: Seq[RefNode] def closed: Boolean def deactivated: Boolean def message: MessageMap + def name: MessageMap + def description: MessageMap + def order: Option[DecimalLiteral] + def group: Option[RefNode] + def sourceIRI: Option[IRI] + def severity: Option[Severity] def ignoredProperties: List[IRI] @@ -39,7 +44,7 @@ sealed abstract class Shape { def targetObjectsOf: Seq[IRI] = targets.map(_.toTargetObjectsOf).flatten.map(_.pred) - def componentShapes: Seq[ShapeRef] = { + def componentShapes: Seq[RefNode] = { components.collect { case NodeComponent(sref) => sref // case Or(srefs) => srefs @@ -54,12 +59,17 @@ case class NodeShape( id: RDFNode, components: List[Component], targets: Seq[Target], - propertyShapes: Seq[ShapeRef], + propertyShapes: Seq[RefNode], closed: Boolean, ignoredProperties: List[IRI], deactivated: Boolean, message: MessageMap, - severity: Option[Severity] + severity: Option[Severity], + name: MessageMap, + description: MessageMap, + order: Option[DecimalLiteral], + group: Option[RefNode], + sourceIRI: Option[IRI] ) extends Shape { def isPropertyConstraint = false @@ -71,12 +81,17 @@ case class PropertyShape( path: SHACLPath, components: List[Component], targets: Seq[Target], - propertyShapes: Seq[ShapeRef], + propertyShapes: Seq[RefNode], closed: Boolean, ignoredProperties: List[IRI], deactivated: Boolean, message: MessageMap, - severity: Option[Severity] + severity: Option[Severity], + name: MessageMap, + description: MessageMap, + order: Option[DecimalLiteral], + group: Option[RefNode], + sourceIRI: Option[IRI] ) extends Shape { def isPropertyConstraint = true @@ -96,7 +111,12 @@ object Shape { ignoredProperties = List(), deactivated = false, message = MessageMap.empty, - severity = None + severity = None, + name = MessageMap.empty, + description = MessageMap.empty, + order = None, + group = None, + sourceIRI = None ) def emptyPropertyShape( @@ -111,6 +131,11 @@ object Shape { ignoredProperties = List(), deactivated = false, message = MessageMap.empty, - severity = None + severity = None, + name = MessageMap.empty, + description = MessageMap.empty, + order = None, + group = None, + sourceIRI = None ) } diff --git a/modules/shacl/src/main/scala/es/weso/shacl/converter/RDF2Shacl.scala b/modules/shacl/src/main/scala/es/weso/shacl/converter/RDF2Shacl.scala index 0dbfd2c4..a0faa00c 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/converter/RDF2Shacl.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/converter/RDF2Shacl.scala @@ -2,13 +2,17 @@ package es.weso.shacl.converter import com.typesafe.scalalogging.LazyLogging import es.weso.rdf.PREFIXES._ -import es.weso.rdf.RDFReader +import es.weso.rdf.{RDFBuilder, RDFReader} import es.weso.rdf.nodes._ import es.weso.rdf.parser.RDFParser import es.weso.rdf.path._ import es.weso.shacl.SHACLPrefixes._ import es.weso.shacl._ import es.weso.shacl.report._ +import es.weso.utils.EitherUtils +import cats._ +import cats.data._ +import cats.implicits._ import scala.util.{Failure, Success, Try} @@ -16,30 +20,66 @@ object RDF2Shacl extends RDFParser with LazyLogging { // Keep track of parsed shapes // TODO: Refactor this code to use a StateT - val parsedShapes = collection.mutable.Map[ShapeRef, Shape]() + val parsedShapes = collection.mutable.Map[RefNode, Shape]() // TODO: Refactor this code to avoid imperative style var pendingNodes = List[RDFNode]() - def tryGetShacl(rdf: RDFReader): Try[Schema] = - getShacl(rdf).fold( + val parsedPropertyGroups = collection.mutable.Map[RefNode, PropertyGroup]() + + var sourceIRI: Option[IRI] = None + + def tryGetShacl(rdf: RDFBuilder, + resolveImports: Boolean): Try[Schema] = + getShacl(rdf, resolveImports).fold( str => Failure(new Exception(str)), Success(_)) + def getShaclFromRDFReader(rdf: RDFReader): Either[String,Schema] = { + val pm = rdf.getPrefixMap + sourceIRI = rdf.sourceIRI + for { + shapesMap <- shapesMap(rdf) + imports <- parseImports(rdf) + entailments <- parseEntailments(rdf) + } yield Schema( + pm = pm, + imports = imports, + entailments = entailments, + shapesMap = shapesMap, + propertyGroups = parsedPropertyGroups.toMap + ) + } + /** * Parses RDF content and obtains a SHACL Schema and a PrefixMap */ - def getShacl(rdf: RDFReader): Either[String, Schema] = { - val pm = rdf.getPrefixMap + def getShacl(rdf: RDFBuilder, + resolveImports: Boolean = true + ): Either[String, Schema] = { for { - shapesMap <- shapesMap(rdf) - } yield (Schema(pm, shapesMap)) + extendedRdf <- + if (resolveImports) rdf.extendImports() + else Right(rdf) + schema <- getShaclFromRDFReader(extendedRdf) + } yield schema } - type ShapesMap = Map[ShapeRef, Shape] + private def parseEntailments(rdf: RDFReader): Either[String, List[IRI]] = + EitherUtils.sequence( + rdf.triplesWithPredicate(sh_entailment).map(_.obj).toList.map(_.toIRI) + ) + + private def parseImports(rdf: RDFReader): Either[String, List[IRI]] = + EitherUtils.sequence( + rdf.triplesWithPredicate(owl_imports).map(_.obj).toList.map(_.toIRI) + ) + + type ShapesMap = Map[RefNode, Shape] def shapesMap(rdf: RDFReader): Either[String, ShapesMap] = { parsedShapes.clear() + parsedPropertyGroups.clear() val nodeShapes = subjectsWithType(sh_NodeShape, rdf) val propertyShapes = subjectsWithType(sh_PropertyShape, rdf) val shapes = subjectsWithType(sh_Shape, rdf) @@ -68,8 +108,8 @@ object RDF2Shacl extends RDFParser with LazyLogging { type ShaclParser[A] = Set[RDFNode] => RDFParser[(A, Set[RDFNode])] - def shape: RDFParser[ShapeRef] = (n, rdf) => { - val shapeRef = ShapeRef(n) + def shape: RDFParser[RefNode] = (n, rdf) => { + val shapeRef = RefNode(n) if (parsedShapes.contains(shapeRef)) { parseOk(shapeRef) } else { @@ -87,7 +127,7 @@ object RDF2Shacl extends RDFParser with LazyLogging { case _ => None } - def nodeShape: RDFParser[ShapeRef] = (n, rdf) => for { + def nodeShape: RDFParser[RefNode] = (n, rdf) => for { types <- rdfTypes(n, rdf) _ <- failIf(types.contains(sh_PropertyShape), "Node shapes must not have rdf:type sh:PropertyShape")(n, rdf) targets <- targets(n, rdf) @@ -96,6 +136,10 @@ object RDF2Shacl extends RDFParser with LazyLogging { closed <- booleanFromPredicateOptional(sh_closed)(n, rdf) deactivated <- booleanFromPredicateOptional(sh_deactivated)(n,rdf) message <- parseMessage(n, rdf) + name <- parseMessage(n,rdf) + description <- parseMessage(n,rdf) + group <- parsePropertyGroup(n,rdf) + order <- parseOrder(n,rdf) severity <- parseSeverity(n,rdf) ignoredNodes <- { println(s"Parsing deactivated: $deactivated for node: $n") ; @@ -113,13 +157,43 @@ object RDF2Shacl extends RDFParser with LazyLogging { ignoredProperties = ignoredIRIs, deactivated = deactivated.getOrElse(false), message = message, - severity = severity + severity = severity, + name = name, + description = description, + group = group, + order = order, + sourceIRI = sourceIRI ) - val sref = ShapeRef(n) + val sref = RefNode(n) parsedShapes += (sref -> shape) sref } + private def parsePropertyGroup: RDFParser[Option[RefNode]] = (n,rdf) => for { + maybeGroup <- objectFromPredicateOptional(sh_group)(n,rdf) + group <- maybeGroup match { + case None => Right(None) + case Some(groupNode) => { + val ref = RefNode(groupNode) + parsedPropertyGroups.get(ref) match { + case Some(pg) => Right(Some(ref)) + case None => for { + labels <- objectsFromPredicate(rdfs_label)(n,rdf) + order <- parseOrder(n,rdf) + } yield { + val pg = PropertyGroup(order,labels) + parsedPropertyGroups += (ref -> pg) + Some(ref) + } + } + } + } + } yield group + + private def parseOrder: RDFParser[Option[DecimalLiteral]] = (n,rdf) => for { + maybeOrder <- decimalLiteralFromPredicateOptional(sh_order)(n,rdf) + } yield maybeOrder + private def parseSeverity: RDFParser[Option[Severity]] = (n,rdf) => for { maybeIri <- iriFromPredicateOptional(sh_severity)(n,rdf) } yield maybeIri match { @@ -140,7 +214,7 @@ object RDF2Shacl extends RDFParser with LazyLogging { } - def propertyShape: RDFParser[ShapeRef] = (n, rdf) => for { + def propertyShape: RDFParser[RefNode] = (n, rdf) => for { types <- rdfTypes(n, rdf) _ <- failIf(types.contains(sh_NodeShape), "Property shapes must not have rdf:type sh:NodeShape")(n, rdf) targets <- targets(n, rdf) @@ -153,6 +227,10 @@ object RDF2Shacl extends RDFParser with LazyLogging { deactivated <- booleanFromPredicateOptional(sh_deactivated)(n, rdf) message <- parseMessage(n, rdf) severity <- parseSeverity(n,rdf) + name <- parseMessage(n,rdf) + description <- parseMessage(n,rdf) + group <- parsePropertyGroup(n,rdf) + order <- parseOrder(n,rdf) ignoredIRIs <- { println(s"Parsing deactivated: $deactivated for node: $n") nodes2iris(ignoredNodes) @@ -168,10 +246,15 @@ object RDF2Shacl extends RDFParser with LazyLogging { ignoredProperties = ignoredIRIs, deactivated = deactivated.getOrElse(false), message = message, - severity = severity + severity = severity, + name = name, + description = description, + order = order, + group = group, + sourceIRI = sourceIRI ) - val sref = ShapeRef(n) + val sref = RefNode(n) parsedShapes += (sref -> ps) sref } @@ -246,16 +329,16 @@ object RDF2Shacl extends RDFParser with LazyLogging { } yield cs.map(c => NodeShape(id, components = List(c))) } */ - def propertyShapes: RDFParser[Seq[ShapeRef]] = (n, rdf) => { + def propertyShapes: RDFParser[Seq[RefNode]] = (n, rdf) => { for { ps <- objectsFromPredicate(sh_property)(n, rdf) vs <- sequenceEither(ps.toList.map(p => propertyShapeRef(p, rdf))) } yield vs } - def propertyShapeRef: RDFParser[ShapeRef] = (n, rdf) => { + def propertyShapeRef: RDFParser[RefNode] = (n, rdf) => { pendingNodes = n :: pendingNodes - parseOk(ShapeRef(n)) + parseOk(RefNode(n)) } /* @@ -425,12 +508,12 @@ object RDF2Shacl extends RDFParser with LazyLogging { disjoint <- booleanFromPredicateOptional(sh_qualifiedValueShapesDisjoint)(n, rdf) } yield QualifiedValueShape(sref, min, max, disjoint) - def shapeRef: RDFParser[ShapeRef] = (n, rdf) => { + def shapeRef: RDFParser[RefNode] = (n, rdf) => { pendingNodes = n :: pendingNodes - parseOk(ShapeRef(n)) + parseOk(RefNode(n)) } - def shapeRefConst(sref: RDFNode): RDFParser[ShapeRef] = (_, rdf) => + def shapeRefConst(sref: RDFNode): RDFParser[RefNode] = (_, rdf) => shapeRef(sref, rdf) def minCount : RDFParser[List[MinCount]] = parsePredicateIntList(sh_minCount, MinCount) diff --git a/modules/shacl/src/main/scala/es/weso/shacl/converter/Shacl2RDF.scala b/modules/shacl/src/main/scala/es/weso/shacl/converter/Shacl2RDF.scala index 186f5848..ea7adc9a 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/converter/Shacl2RDF.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/converter/Shacl2RDF.scala @@ -1,4 +1,5 @@ package es.weso.shacl.converter + import scala.util._ import cats.data._ import cats.implicits._ @@ -6,7 +7,7 @@ import com.typesafe.scalalogging.LazyLogging import es.weso.rdf.nodes._ import es.weso.shacl.SHACLPrefixes._ import es.weso.rdf.PREFIXES._ -import es.weso.rdf.{RDFBuilder, nodes} +import es.weso.rdf._ import es.weso.rdf.saver.RDFSaver import es.weso.shacl._ import es.weso.shacl.report.Severity @@ -30,6 +31,7 @@ class Shacl2RDF extends RDFSaver with LazyLogging { _ <- addPrefix("rdf", rdf.str) _ <- addPrefix("rdfs", rdfs.str) _ <- sequence(shacl.shapes.toList.map(shape(_))) + _ <- sequence(shacl.propertyGroups.toList.map(propertyGroup)) } yield () } @@ -38,7 +40,21 @@ class Shacl2RDF extends RDFSaver with LazyLogging { case ps: PropertyShape => propertyShape(ps) } - private def shapeRef(shape: ShapeRef): RDFSaver[RDFNode] = ok(shape.id) + private def propertyGroup(pair: (RefNode, PropertyGroup)): RDFSaver[RDFNode] = { + val (ref,pg) = pair + val node = ref.id + for { + _ <- order(node,pg.order) + _ <- labels(node, pg.label) + } yield node + } + + private def labels(node: RDFNode, labels: Set[RDFNode]): RDFSaver[Unit] = + sequence( + labels.toList.map(lbl => addTriple(node,rdfs_label,lbl)) + ).map(_ => ()) + + private def shapeRef(shape: RefNode): RDFSaver[RDFNode] = ok(shape.id) private def makeShapeId(v: RDFNode): RDFSaver[RDFNode] = ok(v) @@ -52,10 +68,10 @@ class Shacl2RDF extends RDFSaver with LazyLogging { case TargetObjectsOf(node) => addTriple(id, sh_targetObjectsOf, node) } - private def propertyShapes(id: RDFNode, ts: Seq[ShapeRef]): RDFSaver[Unit] = + private def propertyShapes(id: RDFNode, ts: Seq[RefNode]): RDFSaver[Unit] = saveList(ts.toList, makePropertyShape(id)) - private def makePropertyShape(id: RDFNode)(p: ShapeRef): RDFSaver[Unit] = + private def makePropertyShape(id: RDFNode)(p: RefNode): RDFSaver[Unit] = for { node <- ok(p.id) // propertyShape(p) _ <- addTriple(id, sh_property, node) @@ -88,7 +104,11 @@ class Shacl2RDF extends RDFSaver with LazyLogging { _ <- closed(shapeNode, ps.closed) _ <- deactivated(shapeNode, ps.deactivated) _ <- ignoredProperties(shapeNode, ps.ignoredProperties) - _ <- message(shapeNode, ps.message) + _ <- messageMap(shapeNode, ps.message, sh_message) + _ <- messageMap(shapeNode, ps.message, sh_name) + _ <- messageMap(shapeNode, ps.message, sh_description) + _ <- order(shapeNode,ps.order) + _ <- group(shapeNode,ps.group) _ <- severity(shapeNode, ps.severity) pathNode <- makePath(ps.path) _ <- addTriple(shapeNode, sh_path, pathNode) @@ -104,12 +124,30 @@ class Shacl2RDF extends RDFSaver with LazyLogging { _ <- deactivated(shapeNode, n.deactivated) _ <- ignoredProperties(shapeNode, n.ignoredProperties) _ <- saveList(n.components, component(shapeNode)) - _ <- message(shapeNode, n.message) + _ <- messageMap(shapeNode, n.message, sh_message) + _ <- messageMap(shapeNode, n.name, sh_name) + _ <- messageMap(shapeNode, n.name, sh_description) _ <- severity(shapeNode, n.severity) + _ <- order(shapeNode,n.order) + _ <- group(shapeNode,n.group) } yield shapeNode - private def message(n: RDFNode, message: MessageMap): RDFSaver[Unit] = - sequence(message.getRDFNodes.map(addTriple(n,sh_message,_)) + private def order(n: RDFNode, maybeValue: Option[DecimalLiteral]): RDFSaver[Unit] = + maybeValue match { + case None => ok(()) + case Some(value) => addTriple(n,sh_order,value) + } + + private def group(n: RDFNode, maybeValue: Option[RefNode]): RDFSaver[Unit] = + maybeValue match { + case None => ok(()) + case Some(pg) => { + addTriple(n, sh_group, pg.id) + } + } + + private def messageMap(n: RDFNode, message: MessageMap, pred: IRI): RDFSaver[Unit] = + sequence(message.getRDFNodes.map(addTriple(n,pred,_)) ).map(_ => ()) private def severity(n: RDFNode, severity: Option[Severity]): RDFSaver[Unit] = @@ -118,7 +156,6 @@ class Shacl2RDF extends RDFSaver with LazyLogging { case Some(s) => addTriple(n,sh_severity,s.toIRI) } - private def component(id: RDFNode)(c: Component): RDFSaver[Unit] = c match { case ClassComponent(v) => addTriple(id, sh_class, v) case Datatype(iri) => addTriple(id, sh_datatype, iri) diff --git a/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationResult.scala b/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationResult.scala index 8f161032..f14da0e3 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationResult.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationResult.scala @@ -15,7 +15,7 @@ case class ValidationResult(focusNode: RDFNode, resultSeverity: Severity, sourceConstraintComponent: IRI, focusPath: Option[SHACLPath], - sourceShape: ShapeRef, + sourceShape: RefNode, values: Seq[RDFNode], message: Seq[LiteralValue], messageMap: MessageMap, @@ -144,16 +144,16 @@ object ValidationResult { def iriOrLiteralKindError(focusNode: RDFNode, attempt: Attempt) = basic("IriOrLiteralConstraintComponent", focusNode, attempt, s"Node $focusNode is not a IRI or a Literal") - def notError(focusNode: RDFNode, attempt: Attempt, shape: ShapeRef) = + def notError(focusNode: RDFNode, attempt: Attempt, shape: RefNode) = basic("NotConstraintComponent", focusNode, attempt, s"Not violation. Expected $focusNode not to satisfy ${shape.showId}") - def andError(focusNode: RDFNode, attempt: Attempt, shapes: List[ShapeRef]) = + def andError(focusNode: RDFNode, attempt: Attempt, shapes: List[RefNode]) = basic("AndConstraintComponent", focusNode, attempt, s"And violation. Expected $focusNode to satisfy all of the shapes ${shapes.map(_.showId).mkString(",")}") - def orError(focusNode: RDFNode, attempt: Attempt, shapes: List[ShapeRef]) = + def orError(focusNode: RDFNode, attempt: Attempt, shapes: List[RefNode]) = basic("OrConstraintComponent", focusNode, attempt, s"Or violation. Expected $focusNode to satisfy some of the shapes ${shapes.map(_.showId).mkString(",")}") - def xoneError(focusNode: RDFNode, attempt: Attempt, shapes: Seq[ShapeRef]) = + def xoneError(focusNode: RDFNode, attempt: Attempt, shapes: Seq[RefNode]) = basic("XoneConstraintComponent", focusNode, attempt, s"Xone violation. Expected $focusNode to satisfy exactly one of the shapes ${shapes.map(_.showId).mkString(",")}") def qualifiedShapeError(focusNode: RDFNode, attempt: Attempt, value: Int, min: Option[Int], max: Option[Int]) = @@ -171,7 +171,7 @@ object ValidationResult { def inError(focusNode: RDFNode, attempt: Attempt, values: Seq[Value]) = basic("InConstraintComponent", focusNode, attempt, s"In violation. Expected $focusNode to be in $values") - def notShapeError(focusNode: RDFNode, shapeRef: ShapeRef, attempt: Attempt) = + def notShapeError(focusNode: RDFNode, shapeRef: RefNode, attempt: Attempt) = basic("notShape", focusNode, attempt, s"Not failed because $focusNode has shape $shapeRef and it should not have") def closedError( diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/Attempt.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/Attempt.scala index 90540c53..1f752467 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/validator/Attempt.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/validator/Attempt.scala @@ -2,7 +2,7 @@ package es.weso.shacl.validator import es.weso.rdf.nodes._ import es.weso.rdf.path.SHACLPath -import es.weso.shacl.{MessageMap, ShapeRef} +import es.weso.shacl.{MessageMap, RefNode} import es.weso.shacl.report.Severity /** * Represents current validation attempt @@ -10,7 +10,7 @@ import es.weso.shacl.report.Severity * It may contain a predicate, path or nothing */ case class Attempt(node: RDFNode, - shapeRef: ShapeRef, + shapeRef: RefNode, messageMap: MessageMap, severity: Severity, path: Option[SHACLPath] diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/AttemptInfo.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/AttemptInfo.scala index 978ad3ec..6371a968 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/validator/AttemptInfo.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/validator/AttemptInfo.scala @@ -3,10 +3,10 @@ package es.weso.shacl.validator import cats._ import es.weso.rdf.nodes._ import es.weso.shacl.report.Severity -import es.weso.shacl.{MessageMap, ShapeRef} +import es.weso.shacl.{MessageMap, RefNode} case class AttemptInfo(node: RDFNode, - shape: ShapeRef, + shape: RefNode, messageMap: MessageMap, severity: Severity ) { diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/Evidence.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/Evidence.scala index 3eb61876..f7486d4a 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/validator/Evidence.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/validator/Evidence.scala @@ -2,7 +2,7 @@ package es.weso.shacl.validator import cats._ import es.weso.rdf.nodes.RDFNode -import es.weso.shacl.ShapeRef +import es.weso.shacl.RefNode case class Evidences(ls: List[Evidence]) @@ -11,7 +11,7 @@ abstract class Evidence { } case class NodeShapeEvidence(node: RDFNode, - shape: ShapeRef, + shape: RefNode, msg: String ) extends Evidence case class MsgEvidence(msg: String) extends Evidence diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/Validator.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/Validator.scala index 1d661a6e..853639a7 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/validator/Validator.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/validator/Validator.scala @@ -101,7 +101,7 @@ case class Validator(schema: Schema) extends MyLogging { def findNodesInClass(cls: RDFNode, rdf: RDFReader): List[RDFNode] = rdf.getSHACLInstances(cls).toList - private def nodeShapeRef(node: RDFNode, shapeRef: ShapeRef, attempt: Attempt): CheckTyping = for { + private def nodeShapeRef(node: RDFNode, shapeRef: RefNode, attempt: Attempt): CheckTyping = for { rdf <- getRDF shape <- getShapeRef(shapeRef, attempt, node) t <- nodeShape(node, shape) @@ -118,7 +118,7 @@ case class Validator(schema: Schema) extends MyLogging { def nodeNodeShape(node: RDFNode, ns: NodeShape): CheckTyping = { logger.debug(s"Node $node - NodeShape ${ns.showId}") logger.debug(s"Node shape is deactivated? (${ns.deactivated})") - val attempt = Attempt(node, ShapeRef(ns.id), ns.message, getSeverity(ns), None) + val attempt = Attempt(node, RefNode(ns.id), ns.message, getSeverity(ns), None) for { t0 <- getTyping t <- runLocal(checkNodeShape(ns)(attempt)(node), _.addType(node, ns)) @@ -136,7 +136,7 @@ case class Validator(schema: Schema) extends MyLogging { def nodePropertyShape(node: RDFNode, ps: PropertyShape): CheckTyping = { logger.debug(s"Node $node - PropertyShape ${ps.showId}") val path = ps.path - val attempt = Attempt(node, ShapeRef(ps.id), ps.message, getSeverity(ps), Some(path)) + val attempt = Attempt(node, RefNode(ps.id), ps.message, getSeverity(ps), Some(path)) if (ps.deactivated) for { t <- addEvidence(attempt, s"Property shape ${ps.showId} is deactivated") } yield (t,true) @@ -200,7 +200,7 @@ case class Validator(schema: Schema) extends MyLogging { private def checkPropertyShapePath(path: SHACLPath) (attempt: Attempt) (node: RDFNode) - (sref: ShapeRef): CheckTyping = { + (sref: RefNode): CheckTyping = { logger.info(s"checkPropertyShapePath $node $sref path: ${path.show}") for { ps <- getPropertyShapeRef(sref, attempt, node) @@ -215,7 +215,7 @@ case class Validator(schema: Schema) extends MyLogging { } yield r } - private def checkPropertyShapes(shapeRefs: List[ShapeRef]): NodeChecker = attempt => node => { + private def checkPropertyShapes(shapeRefs: List[RefNode]): NodeChecker = attempt => node => { logger.debug(s"Check propertyShapes($node, ${shapeRefs.map(_.showId).mkString(",")})") for { pss <- getPropertyShapeRefs(shapeRefs, attempt, node) @@ -272,8 +272,8 @@ case class Validator(schema: Schema) extends MyLogging { r <- combineResultSeq(ts) } yield r - private def propertyShape2PropertyChecker(attempt: Attempt, path: SHACLPath) - (psref: ShapeRef): CheckTyping = { +/* private def propertyShape2PropertyChecker(attempt: Attempt, path: SHACLPath) + (psref: RefNode): CheckTyping = { logger.debug(s"propertyShape2PropertyChecker. path: $path, propertyShape: $psref") val node = attempt.node for { @@ -290,7 +290,7 @@ case class Validator(schema: Schema) extends MyLogging { r <- check _ <- debug(s"Result of propertyShape2PropertyChecker\n${showResult(r)}") } yield r - } + } */ private def component2PropertyChecker(p: PropertyShape)(attempt: Attempt, path: SHACLPath)(c: Component): CheckTyping = { logger.debug(s"component2PropertyChecker. propertyShape: $p, path: $path, component: $c") @@ -319,7 +319,7 @@ case class Validator(schema: Schema) extends MyLogging { } yield t } - private def nodeComponentChecker(sref: ShapeRef): NodeChecker = attempt => node => { + private def nodeComponentChecker(sref: RefNode): NodeChecker = attempt => node => { for { s <- getShapeRef(sref, attempt, node) typing <- getTyping @@ -529,7 +529,7 @@ case class Validator(schema: Schema) extends MyLogging { } yield t } - private def and(srefs: Seq[ShapeRef]): NodeChecker = attempt => node => { + private def and(srefs: Seq[RefNode]): NodeChecker = attempt => node => { for { shapes <- getShapeRefs(srefs.toList, attempt, node) r <- checkAllWithTyping(shapes, (s: Shape) => nodeShape(node,s)) @@ -540,11 +540,11 @@ case class Validator(schema: Schema) extends MyLogging { t.getFailedValues(node).isEmpty } */ - private def xone(sRefs: Seq[ShapeRef]): NodeChecker = attempt => node => { + private def xone(sRefs: Seq[RefNode]): NodeChecker = attempt => node => { for { t <- getTyping // shapes <- getShapeRefs(sRefs.toList, attempt, node) - r <- checkSomeFlagCount(sRefs.toStream, (s: ShapeRef) => nodeShapeRef(node,s,attempt),t) + r <- checkSomeFlagCount(sRefs.toStream, (s: RefNode) => nodeShapeRef(node,s,attempt),t) count = r._2 t1 <- condition(count == 1, attempt, @@ -558,7 +558,7 @@ case class Validator(schema: Schema) extends MyLogging { } private def qualifiedValueShape( - shape: ShapeRef, + shape: RefNode, p: PropertyShape, min: Option[Int], max: Option[Int], @@ -586,11 +586,11 @@ case class Validator(schema: Schema) extends MyLogging { } private def filterConformSiblings(values: Seq[RDFNode], p: PropertyShape, attempt: Attempt): Check[Seq[RDFNode]] = { - val shapes = schema.siblingQualifiedShapes(ShapeRef(p.id)) + val shapes = schema.siblingQualifiedShapes(RefNode(p.id)) filterConformShapes(values, shapes, attempt) } - private def filterConformShapes(values: Seq[RDFNode], shapes: Seq[ShapeRef], attempt: Attempt): Check[Seq[RDFNode]] = { + private def filterConformShapes(values: Seq[RDFNode], shapes: Seq[RefNode], attempt: Attempt): Check[Seq[RDFNode]] = { logger.debug(s"FilterConformShapes(values=$values, shapes=$shapes)") def checkValuesShapes: Check[List[(RDFNode, Boolean)]] = { sequence(values.toList.map(value => conformsNodeShapes(value, shapes, attempt))) @@ -605,7 +605,7 @@ case class Validator(schema: Schema) extends MyLogging { } private def conformsNodeShapes(node: RDFNode, - shapes: Seq[ShapeRef], + shapes: Seq[RefNode], attempt: Attempt): Check[(RDFNode, Boolean)] = for { ls <- checkLs(shapes.toList.map(nodeShapeRef(node, _, attempt))) } yield (node, !ls.isEmpty) @@ -617,13 +617,13 @@ case class Validator(schema: Schema) extends MyLogging { case (Some(min), Some(max)) => v >= min && v <= max } - private def or(sRefs: Seq[ShapeRef]): NodeChecker = attempt => node => { + private def or(sRefs: Seq[RefNode]): NodeChecker = attempt => node => { val last: CheckTyping = fail(s"None of the components of or pass") - def fn(sref: ShapeRef): CheckTyping = nodeShapeRef(node, sref, attempt) + def fn(sref: RefNode): CheckTyping = nodeShapeRef(node, sref, attempt) checkSomeFlag(sRefs.toStream,fn,last) } - private def not(sref: ShapeRef): NodeChecker = attempt => node => { + private def not(sref: RefNode): NodeChecker = attempt => node => { for { shape <- getShapeRef(sref, attempt, node) typing <- getTyping @@ -747,13 +747,13 @@ case class Validator(schema: Schema) extends MyLogging { } yield t } - private def getShapeRefs(sRefs: List[ShapeRef], attempt: Attempt, node: RDFNode): Check[List[Shape]] = + private def getShapeRefs(sRefs: List[RefNode], attempt: Attempt, node: RDFNode): Check[List[Shape]] = sequence(sRefs.map(getShapeRef(_, attempt, node))) - private def getPropertyShapeRefs(srefs: List[ShapeRef], attempt: Attempt, node: RDFNode): Check[List[PropertyShape]] = + private def getPropertyShapeRefs(srefs: List[RefNode], attempt: Attempt, node: RDFNode): Check[List[PropertyShape]] = sequence(srefs.map(getPropertyShapeRef(_, attempt, node))) - private def getPropertyShapeRef(sref: ShapeRef, attempt: Attempt, node: RDFNode): Check[PropertyShape] = for { + private def getPropertyShapeRef(sref: RefNode, attempt: Attempt, node: RDFNode): Check[PropertyShape] = for { shape <- getShapeRef(sref, attempt, node) ps <- shape2PropertyShape(shape, attempt, node) } yield ps @@ -785,7 +785,7 @@ case class Validator(schema: Schema) extends MyLogging { } } - private def getShapeRef(sref: ShapeRef, attempt: Attempt, node: RDFNode): Check[Shape] = + private def getShapeRef(sref: RefNode, attempt: Attempt, node: RDFNode): Check[Shape] = schema.shapesMap.get(sref) match { case Some(shape) => ok(shape) case None => err(notFoundShapeRef(node, attempt, diff --git a/modules/shacl/src/test/resources/shacl/imports/hasName.ttl b/modules/shacl/src/test/resources/shacl/imports/hasName.ttl new file mode 100644 index 00000000..e594dbfd --- /dev/null +++ b/modules/shacl/src/test/resources/shacl/imports/hasName.ttl @@ -0,0 +1,12 @@ +prefix : +prefix foaf: +prefix rdfs: +prefix schema: +prefix sh: +prefix xsd: +prefix owl: + +:hasName a sh:PropertyShape ; + sh:path :name ; + sh:minCount 1 . + diff --git a/modules/shacl/src/test/resources/shacl/imports/import.ttl b/modules/shacl/src/test/resources/shacl/imports/import.ttl new file mode 100644 index 00000000..a8e6b840 --- /dev/null +++ b/modules/shacl/src/test/resources/shacl/imports/import.ttl @@ -0,0 +1,16 @@ +prefix : +prefix foaf: +prefix rdfs: +prefix schema: +prefix sh: +prefix xsd: +prefix owl: + +<> owl:imports . + +:Person a sh:NodeShape ; + sh:property :hasName . + +:Person sh:targetNode :alice . + +:alice :name "Alice" . diff --git a/modules/shacl/src/test/scala/es/weso/shacl/AbstractSyntaxTest.scala b/modules/shacl/src/test/scala/es/weso/shacl/AbstractSyntaxTest.scala index 4cd9900d..1e0a7221 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/AbstractSyntaxTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/AbstractSyntaxTest.scala @@ -13,12 +13,17 @@ class AbstractSyntaxTest extends FunSpec with Matchers { id = id, components = List(), targets = List(), - propertyShapes = List(ShapeRef(x)), + propertyShapes = List(RefNode(x)), false, List(), false, MessageMap.empty, - None + None, + name = MessageMap.empty, + description = MessageMap.empty, + order = None, + group = None, + sourceIRI = None ) shape.id should be(id) diff --git a/modules/shacl/src/test/scala/es/weso/shacl/RDF2ShaclTest.scala b/modules/shacl/src/test/scala/es/weso/shacl/RDF2ShaclTest.scala index 6c54b8e2..bdc27fb3 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/RDF2ShaclTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/RDF2ShaclTest.scala @@ -108,7 +108,7 @@ class RDF2ShaclTest extends FunSpec with Matchers with TryValues with EitherValu attempt match { case Left(e) => fail(s"Failed $e") case Right(shape) => { - shape.propertyShapes should contain only (ShapeRef(prop)) + shape.propertyShapes should contain only (RefNode(prop)) } } diff --git a/modules/shacl/src/test/scala/es/weso/shacl/SiblingsTest.scala b/modules/shacl/src/test/scala/es/weso/shacl/SiblingsTest.scala index 3d78c4ea..fa1ef857 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/SiblingsTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/SiblingsTest.scala @@ -56,15 +56,15 @@ class SiblingsTest extends FunSpec describe("Parent") { it("should be able to find parent of a shape") { - val eitherParents: Either[String, List[ShapeRef]] = for { + val eitherParents: Either[String, List[RefNode]] = for { rdf <- RDFAsJenaModel.fromChars(str, "TURTLE") schema <- RDF2Shacl.getShacl(rdf) - } yield schema.parents(ShapeRef(psFemale)) + } yield schema.parents(RefNode(psFemale)) eitherParents match { case Right(ps) => { info(s"Parents found: $ps") - ps should contain only (ShapeRef(marriage)) + ps should contain only (RefNode(marriage)) } case Left(e) => fail(e) } @@ -73,10 +73,10 @@ class SiblingsTest extends FunSpec describe("SiblingQualifiedValueShapes") { it("should be able to find siblings of a shape") { - val eitherShapes: Either[String, List[ShapeRef]] = for { + val eitherShapes: Either[String, List[RefNode]] = for { rdf <- RDFAsJenaModel.fromChars(str, "TURTLE") schema <- RDF2Shacl.getShacl(rdf) - } yield (schema.siblingQualifiedShapes(ShapeRef(psFemale))) + } yield (schema.siblingQualifiedShapes(RefNode(psFemale))) eitherShapes match { case Right(ss) => { diff --git a/modules/shacl/src/test/scala/es/weso/shacl/ValidatorTest.scala b/modules/shacl/src/test/scala/es/weso/shacl/ValidatorTest.scala index a1ab47e9..788385ca 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/ValidatorTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/ValidatorTest.scala @@ -74,7 +74,7 @@ class ValidatorTest extends FunSpec with Matchers with TryValues with OptionValu val good2 = ex + "good2" val bad1 = ex + "bad1" val ps = Shape.emptyPropertyShape(PS, PredicatePath(p)).copy(components = List(MinCount(1))) - val psRefs = Seq(ShapeRef(PS)) + val psRefs = Seq(RefNode(PS)) val s = Shape.empty(S).copy( targets = Seq(TargetNode(x)), propertyShapes = psRefs) diff --git a/modules/shacl/src/test/scala/es/weso/shacl/manifest/RDF2Manifest.scala b/modules/shacl/src/test/scala/es/weso/shacl/manifest/RDF2Manifest.scala index d6709fd8..4a8dc638 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/manifest/RDF2Manifest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/manifest/RDF2Manifest.scala @@ -255,7 +255,7 @@ object RDF2Manifest extends LazyLogging { format: String, base: Option[String], derefIncludes: Boolean - ): Either[String, (Manifest,RDFReader)] = { + ): Either[String, (Manifest,RDFBuilder)] = { for { cs <- getContents(fileName) rdf <- RDFAsJenaModel.fromChars(cs, format, None).map(_.normalizeBNodes) diff --git a/modules/shacl/src/test/scala/es/weso/shacl/report/ReportGeneratorCompatTest.scala b/modules/shacl/src/test/scala/es/weso/shacl/report/ReportGeneratorCompatTest.scala index 8466a7c8..8edbd8e2 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/report/ReportGeneratorCompatTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/report/ReportGeneratorCompatTest.scala @@ -4,7 +4,7 @@ import java.io._ import java.nio.file.{Path, Paths} import com.typesafe.config._ -import es.weso.rdf.RDFReader +import es.weso.rdf.{RDFBuilder, RDFReader} import es.weso.rdf.jena.RDFAsJenaModel import es.weso.rdf.nodes.{IRI, RDFNode} import es.weso.rdf.parser.RDFParser @@ -64,7 +64,7 @@ class ReportGeneratorCompatTest extends FunSpec with Matchers with RDFParser { } } } - def processManifest(m: Manifest, name: String, parentFolder: Path, rdfManifest: RDFReader): Unit = { + def processManifest(m: Manifest, name: String, parentFolder: Path, rdfManifest: RDFBuilder): Unit = { // println(s"processManifest with ${name} and parent folder $parentFolder") for ((includeNode, manifest) <- m.includes) { describeManifest(includeNode, parentFolder) @@ -73,7 +73,7 @@ class ReportGeneratorCompatTest extends FunSpec with Matchers with RDFParser { processEntry(e,name,parentFolder, rdfManifest) } - def processEntry(e: manifest.Entry, name: String, parentFolder: Path, rdfManifest: RDFReader): Unit = { + def processEntry(e: manifest.Entry, name: String, parentFolder: Path, rdfManifest: RDFBuilder): Unit = { println(s"Should check entry ${e.node.getLexicalForm} with $parentFolder") getSchemaRdf(e.action, name, parentFolder,rdfManifest) match { case Left(f) => { @@ -89,7 +89,11 @@ class ReportGeneratorCompatTest extends FunSpec with Matchers with RDFParser { } } - def getSchemaRdf(a: ManifestAction, fileName: String, parentFolder: Path, manifestRdf: RDFReader): Either[String, (Schema,RDFReader)] = for { + def getSchemaRdf(a: ManifestAction, + fileName: String, + parentFolder: Path, + manifestRdf: RDFBuilder + ): Either[String, (Schema,RDFReader)] = for { pair <- getSchema(a,fileName,parentFolder,manifestRdf) (schema,schemaRdf) = pair dataRdf <- getData(a,fileName,parentFolder,manifestRdf,schemaRdf) @@ -110,7 +114,11 @@ class ReportGeneratorCompatTest extends FunSpec with Matchers with RDFParser { } } - def getSchema(a: ManifestAction, fileName: String, parentFolder: Path, manifestRdf: RDFReader): Either[String, (Schema, RDFReader)] = { + def getSchema(a: ManifestAction, + fileName: String, + parentFolder: Path, + manifestRdf: RDFBuilder + ): Either[String, (Schema, RDFReader)] = { val parentIri = absoluteIri // absoluteIri.resolve(IRI(parentFolder)) a.schema match { case None => { diff --git a/modules/shacl/src/test/scala/es/weso/shacl/validator/SHACLCheckerTest.scala b/modules/shacl/src/test/scala/es/weso/shacl/validator/SHACLCheckerTest.scala index 538968b7..20f03106 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/validator/SHACLCheckerTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/validator/SHACLCheckerTest.scala @@ -7,7 +7,7 @@ import cats.implicits._ import es.weso.rdf.RDFReader import es.weso.rdf.jena.RDFAsJenaModel import es.weso.rdf.nodes._ -import es.weso.shacl.{MessageMap, Shape, ShapeRef} +import es.weso.shacl.{MessageMap, Shape, RefNode} import es.weso.shacl.report.{Severity, ValidationResult} // import es.weso.shacl.validator.ShapeTyping._ // import es.weso.typing.Typing @@ -23,7 +23,7 @@ class SHACLCheckerTest extends FunSpec with Matchers with TryValues with OptionV def mkErr(str: String): ValidationResult = ValidationResult.basic("",StringLiteral(str),Attempt(StringLiteral(str), - ShapeRef(StringLiteral(str)), + RefNode(StringLiteral(str)), MessageMap.empty, Severity.defaultSeverity,None ),str) diff --git a/modules/shacl/src/test/scala/es/weso/shacl/validator/ShaclCoreTest.scala b/modules/shacl/src/test/scala/es/weso/shacl/validator/ShaclCoreTest.scala index cf073885..6265a8ce 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/validator/ShaclCoreTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/validator/ShaclCoreTest.scala @@ -41,7 +41,7 @@ class ShaclCoreTest extends FunSpec with Matchers with TryValues with OptionValu } } } - def processManifest(m: Manifest, name: String, parentFolder: Path, rdfManifest: RDFReader): Unit = { + def processManifest(m: Manifest, name: String, parentFolder: Path, rdfManifest: RDFBuilder): Unit = { // println(s"processManifest with ${name} and parent folder $parentFolder") for ((includeNode, manifest) <- m.includes) { describeManifest(includeNode, parentFolder) @@ -50,7 +50,7 @@ class ShaclCoreTest extends FunSpec with Matchers with TryValues with OptionValu processEntry(e,name,parentFolder, rdfManifest) } - def processEntry(e: manifest.Entry, name: String, parentFolder: Path, rdfManifest: RDFReader): Unit = { + def processEntry(e: manifest.Entry, name: String, parentFolder: Path, rdfManifest: RDFBuilder): Unit = { it(s"Should check entry ${e.node.getLexicalForm} with $parentFolder") { getSchemaRdf(e.action, name, parentFolder,rdfManifest) match { case Left(f) => { @@ -63,7 +63,7 @@ class ShaclCoreTest extends FunSpec with Matchers with TryValues with OptionValu } } - def getSchemaRdf(a: ManifestAction, fileName: String, parentFolder: Path, manifestRdf: RDFReader): Either[String, (Schema,RDFReader)] = for { + def getSchemaRdf(a: ManifestAction, fileName: String, parentFolder: Path, manifestRdf: RDFBuilder): Either[String, (Schema,RDFReader)] = for { pair <- getSchema(a,fileName,parentFolder,manifestRdf) (schema,schemaRdf) = pair dataRdf <- getData(a,fileName,parentFolder,manifestRdf,schemaRdf) @@ -84,7 +84,7 @@ class ShaclCoreTest extends FunSpec with Matchers with TryValues with OptionValu } } - def getSchema(a: ManifestAction, fileName: String, parentFolder: Path, manifestRdf: RDFReader): Either[String, (Schema, RDFReader)] = { + def getSchema(a: ManifestAction, fileName: String, parentFolder: Path, manifestRdf: RDFBuilder): Either[String, (Schema, RDFReader)] = { info(s"Manifest action $a, fileName $fileName, parent: $parentFolder }") val parentIri = absoluteIri // absoluteIri.resolve(IRI(parentFolder)) a.schema match { diff --git a/modules/shex/src/test/scala/es/weso/shex/report/ReportGeneratorCompatTest.scala b/modules/shex/src/test/scala/es/weso/shex/report/ReportGeneratorCompatTest.scala index 302e25b6..1c458d80 100644 --- a/modules/shex/src/test/scala/es/weso/shex/report/ReportGeneratorCompatTest.scala +++ b/modules/shex/src/test/scala/es/weso/shex/report/ReportGeneratorCompatTest.scala @@ -90,7 +90,7 @@ class ReportGeneratorCompatTest extends FunSpec with Matchers with RDFParser { SingleTestReport( passed = false, name = name, - uriTest = node.toIRI.str, + uriTest = node.getLexicalForm, testType = sht_ValidationTest.str, moreInfo = s"Error ${e}") } diff --git a/modules/srdf/src/main/scala/es/weso/rdf/Prefixes.scala b/modules/srdf/src/main/scala/es/weso/rdf/Prefixes.scala index 74e8b508..1dee68b3 100644 --- a/modules/srdf/src/main/scala/es/weso/rdf/Prefixes.scala +++ b/modules/srdf/src/main/scala/es/weso/rdf/Prefixes.scala @@ -39,6 +39,8 @@ object PREFIXES { lazy val rdf_first = rdf.add("first") lazy val rdf_rest = rdf.add("rest") lazy val rdf_langString = rdf.add("langString") + + lazy val rdfs_label = rdfs.add("subClassOf") lazy val rdfs_subClassOf = rdfs.add("subClassOf") lazy val sh_alternativePath: IRI = sh + "alternativePath" diff --git a/modules/srdf/src/main/scala/es/weso/rdf/RDFBuilder.scala b/modules/srdf/src/main/scala/es/weso/rdf/RDFBuilder.scala index 74ebd237..d1d4bea8 100644 --- a/modules/srdf/src/main/scala/es/weso/rdf/RDFBuilder.scala +++ b/modules/srdf/src/main/scala/es/weso/rdf/RDFBuilder.scala @@ -30,5 +30,9 @@ trait RDFBuilder extends RDFReader { def empty: Rdf + def merge(other: RDFReader): Either[String, Rdf] + + def extendImports(): Either[String, Rdf] + } diff --git a/modules/srdf/src/main/scala/es/weso/rdf/RDFReader.scala b/modules/srdf/src/main/scala/es/weso/rdf/RDFReader.scala index a93c05e3..0cf178b1 100644 --- a/modules/srdf/src/main/scala/es/weso/rdf/RDFReader.scala +++ b/modules/srdf/src/main/scala/es/weso/rdf/RDFReader.scala @@ -60,7 +60,7 @@ trait RDFReader { */ // TODO: Extend this to return all iriObjects: Seq[RDFNode] def iriObjects(): Set[IRI] = { - rdfTriples.map(_.obj).filter(_.isIRI).map(_.toIRI) + rdfTriples.map(_.obj).collect { case i: IRI => i } } /** @@ -195,7 +195,20 @@ trait RDFReader { def getNumberOfStatements(): Either[String,Int] + /** + * + * @param other RDF reader + * @return true if this RDF graph is isomorphic with other + */ def isIsomorphicWith(other: RDFReader): Either[String,Boolean] + /** + * @return Source IRI of this RDF graph if exists + */ + def sourceIRI: Option[IRI] + + def asRDFBuilder: Either[String, RDFBuilder] + + def rdfReaderName: String } diff --git a/modules/srdf/src/main/scala/es/weso/rdf/nodes/RDFNode.scala b/modules/srdf/src/main/scala/es/weso/rdf/nodes/RDFNode.scala index 760ac6c2..c0020d51 100644 --- a/modules/srdf/src/main/scala/es/weso/rdf/nodes/RDFNode.scala +++ b/modules/srdf/src/main/scala/es/weso/rdf/nodes/RDFNode.scala @@ -18,13 +18,12 @@ abstract class RDFNode { case _ => false } - def isNonLiteral = this.isIRI || this.isBNode + def isNonLiteral = + this.isIRI || this.isBNode - // Change this code to use Option - def toIRI = this match { - case i: IRI => i - case _ => - throw RDFNodeException("Cannot convert RDFNode " + this + " to IRI") + def toIRI: Either[String,IRI] = this match { + case i: IRI => Right(i) + case _ => Left(s"Cannot convert node $this to IRI") } def getLexicalForm: String diff --git a/modules/srdf/src/main/scala/es/weso/rdf/parser/RDFParser.scala b/modules/srdf/src/main/scala/es/weso/rdf/parser/RDFParser.scala index 0c90eb06..4ce3a28e 100644 --- a/modules/srdf/src/main/scala/es/weso/rdf/parser/RDFParser.scala +++ b/modules/srdf/src/main/scala/es/weso/rdf/parser/RDFParser.scala @@ -64,7 +64,7 @@ trait RDFParser { /** * Returns the String associated with a predicate `p` * @param p predicate - * @return An RDFParser that returns the String associate with that predicate + * @return An RDFParser that returns the String associated with that predicate * */ def stringFromPredicate(p: IRI): RDFParser[String] = { (n, rdf) => @@ -77,6 +77,23 @@ trait RDFParser { } yield str } + /** + * Returns the Decimal literal associated with a predicate `p` + * @param p predicate + * @return An RDFParser that returns the decimal literal associated with that predicate + * + */ + def decimalLiteralFromPredicate(p: IRI): RDFParser[DecimalLiteral] = { (n, rdf) => + for { + obj <- objectFromPredicate(p)(n, rdf) + node <- obj match { + case d: DecimalLiteral => parseOk(d) + case _ => parseFail("Value of predicate " + p + " must be a decimal literal but it is: " + obj) + } + } yield node + } + + /** * */ @@ -86,6 +103,9 @@ trait RDFParser { def objectFromPredicateOptional(p: IRI): RDFParser[Option[RDFNode]] = optional(objectFromPredicate(p)) + def decimalLiteralFromPredicateOptional(p: IRI): RDFParser[Option[DecimalLiteral]] = + optional(decimalLiteralFromPredicate(p)) + /** * Returns a parser that obtains the type associated with the current node *

@@ -233,7 +253,10 @@ trait RDFParser { def hasSomeRDFType(ts: Set[IRI]): RDFParser[Boolean] = { (n, rdf) => for { declaredTypes <- objectsFromPredicate(rdf_type)(n, rdf) - } yield (declaredTypes.map(_.toIRI).diff(ts)).size > 0 + } yield { + val iriTypes = declaredTypes.collect { case i: IRI => i} + iriTypes.diff(ts).size > 0 + } } /** @@ -589,7 +612,7 @@ trait RDFParser { else parseFail(s"Expected rdf_nil but got $n") def nodes2iris(ns: List[RDFNode]): Either[String, List[IRI]] = { - sequenceEither(ns.map(node2IRI(_))) + sequenceEither(ns.map(_.toIRI)) } // Todo: Use "sequence" when I find why it gives a type error... @@ -606,11 +629,6 @@ trait RDFParser { xs.foldLeft(zero)(next) } - def node2IRI(node: RDFNode): Either[String, IRI] = node match { - case (i: IRI) => Right(i) - case _ => Left(s"$node is not an IRI\n") - } - /* def fromEitherString[A](e: Either[String,A]): Try[A] = e.fold(str => parseFail(str),v => Success(v)) */ diff --git a/modules/srdf4j/src/main/scala/es/weso/rdf/rdf4j/RDFAsRDF4jModel.scala b/modules/srdf4j/src/main/scala/es/weso/rdf/rdf4j/RDFAsRDF4jModel.scala index fdca437d..e10e57e9 100644 --- a/modules/srdf4j/src/main/scala/es/weso/rdf/rdf4j/RDFAsRDF4jModel.scala +++ b/modules/srdf4j/src/main/scala/es/weso/rdf/rdf4j/RDFAsRDF4jModel.scala @@ -7,7 +7,7 @@ import es.weso.rdf.path.SHACLPath import es.weso.rdf.triples.RDFTriple import io.circe.Json import org.eclipse.rdf4j.model.{IRI => IRI_RDF4j, BNode => _, Literal => _, _} -import es.weso.rdf.nodes._ +import es.weso.rdf.nodes.{IRI, _} import org.eclipse.rdf4j.model.util.{ModelBuilder, Models} import org.eclipse.rdf4j.rio.RDFFormat._ import org.eclipse.rdf4j.rio.{RDFFormat, Rio} @@ -17,8 +17,10 @@ import scala.util._ import scala.collection.JavaConverters._ import RDF4jMapper._ import cats.implicits._ +import es.weso.utils.EitherUtils -case class RDFAsRDF4jModel(model: Model) +case class RDFAsRDF4jModel(model: Model, + sourceIRI: Option[IRI] = None) extends RDFReader with RDFBuilder with RDFReasoner { @@ -233,6 +235,52 @@ case class RDFAsRDF4jModel(model: Model) case _ => Left(s"Cannot compare RDFAsJenaModel with reader of different type: ${other.getClass.toString}") } + override def merge(other: RDFReader): Either[String,Rdf] = other match { + // TODO: optimize merge using RDF4j merge... + // case rdf4j: RDFAsRDF4jModel => + case _ => { + val zero: Either[String,Rdf] = Right(this) + def cmb(next: Either[String,Rdf], x: RDFTriple): Either[String,Rdf] = for { + rdf1 <- next + rdf2 <- rdf1.addTriple(x) + } yield rdf2 + other.rdfTriples.foldLeft(zero)(cmb) + } + } + + override def extendImports():Either[String, Rdf] = for { + imports <- getImports + newRdf <- extendImports(this,imports,List(IRI(""))) + } yield newRdf + + private lazy val owlImports = IRI("http://www.w3.org/2002/07/owl#imports") + + private def getImports: Either[String, List[IRI]] = + EitherUtils.sequence( + triplesWithPredicate(owlImports).toList.map(_.obj).map(_.toIRI) + ) + + private def extendImports(rdf: Rdf, + imports: List[IRI], + visited: List[IRI] + ): Either[String,Rdf] = { + imports match { + case Nil => Right(rdf) + case iri :: rest => + if (visited contains iri) + extendImports(rdf,rest,visited) + else for { + newRdf <- RDFAsRDF4jModel.fromIRI(iri) + merged <- merge(newRdf) + restRdf <- extendImports(merged,rest,iri :: visited) + } yield restRdf + } + } + + override def asRDFBuilder: Either[String, RDFBuilder] = + Right(this) + + override def rdfReaderName: String = s"RDF4j" } @@ -256,5 +304,8 @@ object RDFAsRDF4jModel { formats.map(_.getName) } + def fromIRI(iri: IRI): Either[String,RDFAsRDF4jModel] = { + Left(s"Not implemented get RDF4j from IRI: $iri") + } } diff --git a/modules/srdfJena/src/main/scala/es/weso/rdf/jena/Endpoint.scala b/modules/srdfJena/src/main/scala/es/weso/rdf/jena/Endpoint.scala index 33555520..7eb3c316 100644 --- a/modules/srdfJena/src/main/scala/es/weso/rdf/jena/Endpoint.scala +++ b/modules/srdfJena/src/main/scala/es/weso/rdf/jena/Endpoint.scala @@ -24,7 +24,10 @@ import com.typesafe.scalalogging.LazyLogging import es.weso.rdf.jena.JenaMapper.jenaNode2RDFNode // TODO: Refactor to change String type by Url -case class Endpoint(endpoint: String) extends RDFReader with RDFReasoner with LazyLogging { +case class Endpoint(endpoint: String) + extends RDFReader + with RDFReasoner + with LazyLogging { type Rdf = Endpoint def availableParseFormats: List[String] = List() @@ -95,12 +98,12 @@ case class Endpoint(endpoint: String) extends RDFReader with RDFReasoner with La model2triples(model) } - def triplesWithSubject(node: RDFNode): Set[RDFTriple] = { - logger.debug(s"Triples with subject ${node.show}") - if (node.isIRI) { - val model = QueryExecutionFactory.sparqlService(endpoint, queryTriplesWithSubject(node.toIRI)).execConstruct() + def triplesWithSubject(node: RDFNode): Set[RDFTriple] = node match { + case subj: IRI => { + val model = QueryExecutionFactory.sparqlService(endpoint, queryTriplesWithSubject(subj)).execConstruct() model2triples(model) - } else throw new Exception("triplesWithSubject: node " + node + " must be a IRI") + } + case _ => throw new Exception("triplesWithSubject: node " + node + " must be a IRI") } def triplesWithPredicate(p: IRI): Set[RDFTriple] = { @@ -108,20 +111,20 @@ case class Endpoint(endpoint: String) extends RDFReader with RDFReasoner with La model2triples(model) } - def triplesWithObject(node: RDFNode): Set[RDFTriple] = { - log.debug(s"Triples with object ${node}") - if (node.isIRI) { - val model = QueryExecutionFactory.sparqlService(endpoint, queryTriplesWithObject(node.toIRI)).execConstruct() + def triplesWithObject(node: RDFNode): Set[RDFTriple] = node match { + case obj: IRI => { + val model = QueryExecutionFactory.sparqlService(endpoint, queryTriplesWithObject(obj)).execConstruct() model2triples(model) - } else throw new Exception("triplesWithObject: node " + node + " must be a IRI") + } + case _ => throw new Exception("triplesWithObject: node " + node + " must be a IRI") } - def triplesWithPredicateObject(p: IRI, o: RDFNode): Set[RDFTriple] = { - log.debug(s"Triples with predicate ${p} and object $o") - if (o.isIRI) { - val model = QueryExecutionFactory.sparqlService(endpoint, queryTriplesWithPredicateObject(p, o.toIRI)).execConstruct() + def triplesWithPredicateObject(p: IRI, o: RDFNode): Set[RDFTriple] = o match { + case iri: IRI => { + val model = QueryExecutionFactory.sparqlService(endpoint, queryTriplesWithPredicateObject(p, iri)).execConstruct() model2triples(model) - } else throw new Exception("triplesWithPredicateObject: o " + o + " must be a IRI") + } + case _ => throw new Exception("triplesWithPredicateObject: o " + o + " must be a IRI") } def model2triples(model: Model): Set[RDFTriple] = { @@ -230,6 +233,13 @@ case class Endpoint(endpoint: String) extends RDFReader with RDFReasoner with La override def isIsomorphicWith(other: RDFReader): Either[String,Boolean] = Left(s"Unimplemented isIsomorphicWith between endpoints") + override def sourceIRI = None + + override def asRDFBuilder: Either[String,RDFBuilder] = + Left(s"Unimplemented isIsomorphicWith between endpoints") + + override def rdfReaderName: String = s"Endpoint($endpoint)" + } object Endpoint { diff --git a/modules/srdfJena/src/main/scala/es/weso/rdf/jena/RDFAsJenaModel.scala b/modules/srdfJena/src/main/scala/es/weso/rdf/jena/RDFAsJenaModel.scala index 8f7c0e84..e877b52d 100644 --- a/modules/srdfJena/src/main/scala/es/weso/rdf/jena/RDFAsJenaModel.scala +++ b/modules/srdfJena/src/main/scala/es/weso/rdf/jena/RDFAsJenaModel.scala @@ -27,7 +27,7 @@ import org.apache.jena.sparql.path.Path import cats.implicits._ import es.weso.rdf.dot.RDF2Dot -case class RDFAsJenaModel(model: Model) +case class RDFAsJenaModel(model: Model, sourceIRI: Option[IRI] = None) extends RDFReader with RDFBuilder with RDFReasoner { @@ -331,6 +331,56 @@ case class RDFAsJenaModel(model: Model) NormalizaBNodes.normalizeBNodes(this,this.empty) } + /** + * Apply owl:imports closure to an RDF source + * @return new RDFReader + */ + override def extendImports():Either[String, Rdf] = for { + imports <- getImports + newRdf <- extendImports(this,imports,List(IRI(""))) + } yield newRdf + + private lazy val owlImports = IRI("http://www.w3.org/2002/07/owl#imports") + + private def getImports: Either[String, List[IRI]] = + EitherUtils.sequence( + triplesWithPredicate(owlImports).toList.map(_.obj.toIRI) + ) + + private def extendImports(rdf: Rdf, + imports: List[IRI], + visited: List[IRI] + ): Either[String,Rdf] = { + imports match { + case Nil => Right(rdf) + case iri :: rest => + if (visited contains iri) + extendImports(rdf,rest,visited) + else for { + newRdf <- RDFAsJenaModel.fromIRI(iri) + merged <- merge(newRdf) + restRdf <- extendImports(merged,rest,iri :: visited) + } yield restRdf + } + } + + override def merge(other: RDFReader): Either[String,Rdf] = other match { + case jenaRdf: RDFAsJenaModel => + Right(RDFAsJenaModel(this.model.add(jenaRdf.model))) + case _ => { + val zero: Either[String,Rdf] = Right(this) + def cmb(next: Either[String,Rdf], x: RDFTriple): Either[String,Rdf] = for { + rdf1 <- next + rdf2 <- rdf1.addTriple(x) + } yield rdf2 + other.rdfTriples.foldLeft(zero)(cmb) + } + } + + override def asRDFBuilder: Either[String, RDFBuilder] = + Right(this) + + override def rdfReaderName: String = s"ApacheJena" } @@ -344,6 +394,15 @@ object RDFAsJenaModel { RDFAsJenaModel(ModelFactory.createDefaultModel) } + def fromIRI(iri: IRI): Either[String,RDFAsJenaModel] = { + Try { + // We delegate RDF management to Jena (content-negotiation and so...) + RDFDataMgr.loadModel(iri.str) + }.toEither. + leftMap(e => s"Exception reading RDF from ${iri.show}: ${e.getMessage}"). + map(model => RDFAsJenaModel(model)) + } + def fromURI(uri: String, format: String = "TURTLE", base: Option[String] = None @@ -352,7 +411,7 @@ object RDFAsJenaModel { Try { val m = ModelFactory.createDefaultModel() RDFDataMgr.read(m, uri, baseURI, shortnameToLang(format)) - RDFAsJenaModel(JenaUtils.relativizeModel(m)) + RDFAsJenaModel(JenaUtils.relativizeModel(m), Some(IRI(uri))) }.fold(e => Left(s"Exception accessing uri $uri: ${e.getMessage}"), (Right(_)) ) @@ -364,7 +423,7 @@ object RDFAsJenaModel { val m = ModelFactory.createDefaultModel() val is: InputStream = new FileInputStream(file) RDFDataMgr.read(m, is, baseURI, shortnameToLang(format)) - RDFAsJenaModel(JenaUtils.relativizeModel(m)) + RDFAsJenaModel(JenaUtils.relativizeModel(m), Some(IRI(file.toURI))) }.fold(e => Left(s"Exception parsing RDF from file ${file.getName}: ${e.getMessage}"), Right(_)) } @@ -384,5 +443,4 @@ object RDFAsJenaModel { RDFLanguages.getRegisteredLanguages().asScala.map(_.getName).toList.distinct } - } diff --git a/modules/srdfJena/src/main/scala/es/weso/rdf/jena/RDFFromWeb.scala b/modules/srdfJena/src/main/scala/es/weso/rdf/jena/RDFFromWeb.scala index 0c089083..10502b78 100644 --- a/modules/srdfJena/src/main/scala/es/weso/rdf/jena/RDFFromWeb.scala +++ b/modules/srdfJena/src/main/scala/es/weso/rdf/jena/RDFFromWeb.scala @@ -44,17 +44,17 @@ case class RDFFromWeb() extends RDFReader { throw new Exception("Cannot obtain triples from RDFFromWeb ") } - override def triplesWithSubject(node: RDFNode): Set[RDFTriple] = { - if (node.isIRI) { - val subj = node.toIRI + override def triplesWithSubject(node: RDFNode): Set[RDFTriple] = + node match { + case subj: IRI => { val derefModel = ModelFactory.createDefaultModel RDFDataMgr.read(derefModel, subj.str) val model = QueryExecutionFactory.create(queryTriplesWithSubject(subj), derefModel).execConstruct() val triples = model2triples(model) log.debug("triples with subject " + subj + " =\n" + triples) triples - } else - throw new Exception("triplesWithSubject: node " + node + " must be a IRI") + } + case _ => throw new Exception("triplesWithSubject: node " + node + " must be a IRI") } override def triplesWithPredicate(p: IRI): Set[RDFTriple] = { @@ -64,26 +64,27 @@ case class RDFFromWeb() extends RDFReader { model2triples(model) } - override def triplesWithObject(node: RDFNode): Set[RDFTriple] = { - if (node.isIRI) { - val obj = node.toIRI + override def triplesWithObject(node: RDFNode): Set[RDFTriple] = + node match { + case obj: IRI => { val derefModel = ModelFactory.createDefaultModel RDFDataMgr.read(derefModel, obj.str) val model = QueryExecutionFactory.create(queryTriplesWithObject(obj), derefModel).execConstruct() model2triples(model) - } else + } + case _ => throw new Exception("triplesWithObject: node " + node + " must be a IRI") } - override def triplesWithPredicateObject(p: IRI, node: RDFNode): Set[RDFTriple] = { - if (node.isIRI) { - val obj = node.toIRI + override def triplesWithPredicateObject(p: IRI, node: RDFNode): Set[RDFTriple] = + node match { + case obj: IRI => { val derefModel = ModelFactory.createDefaultModel RDFDataMgr.read(derefModel, obj.str) val model = QueryExecutionFactory.create(queryTriplesWithPredicateObject(p, obj), derefModel).execConstruct() model2triples(model) - } else - throw new Exception("triplesWithObject: node " + node + " must be a IRI") + } + case _ => throw new Exception("triplesWithObject: node " + node + " must be a IRI") } override def getSHACLInstances(c: RDFNode): Seq[RDFNode] = { @@ -154,4 +155,11 @@ case class RDFFromWeb() extends RDFReader { override def isIsomorphicWith(other: RDFReader) = Left(s"Unimplemented isomorphic test in RDFFromWeb") + override def sourceIRI = None + + override def asRDFBuilder: Either[String, RDFBuilder] = + Left(s"Cannot convert RDFFromWeb to RDFBuilder") + + override def rdfReaderName: String = s"RDFFromWeb" + } \ No newline at end of file diff --git a/modules/srdfJena/src/test/resources/application.properties b/modules/srdfJena/src/test/resources/application.properties index a0087c3a..524e0c93 100644 --- a/modules/srdfJena/src/test/resources/application.properties +++ b/modules/srdfJena/src/test/resources/application.properties @@ -1,9 +1,3 @@ # Folders for local shexTest tests -shaclFolder=src/test/resources/shacl -shaclCore=src/test/resources/shacl/core -shaclLocal=src/test/resources/shaclLocal -schemasFolder=src/test/resources/shexTest/schemas -shexLocalFolder=src/test/resources/shexLocal -negativeSyntaxFolder=src/test/resources/shexTest/negativeSyntax -validationFolder=src/test/resources/shexTest/validation/ +rdfFolder=modules/srdfJena/src/test/resources/rdf/ diff --git a/modules/srdfJena/src/test/resources/rdf/imported.ttl b/modules/srdfJena/src/test/resources/rdf/imported.ttl new file mode 100644 index 00000000..d3434e33 --- /dev/null +++ b/modules/srdfJena/src/test/resources/rdf/imported.ttl @@ -0,0 +1,6 @@ +prefix : +prefix owl: + +:x :p _:1 . +_:1 :name "Bob" ; + :source <> . \ No newline at end of file diff --git a/modules/srdfJena/src/test/resources/rdf/m1.ttl b/modules/srdfJena/src/test/resources/rdf/m1.ttl new file mode 100644 index 00000000..ec7e5c7e --- /dev/null +++ b/modules/srdfJena/src/test/resources/rdf/m1.ttl @@ -0,0 +1,5 @@ +prefix : +prefix owl: + +:x :p _:1 . +_:1 :name "Robert" . diff --git a/modules/srdfJena/src/test/resources/rdf/m2.ttl b/modules/srdfJena/src/test/resources/rdf/m2.ttl new file mode 100644 index 00000000..abe7d981 --- /dev/null +++ b/modules/srdfJena/src/test/resources/rdf/m2.ttl @@ -0,0 +1,5 @@ +prefix : +prefix owl: + +:x :p _:1 . +_:1 :name "Bob" . diff --git a/modules/srdfJena/src/test/resources/rdf/merged.ttl b/modules/srdfJena/src/test/resources/rdf/merged.ttl new file mode 100644 index 00000000..e377a05f --- /dev/null +++ b/modules/srdfJena/src/test/resources/rdf/merged.ttl @@ -0,0 +1,5 @@ +prefix : +prefix owl: + +:x :p [:name "Robert"], + [:name "Bob" ]. diff --git a/modules/srdfJena/src/test/resources/rdf/testImport.ttl b/modules/srdfJena/src/test/resources/rdf/testImport.ttl new file mode 100644 index 00000000..0c2debad --- /dev/null +++ b/modules/srdfJena/src/test/resources/rdf/testImport.ttl @@ -0,0 +1,8 @@ +prefix : +prefix owl: + +<> owl:imports . + +:x :p _:1 . +_:1 :name "Robert" ; + :source <> . \ No newline at end of file diff --git a/modules/srdfJena/src/test/resources/rdf/testImportWithLoop.ttl b/modules/srdfJena/src/test/resources/rdf/testImportWithLoop.ttl new file mode 100644 index 00000000..06e15a22 --- /dev/null +++ b/modules/srdfJena/src/test/resources/rdf/testImportWithLoop.ttl @@ -0,0 +1,8 @@ +prefix : +prefix owl: + +<> owl:imports <> . + +:x :p _:1 . +_:1 :name "Robert" ; + :source <> . \ No newline at end of file diff --git a/modules/srdfJena/src/test/scala/es/weso/rdf/jena/ImportsTest.scala b/modules/srdfJena/src/test/scala/es/weso/rdf/jena/ImportsTest.scala new file mode 100644 index 00000000..fcb85d9b --- /dev/null +++ b/modules/srdfJena/src/test/scala/es/weso/rdf/jena/ImportsTest.scala @@ -0,0 +1,88 @@ +package es.weso.rdf.jena + +import java.nio.file.Paths + +import com.typesafe.config.{Config, ConfigFactory} +import es.weso.rdf.nodes._ +import org.scalatest.{FunSpec, Matchers} + +class ImportsTest + extends FunSpec + with JenaBased + with Matchers { + + val conf: Config = ConfigFactory.load() + val rdfFolderStr = conf.getString("rdfFolder") + val rdfFolder = IRI(Paths.get(rdfFolderStr).normalize.toUri.toString) + + describe("Merge test") { + it(s"Should read merged file") { + val r = for { + rdf1 <- RDFAsJenaModel.fromIRI(rdfFolder + "/merged.ttl") + } yield rdf1 + + r.fold(e => fail(s"Error: $e"), rdf => { + info(s"RDF read: ${rdf.iris}") + rdf.iris.size should be(1) + }) + } + + it(s"Should merge files") { + val r = for { + rdf1 <- RDFAsJenaModel.fromIRI(rdfFolder + "/m1.ttl") + rdf2 <- RDFAsJenaModel.fromIRI(rdfFolder + "/m2.ttl") + merged <- rdf1.merge(rdf2) + mergedFromFile <- RDFAsJenaModel.fromIRI(rdfFolder + "/merged.ttl") + b <- merged.isIsomorphicWith(mergedFromFile) + } yield (merged,mergedFromFile,b) + + r.fold(e => fail(s"Error: $e"), values => { + val (_, _,b) = values + b should be(true) + }) + } + } + + describe("Imports test") { + it(s"Should extend with imports") { + val r = for { + rdf1 <- RDFAsJenaModel.fromIRI(rdfFolder + "/testImport.ttl") + extended <- rdf1.extendImports() + } yield (rdf1,extended) + r.fold(e => fail(s"Error: $e"), values => { + val (_,extended) = values + val x = IRI("http://example.org/x") + val p = IRI("http://example.org/p") + extended.triplesWithSubjectPredicate(x,p).size should be(2) +// info(s"Extended: ${extended.serialize("Turtle")}") + }) + } + + it(s"Should handle loops") { + val r = for { + rdf1 <- RDFAsJenaModel.fromIRI(rdfFolder + "/testImportWithLoop.ttl") + extended <- rdf1.extendImports() + } yield (rdf1,extended) + r.fold(e => fail(s"Error: $e"), values => { + val (rdf1,extended) = values + rdf1.rdfTriples.size should be(extended.rdfTriples.size) + }) + } + + ignore(s"Should handle external IRIs") { + val r = for { + rdf1 <- RDFAsJenaModel.fromChars( + """|prefix : + |<> owl:imports <...some IRI?...> + |""".stripMargin,"Turtle",None) + extended <- rdf1.extendImports() + } yield (rdf1,extended) + r.fold(e => fail(s"Error: $e"), values => { + val (rdf1,extended) = values + rdf1.rdfTriples.size should be(extended.rdfTriples.size) + }) + } + + } +} + diff --git a/modules/srdfJena/src/test/scala/es/weso/rdf/jena/RDFAsJenaModelTest.scala b/modules/srdfJena/src/test/scala/es/weso/rdf/jena/RDFAsJenaModelTest.scala index ad7e9b00..26900d70 100644 --- a/modules/srdfJena/src/test/scala/es/weso/rdf/jena/RDFAsJenaModelTest.scala +++ b/modules/srdfJena/src/test/scala/es/weso/rdf/jena/RDFAsJenaModelTest.scala @@ -30,10 +30,7 @@ class RDFAsJenaModelTest with Matchers { describe("Checking base") { - val conf: Config = ConfigFactory.load() - val shaclFolder = conf.getString("shaclCore") - val shaclFolderURI = Paths.get(shaclFolder).normalize.toUri.toString - println(s"ShaclFolder file...${shaclFolderURI}") + // println(s"ShaclFolder file...${shaclFolderURI}") it("should be able to parse RDF with relative URIs and base") { val emptyModel = ModelFactory.createDefaultModel diff --git a/notes/0.1.03.md b/notes/0.1.03.md new file mode 100644 index 00000000..0d279a36 --- /dev/null +++ b/notes/0.1.03.md @@ -0,0 +1,16 @@ +# New features + +- Added support for sh:severity, sh:message and sh:deactivated in SHACL + +TODOs +----- + +- ShEx: Complete semantic actions implementation [Issue 116](https://github.com/labra/shaclex/issues/116) + +- ShEx: test-suite with shape maps and update report [Issue 115](https://github.com/labra/shaclex/issues/115) + +- Shaclex: Conversion from ShEx to SHACL [Issue 114](https://github.com/labra/shaclex/issues/114) + +- Shaclex: Conversion from SHACL to ShEx [Issue 113](https://github.com/labra/shaclex/issues/113) + +- Shacl: Implement SHACL-Sparql [Issue 112](https://github.com/labra/shaclex/issues/112) diff --git a/version.sbt b/version.sbt index f42b2b4f..6928edf8 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.1.02" +version in ThisBuild := "0.1.03"