diff --git a/README.md b/README.md index c8ba5ed8..596ef40f 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,19 @@ It is possible to run the program inside `sbt` as: ### Validating RDF data with SHACL -Example: +Example with conformant RDF data: ``` sbt "run -d examples/shacl/good1.ttl --engine ShaClex" ``` +Example with non-conformant RDF generating SHACL validation report: + +``` +sbt "run -d examples/shacl/bad1.ttl --engine ShaClex --showValidationReport --validationReportFormat TURTLE" +``` + + ### Validating RDF with ShEx Example: diff --git a/build.sbt b/build.sbt index f12c8f23..f1a620af 100644 --- a/build.sbt +++ b/build.sbt @@ -101,7 +101,7 @@ lazy val shaclex = project .settings( unidocProjectFilter in (ScalaUnidoc, unidoc) := inAnyProject -- inProjects(noDocProjects: _*), libraryDependencies ++= Seq( - logbackClassic % Test, + logbackClassic, scalaLogging, scallop ), diff --git a/examples/shacl/tests/and-001.ttl b/examples/shacl/tests/and-001.ttl new file mode 100644 index 00000000..a5a65245 --- /dev/null +++ b/examples/shacl/tests/and-001.ttl @@ -0,0 +1,30 @@ +@prefix dash: . +@prefix ex: . +@prefix : . +@prefix mf: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix sht: . +@prefix xsd: . + +ex:InvalidRectangle1 + rdf:type ex:Rectangle ; + ex:height 3 ; +. + +:c1 sh:property :p1 . +:p1 sh:path ex:width ; + sh:minCount 1 . + +:c2 sh:property :p2 . +:p2 sh:path ex:height ; + sh:minCount 1 . + +ex:Rectangle + rdf:type rdfs:Class ; + rdf:type sh:NodeShape ; + rdfs:subClassOf rdfs:Resource ; + sh:and ( :c1 :c2 ) ; +. diff --git a/modules/manifest/src/main/scala/es/weso/manifest/RDF2Manifest.scala b/modules/manifest/src/main/scala/es/weso/manifest/RDF2Manifest.scala index 66923752..9fc853e5 100644 --- a/modules/manifest/src/main/scala/es/weso/manifest/RDF2Manifest.scala +++ b/modules/manifest/src/main/scala/es/weso/manifest/RDF2Manifest.scala @@ -8,7 +8,6 @@ import ManifestPrefixes._ import es.weso.rdf.parser.RDFParser import es.weso.rdf.jena.RDFAsJenaModel import es.weso.utils.FileUtils._ -import cats._ import cats.implicits._ import com.typesafe.scalalogging.LazyLogging diff --git a/modules/schema/src/main/scala/es/weso/schema/Explanation.toDelete b/modules/schema/src/main/scala/es/weso/schema/Explanation.toDelete deleted file mode 100644 index 1ea688d0..00000000 --- a/modules/schema/src/main/scala/es/weso/schema/Explanation.toDelete +++ /dev/null @@ -1,5 +0,0 @@ -package es.weso.schema - -case class Explanation(str: String) { - -} diff --git a/modules/schema/src/main/scala/es/weso/schema/InfoNode.toDelete b/modules/schema/src/main/scala/es/weso/schema/InfoNode.toDelete deleted file mode 100644 index c84845fe..00000000 --- a/modules/schema/src/main/scala/es/weso/schema/InfoNode.toDelete +++ /dev/null @@ -1,120 +0,0 @@ -package es.weso.schema -import Explanation._ -import es.weso.rdf.PrefixMap -import cats._ -import data._ -import es.weso.rdf.nodes.{ IRI, RDFNode } -import implicits._ -import es.weso.shex.implicits.showShEx -import io.circe._ -import io.circe.JsonObject._ -import io.circe.syntax._ -import cats.syntax.either._ -import es.weso.json.DecoderUtils._ - -case class InfoNode( - hasShapes: Seq[(SchemaLabel, Explanation)], - hasNoShapes: Seq[(SchemaLabel, Explanation)], - pm: PrefixMap) { - - def contains(label: SchemaLabel): Boolean = { - hasShapes.map(_._1).contains(label) - } - - override def toString: String = show - - def show: String = { - val sb = new StringBuilder - for ((s, e) <- hasShapes) { - sb ++= ("+" + s.show + " " + e.str) - } - for ((s, e) <- hasNoShapes) { - sb ++= ("-" + s.show + " " + e.str) - } - sb.toString - } - - def conditionalAdd(cond: Boolean, obj: JsonObject, key: String, value: Json): JsonObject = { - if (cond) obj.add(key, value) - else obj - } - - def toJson: Json = { - val jsonPositive: Json = Json.fromJsonObject( - JsonObject.fromFoldable(hasShapes.toList.map { case (label, e) => (label.show, Json.fromString(e.str)) })) - val jsonNegative: Json = Json.fromJsonObject( - JsonObject.fromFoldable(hasNoShapes.toList.map { case (label, e) => (label.show, Json.fromString(e.str)) })) - Json.fromJsonObject { - val obj = JsonObject.empty - val pos = conditionalAdd(!hasShapes.isEmpty, obj, "hasShapes", jsonPositive) - val neg = conditionalAdd(!hasNoShapes.isEmpty, pos, "hasNoShapes", jsonNegative) - neg - } - } -} - -object InfoNode { - - implicit val showInfoNode = new Show[InfoNode] { - override def show(n: InfoNode): String = { - n.show - } - } - - implicit val encodeInfoNode: Encoder[InfoNode] = new Encoder[InfoNode] { - final def apply(i: InfoNode): Json = i.toJson - } - - implicit val decodeInfoNode: Decoder[InfoNode] = new Decoder[InfoNode] { - final def apply(c: HCursor): Decoder.Result[InfoNode] = for { - hasShapes <- getPair(c.downField("hasShapes")) - hasNoShapes <- getPair(c.downField("hasNoShapes")) - // solutionMap <- decodeMap(c.downField("solution")) - } yield InfoNode( - hasShapes = hasShapes, - hasNoShapes = hasNoShapes, - PrefixMap.empty) - } - - def getPair(c: ACursor): Decoder.Result[Seq[(SchemaLabel, Explanation)]] = - if (c.keys.isDefined) { - val fields: Either[DecodingFailure, Vector[String]] = c.keys.map(_.toVector).toRight(DecodingFailure(s"getPair: no fields for $c", c.history)) - for { - fs <- fields - rs <- fs.map(field => getSchemaLabelExplanation(field, c)).sequence - } yield rs.toSeq - } else { - val result: Either[DecodingFailure, Seq[(SchemaLabel, Explanation)]] = Right(Seq()) - result - } - - def getSchemaLabelExplanation(field: String, c: ACursor): Decoder.Result[(SchemaLabel, Explanation)] = - throw new Exception("Unimplemented getSchemaLabelExplanation...") - /*{ - for { - e <- c.downField(field).as[String] - } yield (SchemaLabel(field,pm), Explanation(e)) - } */ - - implicit val rdfNodeKeyDecoder = new KeyDecoder[RDFNode] { - override def apply(key: String): Option[RDFNode] = Some(IRI(key)) - } - - /* def toHTML(pm: PrefixMap): String = { - val sb = new StringBuilder - sb ++= "
    " - for ((s,e) <- hasShapes) { - sb ++= ("
  • " + - "" + e.toHTML(pm) + "
  • ") - } - sb.append("
") - sb.append("
    ") - for ((s,e) <- hasNoShapes) { - sb ++= ("
  • " + - "" + e.toHTML(pm) + "
  • ") - } - sb.append("
") - sb.toString - } */ - -} diff --git a/modules/schema/src/main/scala/es/weso/schema/Result.scala b/modules/schema/src/main/scala/es/weso/schema/Result.scala index ddf1b9a9..2e381bb3 100644 --- a/modules/schema/src/main/scala/es/weso/schema/Result.scala +++ b/modules/schema/src/main/scala/es/weso/schema/Result.scala @@ -1,14 +1,12 @@ package es.weso.schema + import cats.Show import com.typesafe.scalalogging.LazyLogging -import es.weso.rdf.PrefixMap -import es.weso.rdf.nodes.{ IRI, RDFNode } -import io.circe.JsonObject._ +import es.weso.rdf.{PrefixMap, RDFReader} +import es.weso.rdf.nodes.{IRI, RDFNode} import io.circe._ import io.circe.generic.auto._ import io.circe.syntax._ -import cats.syntax.either._ -import cats.instances.either._ import es.weso.shapeMaps._ import es.weso.utils.MapUtils._ @@ -16,6 +14,7 @@ case class Result( isValid: Boolean, message: String, shapeMaps: Seq[ResultShapeMap], + validationReport: Either[String,RDFReader], errors: Seq[ErrorInfo], trigger: Option[ValidationTrigger], nodesPrefixMap: PrefixMap, @@ -145,6 +144,7 @@ object Result extends LazyLogging { isValid = true, message = "", shapeMaps = Seq(), + validationReport = Left("No report"), errors = Seq(), None, PrefixMap.empty, @@ -195,17 +195,18 @@ object Result extends LazyLogging { solutions <- if (isValid) { for { ls <- c.downField("shapeMap").as[List[ResultShapeMap]] - } yield ls.toSeq + } yield ls } else Right(Seq()) errors <- if (isValid) { Right(Seq()) } else for { ls <- c.downField("shapeMap").as[List[ErrorInfo]] - } yield ls.toSeq + } yield ls } yield Result( isValid, message, solutions, + Left("Not implemented ValidationReport Json decoder yet"), errors, trigger, nodesPrefixMap, diff --git a/modules/schema/src/main/scala/es/weso/schema/Schemas.scala b/modules/schema/src/main/scala/es/weso/schema/Schemas.scala index 122de419..e59ff2db 100644 --- a/modules/schema/src/main/scala/es/weso/schema/Schemas.scala +++ b/modules/schema/src/main/scala/es/weso/schema/Schemas.scala @@ -5,9 +5,6 @@ import util._ import es.weso.rdf.RDFReader import es.weso.utils.FileUtils -import scala.io._ -import scala.util.{ Failure, Success, Try } - object Schemas { type SchemaParser = (CharSequence, String, Option[String]) => Either[String, Schema] @@ -27,6 +24,7 @@ object Schemas { availableSchemas.map(_.formats).flatten.distinct } + val availableTriggerModes: List[String] = { ValidationTrigger.triggerValues.map(_._1) } diff --git a/modules/schema/src/main/scala/es/weso/schema/ShExSchema.scala b/modules/schema/src/main/scala/es/weso/schema/ShExSchema.scala index cef45cfe..7a0c8bd9 100644 --- a/modules/schema/src/main/scala/es/weso/schema/ShExSchema.scala +++ b/modules/schema/src/main/scala/es/weso/schema/ShExSchema.scala @@ -1,7 +1,7 @@ package es.weso.schema + import cats._ import com.typesafe.scalalogging.LazyLogging -import data._ import implicits._ import es.weso.rdf._ import es.weso.rdf.nodes._ @@ -10,11 +10,8 @@ import es.weso.shapeMaps._ import es.weso.shex.{ Schema => Schema_, _ } import es.weso.shex.validator._ import es.weso.shex._ -import es.weso.typing._ import es.weso.shex.shexR._ - import scala.util._ -import es.weso.shex.implicits.showShEx.showShapeLabel case class ShExSchema(schema: Schema_) extends Schema with LazyLogging { override def name = "ShEx" @@ -52,10 +49,12 @@ case class ShExSchema(schema: Schema_) extends Schema with LazyLogging { false, message = "Error validating", shapeMaps = Seq(), + validationReport = Left("No validation report in ShEx"), errors = Seq(ErrorInfo(msg)), None, rdf.getPrefixMap(), schema.prefixMap) case Right(resultShapeMap) => Result(true, "Validated", shapeMaps = Seq(resultShapeMap), + validationReport = Left(s"No validaton report in ShEx"), errors = Seq(), None, rdf.getPrefixMap(), schema.prefixMap) } @@ -80,10 +79,24 @@ case class ShExSchema(schema: Schema_) extends Schema with LazyLogging { rdf: RDFReader): Result = { Validator.validate(schema, shapeMap, rdf) match { case Left(error) => - Result(false, "Error validating", Seq(), Seq(ErrorInfo(error)), None, rdf.getPrefixMap(), schema.prefixMap) + Result(false, + "Error validating", + Seq(), + Left("No validation report yet"), + Seq(ErrorInfo(error)), + None, + rdf.getPrefixMap(), + schema.prefixMap) case Right(resultShapeMap) => { // println(s"Validated, result=$resultShapeMap") - Result(true, "Validated", Seq(resultShapeMap), Seq(), None, rdf.getPrefixMap(), schema.prefixMap) + Result(true, + "Validated", + Seq(resultShapeMap), + Left(s"No validation report for ShEx"), + Seq(), + None, + rdf.getPrefixMap(), + schema.prefixMap) } } } @@ -148,8 +161,9 @@ case class ShExSchema(schema: Schema_) extends Schema with LazyLogging { RDF2ShEx.rdf2Schema(rdf).map(ShExSchema(_)) override def serialize(format: String): Either[String, String] = { + val builder: RDFBuilder = RDFAsJenaModel.empty if (formatsUpperCase.contains(format.toUpperCase())) - Schema_.serialize(schema, format) + Schema_.serialize(schema, format, builder) else Left(s"Can't serialize to format $format. Supported formats=$formats") } 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 04d17750..1219c157 100644 --- a/modules/schema/src/main/scala/es/weso/schema/ShaclexSchema.scala +++ b/modules/schema/src/main/scala/es/weso/schema/ShaclexSchema.scala @@ -1,15 +1,13 @@ package es.weso.schema -import cats._ -import cats.data._ import cats.implicits._ import es.weso.rdf._ import es.weso.rdf.nodes._ import es.weso.rdf.jena.RDFAsJenaModel -import es.weso.shacl.{Schema => ShaclSchema, Shape => ShaclNodeShape, _} -import es.weso.shacl.validator.Validator._ +import es.weso.shacl.{Schema => ShaclSchema, _} import es.weso.shacl._ import es.weso.shacl.converter.RDF2Shacl -import es.weso.shacl.validator.{CheckResult, Evidence, Validator, ViolationError} +import es.weso.shacl.report.{ValidationReport, ValidationResult} +import es.weso.shacl.validator.{CheckResult, Evidence, ShapeTyping, Validator} import es.weso.shapeMaps._ import util._ @@ -31,27 +29,37 @@ case class ShaclexSchema(schema: ShaclSchema) extends Schema { def validateTargetDecls(rdf: RDFReader): Result = { val validator = Validator(schema) val r = validator.validateAll(rdf) - cnvResult(r, rdf) + val builder = RDFAsJenaModel.empty + builder.addPrefixMap(schema.pm) + cnvResult(r, rdf, builder) } def cnvResult( - r: CheckResult[ViolationError, ShapeTyping, List[Evidence]], - rdf: RDFReader): Result = + r: CheckResult[ValidationResult, ShapeTyping, List[Evidence]], + rdf: RDFReader, + builder: RDFBuilder + ): Result = { + val vr: ValidationReport = + r.result.fold(e => ValidationReport.fromError(e), + _.toValidationReport + ) Result( isValid = r.isOK, message = if (r.isOK) "Valid" else "Not valid", shapeMaps = r.results.map(cnvShapeTyping(_, rdf)), + validationReport = vr.toRDF(builder), errors = r.errors.map(cnvViolationError(_)), trigger = None, nodesPrefixMap = rdf.getPrefixMap(), shapesPrefixMap = schema.pm) + } def cnvShapeTyping(t: ShapeTyping, rdf: RDFReader): ResultShapeMap = { ResultShapeMap( t.getMap.mapValues(cnvMapShapeResult), rdf.getPrefixMap(), schema.pm) } - private def cnvMapShapeResult(m: Map[Shape, TypingResult[ViolationError, String]]): Map[ShapeMapLabel, Info] = { + private def cnvMapShapeResult(m: Map[Shape, TypingResult[ValidationResult, String]]): Map[ShapeMapLabel, Info] = { MapUtils.cnvMap(m, cnvShape, cnvTypingResult) } @@ -64,24 +72,22 @@ case class ShaclexSchema(schema: ShaclSchema) extends Schema { } } - private def cnvTypingResult(t: TypingResult[ViolationError, String]): Info = { + private def cnvTypingResult(t: TypingResult[ValidationResult, String]): Info = { import showShacl._ import TypingResult.showTypingResult - val showVE = implicitly[Show[ViolationError]] - val x = implicitly[Show[TypingResult[ViolationError, String]]] Info( status = if (t.isOK) Conformant else NonConformant, - reason = Some(x.show(t)) + reason = Some(t.show) // TODO: Convert typing result to JSON and add it to appInfo ) } - private def cnvViolationError(v: ViolationError): ErrorInfo = { + private def cnvViolationError(v: ValidationResult): ErrorInfo = { val pm = schema.pm ErrorInfo( - pm.qualify(v.id) + + pm.qualify(v.sourceConstraintComponent) + " FocusNode: " + schema.pm.qualify(v.focusNode) + " " + - v.message.getOrElse("")) + v.message.mkString(",")) } /*def validateShapeMap(sm: Map[RDFNode,Set[String]], nodesStart: Set[RDFNode], rdf: RDFReader) : Result = { @@ -100,8 +106,9 @@ case class ShaclexSchema(schema: ShaclSchema) extends Schema { } yield ShaclexSchema(schemaShacl) override def serialize(format: String): Either[String, String] = { + val builder: RDFBuilder = RDFAsJenaModel.empty if (formats.contains(format)) - schema.serialize(format) + schema.serialize(format, builder) else Left(s"Format $format not supported to serialize $name. Supported formats=$formats") } diff --git a/modules/schema/src/main/scala/es/weso/schema/ValidationTrigger.scala b/modules/schema/src/main/scala/es/weso/schema/ValidationTrigger.scala index 66ff476d..13592c91 100644 --- a/modules/schema/src/main/scala/es/weso/schema/ValidationTrigger.scala +++ b/modules/schema/src/main/scala/es/weso/schema/ValidationTrigger.scala @@ -1,13 +1,10 @@ package es.weso.schema import es.weso.rdf.PrefixMap import es.weso.rdf.nodes._ -import cats._ -import cats.data._ import cats.implicits._ import com.typesafe.scalalogging.LazyLogging import es.weso.shapeMaps._ import io.circe._ -import io.circe.syntax._ import io.circe.JsonObject._ import util._ diff --git a/modules/shacl/src/main/scala/es/weso/shacl/Component.scala b/modules/shacl/src/main/scala/es/weso/shacl/Component.scala new file mode 100644 index 00000000..26498779 --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/Component.scala @@ -0,0 +1,40 @@ +package es.weso.shacl + +import es.weso.rdf.nodes.{IRI, Literal, RDFNode} + +sealed abstract class Component + +case class ClassComponent(value: RDFNode) extends Component +case class Datatype(value: IRI) extends Component +case class NodeKind(value: NodeKindType) extends Component +case class MinCount(value: Int) extends Component +case class MaxCount(value: Int) extends Component +case class MinExclusive(value: Literal) extends Component +case class MinInclusive(value: Literal) extends Component +case class MaxExclusive(value: Literal) extends Component +case class MaxInclusive(value: Literal) extends Component +case class MinLength(value: Int) extends Component +case class MaxLength(value: Int) extends Component +case class Pattern(pattern: String, flags: Option[String]) extends Component +case class UniqueLang(value: Boolean) extends Component +case class LanguageIn(langs: List[String]) extends Component +case class Equals(p: IRI) extends Component +case class Disjoint(p: IRI) extends Component +case class LessThan(p: IRI) extends Component +case class LessThanOrEquals(p: IRI) extends Component +case class Or(shapes: List[ShapeRef]) extends Component +case class And(shapes: List[ShapeRef]) extends Component +case class Not(shape: ShapeRef) extends Component +case class Xone(shapes: List[ShapeRef]) extends Component +case class Closed(isClosed: Boolean, ignoredProperties: List[IRI]) extends Component +case class NodeComponent(shape: ShapeRef) extends Component +case class HasValue(value: Value) extends Component +case class In(list: List[Value]) extends Component + +// TODO: Change representation to include optional parent shape +case class QualifiedValueShape( + shape: ShapeRef, + qualifiedMinCount: Option[Int], + qualifiedMaxCount: Option[Int], + qualifiedValueShapesDisjoint: Option[Boolean]) extends Component + diff --git a/modules/shacl/src/main/scala/es/weso/shacl/NodeKindType.scala b/modules/shacl/src/main/scala/es/weso/shacl/NodeKindType.scala new file mode 100644 index 00000000..127f530b --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/NodeKindType.scala @@ -0,0 +1,26 @@ +package es.weso.shacl + +import es.weso.rdf.nodes.IRI +import es.weso.shacl.SHACLPrefixes._ + +sealed trait NodeKindType { + def id: IRI +} +case object IRIKind extends NodeKindType { + override def id = sh_IRI +} +case object LiteralKind extends NodeKindType { + override def id = sh_Literal +} +case object BlankNodeKind extends NodeKindType { + override def id = sh_BlankNode +} +case object BlankNodeOrIRI extends NodeKindType { + override def id = sh_BlankNodeOrIRI +} +case object BlankNodeOrLiteral extends NodeKindType { + override def id = sh_BlankNodeOrLiteral +} +case object IRIOrLiteral extends NodeKindType { + override def id = sh_IRIOrLiteral +} 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 f53a56b5..899269c7 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/SHACLPrefixes.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/SHACLPrefixes.scala @@ -7,33 +7,42 @@ import es.weso.rdf._ object SHACLPrefixes { - def add(iri: IRI, str: String): IRI = { - iri + str - } - lazy val sh = IRI("http://www.w3.org/ns/shacl#") - lazy val sh_IRI: IRI = sh + "IRI" - lazy val sh_Literal: IRI = sh + "Literal" lazy val sh_BlankNode: IRI = sh + "BlankNode" - lazy val sh_IRIOrLiteral: IRI = sh + "IRIOrLiteral" lazy val sh_BlankNodeOrIRI: IRI = sh + "BlankNodeOrIRI" lazy val sh_BlankNodeOrLiteral: IRI = sh + "BlankNodeOrLiteral" - - lazy val sh_Shape: IRI = sh + "Shape" + lazy val sh_Info: IRI = sh + "Info" + lazy val sh_IRI: IRI = sh + "IRI" + lazy val sh_IRIOrLiteral: IRI = sh + "IRIOrLiteral" + lazy val sh_Literal: IRI = sh + "Literal" lazy val sh_NodeShape: IRI = sh + "NodeShape" lazy val sh_PropertyShape: IRI = sh + "PropertyShape" - lazy val sh_property: IRI = sh + "property" - lazy val sh_path: IRI = sh + "path" - lazy val sh_inversePath: IRI = sh + "inversePath" - lazy val sh_alternativePath: IRI = sh + "alternativePath" - lazy val sh_zeroOrMorePath: IRI = sh + "zeroOrMorePath" - lazy val sh_zeroOrOnePath: IRI = sh + "zeroOrOnePath" - lazy val sh_oneOrMorePath: IRI = sh + "oneOrMorePath" + lazy val sh_Shape: IRI = sh + "Shape" + lazy val sh_Schema: IRI = sh + "Schema" + lazy val sh_ValidationReport: IRI = sh + "ValidationReport" + lazy val sh_ValidationResult: IRI = sh + "ValidationResult" + lazy val sh_Violation: IRI = sh + "Violation" + lazy val sh_Warning: IRI = sh + "Warning" + lazy val sh_and: IRI = sh + "and" + lazy val sh_alternativePath: IRI = sh + "alternativePath" lazy val sh_class: IRI = sh + "class" + lazy val sh_closed: IRI = sh + "closed" + lazy val sh_conforms: IRI = sh + "conforms" lazy val sh_datatype: IRI = sh + "datatype" - lazy val sh_nodeKind: IRI = sh + "nodeKind" + lazy val sh_description: IRI = sh + "description" + lazy val sh_disjoint: IRI = sh + "disjoint" + lazy val sh_equals: IRI = sh + "equals" + lazy val sh_flags: IRI = sh + "flags" + lazy val sh_focusNode: IRI = sh + "focusNode" + lazy val sh_hasValue: IRI = sh + "hasValue" + lazy val sh_ignoredProperties: IRI = sh + "ignoredProperties" + lazy val sh_in: IRI = sh + "in" + lazy val sh_inversePath: IRI = sh + "inversePath" + lazy val sh_languageIn: IRI = sh + "languageIn" + lazy val sh_lessThan: IRI = sh + "lessThan" + lazy val sh_lessThanOrEquals: IRI = sh + "lessThanOrEquals" lazy val sh_minCount: IRI = sh + "minCount" lazy val sh_maxCount: IRI = sh + "maxCount" lazy val sh_minInclusive: IRI = sh + "minInclusive" @@ -42,38 +51,33 @@ object SHACLPrefixes { lazy val sh_maxExclusive: IRI = sh + "maxExclusive" lazy val sh_minLength: IRI = sh + "minLength" lazy val sh_maxLength: IRI = sh + "maxLength" - lazy val sh_pattern: IRI = sh + "pattern" - lazy val sh_flags: IRI = sh + "flags" - lazy val sh_languageIn: IRI = sh + "languageIn" - lazy val sh_uniqueLang: IRI = sh + "uniqueLang" - lazy val sh_equals: IRI = sh + "equals" - lazy val sh_disjoint: IRI = sh + "disjoint" - lazy val sh_lessThan: IRI = sh + "lessThan" - lazy val sh_lessThanOrEquals: IRI = sh + "lessThanOrEquals" + lazy val sh_message: IRI = sh + "message" + lazy val sh_nodeKind: IRI = sh + "nodeKind" + lazy val sh_name: IRI = sh + "name" + lazy val sh_node: IRI = sh + "node" lazy val sh_not: IRI = sh + "not" + lazy val sh_oneOrMorePath: IRI = sh + "oneOrMorePath" lazy val sh_or: IRI = sh + "or" - lazy val sh_and: IRI = sh + "and" - lazy val sh_xone: IRI = sh + "xone" - lazy val sh_node: IRI = sh + "node" - lazy val sh_qualifiedValueShape: IRI = sh + "qualifiedValueShape" - lazy val sh_qualifiedValueShapesDisjoint: IRI = sh + "qualifiedValueShapesDisjoint" - + lazy val sh_path: IRI = sh + "path" + lazy val sh_pattern: IRI = sh + "pattern" + lazy val sh_property: IRI = sh + "property" lazy val sh_qualifiedMinCount: IRI = sh + "qualifiedMinCount" lazy val sh_qualifiedMaxCount: IRI = sh + "qualifiedMaxCount" - lazy val sh_closed: IRI = sh + "closed" - lazy val sh_ignoredProperties: IRI = sh + "ignoredProperties" - lazy val sh_hasValue: IRI = sh + "hasValue" - lazy val sh_in: IRI = sh + "in" - lazy val sh_name: IRI = sh + "name" - lazy val sh_description: IRI = sh + "description" - - lazy val sh_Schema: IRI = sh + "Schema" + lazy val sh_qualifiedValueShape: IRI = sh + "qualifiedValueShape" + lazy val sh_qualifiedValueShapesDisjoint: IRI = sh + "qualifiedValueShapesDisjoint" + lazy val sh_result: IRI = sh + "result" + lazy val sh_resultPath: IRI = sh + "resultPath" + lazy val sh_resultSeverity: IRI = sh + "resultSeverity" + lazy val sh_sourceConstraintComponent: IRI = sh + "sourceConstraintComponent" lazy val sh_targetNode: IRI = sh + "targetNode" lazy val sh_targetClass: IRI = sh + "targetClass" lazy val sh_targetSubjectsOf: IRI = sh + "targetSubjectsOf" lazy val sh_targetObjectsOf: IRI = sh + "targetObjectsOf" - lazy val sh_text: IRI = sh + "text" + lazy val sh_uniqueLang: IRI = sh + "uniqueLang" + lazy val sh_xone: IRI = sh + "xone" + lazy val sh_zeroOrMorePath: IRI = sh + "zeroOrMorePath" + lazy val sh_zeroOrOnePath: IRI = sh + "zeroOrOnePath" lazy val defaultPrefixMap = PrefixMap( Map( diff --git a/modules/shacl/src/main/scala/es/weso/shacl/Schema.scala b/modules/shacl/src/main/scala/es/weso/shacl/Schema.scala new file mode 100644 index 00000000..71db08ab --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/Schema.scala @@ -0,0 +1,112 @@ +package es.weso.shacl + +import es.weso.rdf.{PrefixMap, RDFBuilder} +import es.weso.rdf.nodes.{IRI, RDFNode} +import es.weso.shacl.converter.Shacl2RDF + +import scala.util.{Either, Left, Right} +import sext._ + +case class Schema( + pm: PrefixMap, + shapesMap: Map[ShapeRef, Shape]) { + + lazy val shapes: Seq[Shape] = + shapesMap.toSeq.map(_._2) + + lazy val shapeRefs: Seq[ShapeRef] = + shapesMap.keys.toSeq + + /** + * Get the shape associated to an IRI + * @param node IRI that identifies a shape + */ + def shape(node: RDFNode): Either[String, Shape] = + shapesMap.get(ShapeRef(node)) match { + case None => Left(s"Not found $node in Schema") + case Some(shape) => Right(shape) + } + + private[shacl] def siblingQualifiedShapes(s: ShapeRef): List[ShapeRef] = { + val parentShapes: List[Shape] = + parents(s). + map(shapesMap.get(_)). + collect { case Some(shape) => shape } + val qualifiedPropertyShapes = + parentShapes. + flatMap(_.propertyShapes). + filter(_ != s) + collectQualifiedValueShapes(qualifiedPropertyShapes) + } + + private def collectQualifiedValueShapes(ls: List[ShapeRef]): List[ShapeRef] = { + val zero: List[ShapeRef] = List() + def comb(xs: List[ShapeRef], x: ShapeRef): List[ShapeRef] = + qualifiedShapes(x) ++ xs + ls.foldLeft(zero)(comb) + } + + private def qualifiedShapes(p: ShapeRef): List[ShapeRef] = shapesMap.get(p) match { + case None => List() + case Some(shape) => + shape.components.collect { case x: QualifiedValueShape => x.shape }.toList + } + + /* Find shape x such that x sh:property p + */ + private[shacl] def parents(p: ShapeRef): List[ShapeRef] = { + shapesWithPropertyShape(this.shapeRefs, p) + } + + private def shapesWithPropertyShape(ls: Seq[ShapeRef], p: ShapeRef): List[ShapeRef] = { + ls.filter(hasPropertyShape(_, p)).toList + } + + private def hasPropertyShape(s: ShapeRef, p: ShapeRef): Boolean = { + shapesMap.get(s) match { + case None => false // TODO: Maybe raise an error + case Some(shape) => + if (shape.propertyShapes.contains(p)) true + else false + } + } + + /** + * Get the sequence of sh:targetNode declarations + */ + def targetNodeShapes: Seq[(RDFNode, Shape)] = { + val zero: Seq[(RDFNode, Shape)] = Seq() + def comb(rs: Seq[(RDFNode, Shape)], s: Shape): Seq[(RDFNode, Shape)] = { + val ns: Seq[RDFNode] = s.targetNodes + ns.map(n => (n, s)) ++ rs + } + shapes.foldLeft(zero)(comb) + } + + /** + * Get the sequence of `sh:targetNode` declarations + * @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 serialize(format: String = "TURTLE", builder: RDFBuilder): Either[String, String] = { + format.toUpperCase match { + case "TREE" => { + Right(s"PrefixMap ${pm.treeString}\nShapes: ${shapes.treeString}") + } + case _ => + new Shacl2RDF {}.serialize(this, format, builder) + } + } +} + +// Companion iriObjects +object Schema { + val empty = + Schema( + pm = SHACLPrefixes.defaultPrefixMap, + shapesMap = Map[ShapeRef, Shape]()) +} diff --git a/modules/shacl/src/main/scala/es/weso/shacl/Shacl.scala b/modules/shacl/src/main/scala/es/weso/shacl/Shacl.scala index 5455f356..25e23c97 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/Shacl.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/Shacl.scala @@ -1,334 +1,8 @@ package es.weso.shacl -import es.weso.rdf.nodes._ - -import util._ -import SHACLPrefixes._ -import es.weso.rdf.PrefixMap -import es.weso.rdf.path.SHACLPath -import es.weso.shacl.converter.Shacl2RDF -import sext._ - - object Shacl { case object Unbounded lazy val defaultMin = 0 lazy val defaultMax = Unbounded lazy val defaultFormat = "TURTLE" } - -case class Schema( - pm: PrefixMap, - shapesMap: Map[ShapeRef, Shape]) { - - lazy val shapes: Seq[Shape] = - shapesMap.toSeq.map(_._2) - - lazy val shapeRefs: Seq[ShapeRef] = - shapesMap.keys.toSeq - - /** - * Get the shape associated to an IRI - * @param iri IRI that identifies a shape - */ - def shape(node: RDFNode): Either[String, Shape] = - shapesMap.get(ShapeRef(node)) match { - case None => Left(s"Not found $node in Schema") - case Some(shape) => Right(shape) - } - - def siblingQualifiedShapes(s: ShapeRef): List[ShapeRef] = { - val parentShapes: List[Shape] = - parents(s). - map(shapesMap.get(_)). - collect { case Some(shape) => shape } - val qualifiedPropertyShapes = - parentShapes. - flatMap(_.propertyShapes). - filter(_ != s) - collectQualifiedValueShapes(qualifiedPropertyShapes) - } - - def collectQualifiedValueShapes(ls: List[ShapeRef]): List[ShapeRef] = { - val zero: List[ShapeRef] = List() - def comb(xs: List[ShapeRef], x: ShapeRef): List[ShapeRef] = - qualifiedShapes(x) ++ xs - ls.foldLeft(zero)(comb) - } - - def qualifiedShapes(p: ShapeRef): List[ShapeRef] = shapesMap.get(p) match { - case None => List() - case Some(shape) => - shape.components.collect { case x: QualifiedValueShape => x.shape }.toList - } - - /* Find shape x such that x sh:property p - */ - def parents(p: ShapeRef): List[ShapeRef] = { - shapesWithPropertyShape(this.shapeRefs, p) - } - - private def shapesWithPropertyShape(ls: Seq[ShapeRef], p: ShapeRef): List[ShapeRef] = { - ls.filter(hasPropertyShape(_, p)).toList - } - - private def hasPropertyShape(s: ShapeRef, p: ShapeRef): Boolean = { - shapesMap.get(s) match { - case None => false // TODO: Maybe raise an error - case Some(shape) => - if (shape.propertyShapes.contains(p)) true - else false - } - } - - /** - * Get the sequence of sh:targetNode declarations - */ - def targetNodeShapes: Seq[(RDFNode, Shape)] = { - val zero: Seq[(RDFNode, Shape)] = Seq() - def comb(rs: Seq[(RDFNode, Shape)], s: Shape): Seq[(RDFNode, Shape)] = { - val ns: Seq[RDFNode] = s.targetNodes - ns.map(n => (n, s)) ++ rs - } - shapes.foldLeft(zero)(comb) - } - - /** - * Get the sequence of `sh:targetNode` declarations - * @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 serialize(format: String = "TURTLE"): Either[String, String] = { - format.toUpperCase match { - case "TREE" => { - Right(s"PrefixMap ${pm.treeString}\nShapes: ${shapes.treeString}") - } - case _ => - new Shacl2RDF {}.serialize(this, format) - } - } -} - -sealed abstract class Shape { - def id: RDFNode - def targets: Seq[Target] - def components: Seq[Component] - def propertyShapes: Seq[ShapeRef] - def closed: Boolean - def ignoredProperties: List[IRI] - - def hasId(iri: IRI): Boolean = { - id == iri - } - - def showId: String = - id match { - case iri: IRI => iri.str - case bnode: BNode => bnode.toString - } - - def targetNodes: Seq[RDFNode] = - targets.map(_.toTargetNode).flatten.map(_.node) - - def targetClasses: Seq[RDFNode] = - targets.map(_.toTargetClass).flatten.map(_.node) - - def targetSubjectsOf: Seq[IRI] = - targets.map(_.toTargetSubjectsOf).flatten.map(_.pred) - - def targetObjectsOf: Seq[IRI] = - targets.map(_.toTargetObjectsOf).flatten.map(_.pred) - -} - -sealed abstract class Target { - def toTargetNode: Option[TargetNode] = this match { - case tn: TargetNode => Some(tn) - case _ => None - } - def toTargetClass: Option[TargetClass] = this match { - case tc: TargetClass => Some(tc) - case _ => None - } - def toTargetSubjectsOf: Option[TargetSubjectsOf] = this match { - case t: TargetSubjectsOf => Some(t) - case _ => None - } - def toTargetObjectsOf: Option[TargetObjectsOf] = this match { - case t: TargetObjectsOf => Some(t) - case _ => None - } -} -case class TargetNode(node: RDFNode) extends Target -case class TargetClass(node: RDFNode) extends Target -case class TargetSubjectsOf(pred: IRI) extends Target -case class TargetObjectsOf(pred: IRI) extends Target - -/** - * Captures the common parts of NodeShapes and PropertyShapes - */ -/*sealed abstract class Constraint { - def isPropertyConstraint: Boolean - def toPropertyConstraint: Option[PropertyShape] = None - def components: Seq[Component] -} */ - -case class NodeShape( - id: RDFNode, - components: List[Component], - targets: Seq[Target], - propertyShapes: Seq[ShapeRef], - closed: Boolean, - ignoredProperties: List[IRI]) extends Shape { - - def isPropertyConstraint = false -} - -case class PropertyShape( - id: RDFNode, - path: SHACLPath, - components: Seq[Component], - targets: Seq[Target], - propertyShapes: Seq[ShapeRef], - closed: Boolean, - ignoredProperties: List[IRI]) extends Shape { - - def isPropertyConstraint = true - - def predicate: IRI = path.predicate.get - -} - -sealed abstract class Component - -/** - * Represents IRIs or Literals (no Blank nodes) - */ -trait Value { - /** - * `true` if `node` matches this value - */ - def matchNode(node: RDFNode): Boolean - - /** - * Conversion from values to RDFNode's - */ - def rdfNode: RDFNode -} - -case class IRIValue(iri: IRI) extends Value { - override def matchNode(node: RDFNode) = { - node match { - case i: IRI => iri == i - case _ => false - } - } - - override def rdfNode: RDFNode = iri -} - -case class LiteralValue(literal: Literal) extends Value { - override def matchNode(node: RDFNode) = { - node match { - case l: Literal => l == literal - case _ => false - } - } - - override def rdfNode: RDFNode = literal -} - -case class ShapeRef(id: RDFNode) extends AnyVal { - def showId = id.toString -} - -case class ClassComponent(value: RDFNode) extends Component -case class Datatype(value: IRI) extends Component -case class NodeKind(value: NodeKindType) extends Component -case class MinCount(value: Int) extends Component -case class MaxCount(value: Int) extends Component -case class MinExclusive(value: Literal) extends Component -case class MinInclusive(value: Literal) extends Component -case class MaxExclusive(value: Literal) extends Component -case class MaxInclusive(value: Literal) extends Component -case class MinLength(value: Int) extends Component -case class MaxLength(value: Int) extends Component -case class Pattern(pattern: String, flags: Option[String]) extends Component -case class UniqueLang(value: Boolean) extends Component -case class LanguageIn(langs: List[String]) extends Component -case class Equals(p: IRI) extends Component -case class Disjoint(p: IRI) extends Component -case class LessThan(p: IRI) extends Component -case class LessThanOrEquals(p: IRI) extends Component -case class Or(shapes: List[ShapeRef]) extends Component -case class And(shapes: List[ShapeRef]) extends Component -case class Not(shape: ShapeRef) extends Component -case class Xone(shapes: List[ShapeRef]) extends Component -case class Closed(isClosed: Boolean, ignoredProperties: List[IRI]) extends Component -case class NodeComponent(shape: ShapeRef) extends Component -case class HasValue(value: Value) extends Component -case class In(list: List[Value]) extends Component - -// TODO: Change representation to include optional parent shape -case class QualifiedValueShape( - shape: ShapeRef, - qualifiedMinCount: Option[Int], - qualifiedMaxCount: Option[Int], - qualifiedValueShapesDisjoint: Option[Boolean]) extends Component - -sealed trait NodeKindType { - def id: IRI -} -case object IRIKind extends NodeKindType { - override def id = sh_IRI -} -case object LiteralKind extends NodeKindType { - override def id = sh_Literal -} -case object BlankNodeKind extends NodeKindType { - override def id = sh_BlankNode -} -case object BlankNodeOrIRI extends NodeKindType { - override def id = sh_BlankNodeOrIRI -} -case object BlankNodeOrLiteral extends NodeKindType { - override def id = sh_BlankNodeOrLiteral -} -case object IRIOrLiteral extends NodeKindType { - override def id = sh_IRIOrLiteral -} - -// Companion iriObjects -object Schema { - val empty = - Schema( - pm = SHACLPrefixes.defaultPrefixMap, - shapesMap = Map[ShapeRef, Shape]()) -} - -object Shape { - - def empty(id: RDFNode) = NodeShape( - id = id, - components = List(), - targets = Seq(), - propertyShapes = Seq(), - closed = false, - ignoredProperties = List()) - - def emptyPropertyShape( - id: RDFNode, - path: SHACLPath): PropertyShape = PropertyShape( - id = id, - path = path, - components = Seq(), - targets = Seq(), - propertyShapes = Seq(), - closed = false, - ignoredProperties = List()) -} - diff --git a/modules/shacl/src/main/scala/es/weso/shacl/Shape.scala b/modules/shacl/src/main/scala/es/weso/shacl/Shape.scala new file mode 100644 index 00000000..55adb497 --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/Shape.scala @@ -0,0 +1,94 @@ +package es.weso.shacl + +import es.weso.rdf.nodes.{BNode, IRI, RDFNode} +import es.weso.rdf.path.SHACLPath + +sealed abstract class Shape { + def id: RDFNode + def targets: Seq[Target] + def components: Seq[Component] + def propertyShapes: Seq[ShapeRef] + def closed: Boolean + def ignoredProperties: List[IRI] + + def hasId(iri: IRI): Boolean = { + id == iri + } + + def showId: String = + id match { + case iri: IRI => iri.str + case bnode: BNode => bnode.toString + } + + def targetNodes: Seq[RDFNode] = + targets.map(_.toTargetNode).flatten.map(_.node) + + def targetClasses: Seq[RDFNode] = + targets.map(_.toTargetClass).flatten.map(_.node) + + def targetSubjectsOf: Seq[IRI] = + targets.map(_.toTargetSubjectsOf).flatten.map(_.pred) + + def targetObjectsOf: Seq[IRI] = + targets.map(_.toTargetObjectsOf).flatten.map(_.pred) + +} + + +/** + * Captures the common parts of NodeShapes and PropertyShapes + */ +/*sealed abstract class Constraint { + def isPropertyConstraint: Boolean + def toPropertyConstraint: Option[PropertyShape] = None + def components: Seq[Component] +} */ + +case class NodeShape( + id: RDFNode, + components: List[Component], + targets: Seq[Target], + propertyShapes: Seq[ShapeRef], + closed: Boolean, + ignoredProperties: List[IRI]) extends Shape { + + def isPropertyConstraint = false +} + +case class PropertyShape( + id: RDFNode, + path: SHACLPath, + components: Seq[Component], + targets: Seq[Target], + propertyShapes: Seq[ShapeRef], + closed: Boolean, + ignoredProperties: List[IRI]) extends Shape { + + def isPropertyConstraint = true + + def predicate: IRI = path.predicate.get + +} + +object Shape { + + def empty(id: RDFNode) = NodeShape( + id = id, + components = List(), + targets = Seq(), + propertyShapes = Seq(), + closed = false, + ignoredProperties = List()) + + def emptyPropertyShape( + id: RDFNode, + path: SHACLPath): PropertyShape = PropertyShape( + id = id, + path = path, + components = Seq(), + targets = Seq(), + propertyShapes = Seq(), + closed = false, + ignoredProperties = List()) +} diff --git a/modules/shacl/src/main/scala/es/weso/shacl/ShapeRef.scala b/modules/shacl/src/main/scala/es/weso/shacl/ShapeRef.scala new file mode 100644 index 00000000..e8f7c620 --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/ShapeRef.scala @@ -0,0 +1,7 @@ +package es.weso.shacl + +import es.weso.rdf.nodes.RDFNode + +case class ShapeRef(id: RDFNode) extends AnyVal { + def showId = id.toString +} diff --git a/modules/shacl/src/main/scala/es/weso/shacl/Target.scala b/modules/shacl/src/main/scala/es/weso/shacl/Target.scala new file mode 100644 index 00000000..03300d09 --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/Target.scala @@ -0,0 +1,26 @@ +package es.weso.shacl + +import es.weso.rdf.nodes.{IRI, RDFNode} + +sealed abstract class Target { + def toTargetNode: Option[TargetNode] = this match { + case tn: TargetNode => Some(tn) + case _ => None + } + def toTargetClass: Option[TargetClass] = this match { + case tc: TargetClass => Some(tc) + case _ => None + } + def toTargetSubjectsOf: Option[TargetSubjectsOf] = this match { + case t: TargetSubjectsOf => Some(t) + case _ => None + } + def toTargetObjectsOf: Option[TargetObjectsOf] = this match { + case t: TargetObjectsOf => Some(t) + case _ => None + } +} +case class TargetNode(node: RDFNode) extends Target +case class TargetClass(node: RDFNode) extends Target +case class TargetSubjectsOf(pred: IRI) extends Target +case class TargetObjectsOf(pred: IRI) extends Target diff --git a/modules/shacl/src/main/scala/es/weso/shacl/Value.scala b/modules/shacl/src/main/scala/es/weso/shacl/Value.scala new file mode 100644 index 00000000..e752a7cd --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/Value.scala @@ -0,0 +1,40 @@ +package es.weso.shacl + +import es.weso.rdf.nodes.{IRI, Literal, RDFNode} + +/** + * Represents IRIs or Literals (no Blank nodes) + */ +trait Value { + /** + * `true` if `node` matches this value + */ + def matchNode(node: RDFNode): Boolean + + /** + * Conversion from values to RDFNode's + */ + def rdfNode: RDFNode +} + +case class IRIValue(iri: IRI) extends Value { + override def matchNode(node: RDFNode) = { + node match { + case i: IRI => iri == i + case _ => false + } + } + + override def rdfNode: RDFNode = iri +} + +case class LiteralValue(literal: Literal) extends Value { + override def matchNode(node: RDFNode) = { + node match { + case l: Literal => l == literal + case _ => false + } + } + + override def rdfNode: RDFNode = literal +} 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 36b6044c..71554d88 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 @@ -1,7 +1,5 @@ package es.weso.shacl.converter -import cats._ -import cats.implicits._ import com.typesafe.scalalogging.LazyLogging import es.weso.rdf.PREFIXES._ import es.weso.rdf.RDFReader @@ -10,7 +8,6 @@ import es.weso.rdf.parser.RDFParser import es.weso.rdf.path._ import es.weso.shacl.SHACLPrefixes._ import es.weso.shacl._ -import es.weso.utils.TryUtils._ import scala.util.{ Failure, Success, Try } 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 e48cb223..9a13a78b 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 @@ -5,65 +5,66 @@ import cats.implicits._ import com.typesafe.scalalogging.LazyLogging import es.weso.rdf.nodes._ import es.weso.shacl.SHACLPrefixes._ -import es.weso.rdf.PREFIXES.{ sh => _, _ } -import es.weso.rdf.jena._ +import es.weso.rdf.PREFIXES.{sh => _, _} +import es.weso.rdf.RDFBuilder import es.weso.rdf.path._ +import es.weso.rdf.saver.RDFSaver import es.weso.shacl._ class Shacl2RDF extends RDFSaver with LazyLogging { - def serialize(shacl: Schema, format: String): Either[String, String] = { - val rdf: RDFAsJenaModel = toRDF(shacl, RDFAsJenaModel.empty) + def serialize(shacl: Schema, format: String, builder: RDFBuilder): Either[String, String] = { + val rdf: RDFBuilder = toRDF(shacl, builder) rdf.serialize(format) } - def toRDF(shacl: Schema, initial: RDFAsJenaModel): RDFAsJenaModel = { + def toRDF(shacl: Schema, initial: RDFBuilder): RDFBuilder = { val result = schema(shacl).run(initial) result.value._1 } - def schema(shacl: Schema): RDFSaver[Unit] = { - val rs = shacl.shapes.toList.map(shape(_)) + private def schema(shacl: Schema): RDFSaver[Unit] = { for { _ <- addPrefix("sh", sh.str) _ <- addPrefix("xsd", xsd.str) _ <- addPrefix("rdf", rdf.str) _ <- addPrefix("rdfs", rdfs.str) + rs = shacl.shapes.toList.map(shape(_)) _ <- rs.sequence } yield () } - def shape(shape: Shape): RDFSaver[RDFNode] = shape match { + private def shape(shape: Shape): RDFSaver[RDFNode] = shape match { case ns: NodeShape => nodeShape(ns) case ps: PropertyShape => propertyShape(ps) } - def shapeRef(shape: ShapeRef): RDFSaver[RDFNode] = ok(shape.id) + private def shapeRef(shape: ShapeRef): RDFSaver[RDFNode] = ok(shape.id) - def makeShapeId(v: RDFNode): RDFSaver[RDFNode] = ok(v) + private def makeShapeId(v: RDFNode): RDFSaver[RDFNode] = ok(v) - def targets(id: RDFNode, ts: Seq[Target]): RDFSaver[Unit] = + private def targets(id: RDFNode, ts: Seq[Target]): RDFSaver[Unit] = saveList(ts.toList, target(id)) - def target(id: RDFNode)(t: Target): RDFSaver[Unit] = t match { + private def target(id: RDFNode)(t: Target): RDFSaver[Unit] = t match { case TargetNode(node) => addTriple(id, sh_targetNode, node) case TargetClass(node) => addTriple(id, sh_targetClass, node) case TargetSubjectsOf(node) => addTriple(id, sh_targetSubjectsOf, node) case TargetObjectsOf(node) => addTriple(id, sh_targetObjectsOf, node) } - def propertyShapes(id: RDFNode, ts: Seq[ShapeRef]): RDFSaver[Unit] = + private def propertyShapes(id: RDFNode, ts: Seq[ShapeRef]): RDFSaver[Unit] = saveList(ts.toList, makePropertyShape(id)) - def makePropertyShape(id: RDFNode)(p: ShapeRef): RDFSaver[Unit] = for { + private def makePropertyShape(id: RDFNode)(p: ShapeRef): RDFSaver[Unit] = for { node <- ok(p.id) // propertyShape(p) _ <- addTriple(id, sh_property, node) } yield () - def closed(id: RDFNode, b: Boolean): RDFSaver[Unit] = + private def closed(id: RDFNode, b: Boolean): RDFSaver[Unit] = addTriple(id, sh_closed, BooleanLiteral(b)) - def ignoredProperties(id: RDFNode, ignored: List[IRI]): RDFSaver[Unit] = + private def ignoredProperties(id: RDFNode, ignored: List[IRI]): RDFSaver[Unit] = if (!ignored.isEmpty) { for { nodeList <- saveToRDFList(ignored, (iri: IRI) => State.pure(iri)) @@ -72,7 +73,7 @@ class Shacl2RDF extends RDFSaver with LazyLogging { } else State.pure(()) - def propertyShape(t: PropertyShape): RDFSaver[RDFNode] = for { + private def propertyShape(t: PropertyShape): RDFSaver[RDFNode] = for { shapeNode <- makeShapeId(t.id) _ <- addTriple(shapeNode, rdf_type, sh_PropertyShape) _ <- targets(shapeNode, t.targets) @@ -84,7 +85,7 @@ class Shacl2RDF extends RDFSaver with LazyLogging { _ <- saveList(t.components.toList, component(shapeNode)) } yield (shapeNode) - def nodeShape(n: NodeShape): RDFSaver[RDFNode] = for { + private def nodeShape(n: NodeShape): RDFSaver[RDFNode] = for { shapeNode <- makeShapeId(n.id) _ <- addTriple(shapeNode, rdf_type, sh_NodeShape) _ <- targets(shapeNode, n.targets) @@ -94,7 +95,7 @@ class Shacl2RDF extends RDFSaver with LazyLogging { _ <- saveList(n.components, component(shapeNode)) } yield shapeNode - def makePath(path: SHACLPath): RDFSaver[RDFNode] = path match { + private def makePath(path: SHACLPath): RDFSaver[RDFNode] = path match { case PredicatePath(iri) => State.pure(iri) case InversePath(p) => for { node <- createBNode @@ -126,10 +127,10 @@ class Shacl2RDF extends RDFSaver with LazyLogging { pathNodes <- makePath(p) _ <- addTriple(node,sh_oneOrMorePath,pathNode) } yield node */ - case _ => throw new Exception(s"Not implemented path generation to RDF yet: $path") + case _ => throw new Exception(s"Unimplemented conversion of path: $path") } - def component(id: RDFNode)(c: Component): RDFSaver[Unit] = c match { + 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) case NodeKind(value) => addTriple(id, sh_nodeKind, value.id) @@ -192,7 +193,6 @@ class Shacl2RDF extends RDFSaver with LazyLogging { nodeLs <- saveToRDFList(vs, (v: Value) => State.pure(v.rdfNode)) _ <- addTriple(id, sh_in, nodeLs) } yield () - } } diff --git a/modules/shacl/src/main/scala/es/weso/shacl/report/Severity.scala b/modules/shacl/src/main/scala/es/weso/shacl/report/Severity.scala index 611fc143..4c6e2c98 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/report/Severity.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/report/Severity.scala @@ -1,6 +1,21 @@ package es.weso.shacl.report -sealed abstract class Severity -case object ViolationSeverity extends Severity -case object WarningSeverity extends Severity -case object InfoSeverity extends Severity +import es.weso.rdf.nodes.IRI +import es.weso.shacl.SHACLPrefixes._ + +sealed abstract class Severity { + def toIRI: IRI +} +case object ViolationSeverity extends Severity { + override def toIRI: IRI = sh_Violation +} +case object WarningSeverity extends Severity { + override def toIRI: IRI = sh_Warning +} +case object InfoSeverity extends Severity { + override def toIRI: IRI = sh_Info +} + +object Severity { + val defaultSeverity = ViolationSeverity +} diff --git a/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationReport.scala b/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationReport.scala index 78e4447f..5a085624 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationReport.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationReport.scala @@ -1,7 +1,21 @@ package es.weso.shacl.report +import es.weso.rdf.RDFBuilder +import es.weso.rdf.saver.RDFSaver + case class ValidationReport(conforms: Boolean, results: Seq[ValidationResult], shapesGraphWellFormed: Boolean - ) + ) extends RDFSaver { + + def toRDF(builder: RDFBuilder): Either[String,RDFBuilder] = { + Right(ValidationReport2RDF(this,builder)) + } + +} +object ValidationReport { + def fromError(e: ValidationResult): ValidationReport = { + ValidationReport(conforms = false, results = Seq(e), true) + } +} \ No newline at end of file diff --git a/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationReport2RDF.scala b/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationReport2RDF.scala new file mode 100644 index 00000000..9097f5a3 --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/report/ValidationReport2RDF.scala @@ -0,0 +1,94 @@ +package es.weso.shacl.report + +import cats.data.State +import com.typesafe.scalalogging.LazyLogging +import es.weso.rdf.RDFBuilder +import es.weso.rdf.saver.RDFSaver +import es.weso.shacl.SHACLPrefixes._ +import es.weso.rdf.PREFIXES.{sh => _, _} +import es.weso.rdf.nodes.{BooleanLiteral, RDFNode} +import es.weso.rdf.path._ +import es.weso.shacl.LiteralValue + +class ValidationReport2RDF extends RDFSaver with LazyLogging { + + def toRDF(vr: ValidationReport, initial: RDFBuilder): RDFBuilder = { + val result = validationReport(vr).run(initial) + result.value._1 + } + + private def validationReport(vr: ValidationReport): RDFSaver[Unit] = for { + _ <- addPrefix("sh", sh.str) + node <- createBNode + _ <- addTriple(node, rdf_type, sh_ValidationReport) + _ <- addTriple(node, sh_conforms, BooleanLiteral(vr.conforms)) + _ <- results(node, vr.results) + } yield () + + private def results(id: RDFNode, ts: Seq[ValidationResult]): RDFSaver[Unit] = + saveList(ts.toList, result(id)) + + private def result(id: RDFNode)(vr: ValidationResult): RDFSaver[Unit] = for { + node <- createBNode() + _ <- addTriple(id, sh_result, node) + _ <- addTriple(node, rdf_type, sh_ValidationResult) + _ <- addTriple(node, sh_resultSeverity, vr.resultSeverity.toIRI) + _ <- addTriple(node, sh_focusNode, vr.focusNode) + _ <- addTriple(node, sh_sourceConstraintComponent, vr.sourceConstraintComponent) + _ <- saveList(vr.message.toList, message(node)) + _ <- vr.focusPath match { + case None => ok(()) + case Some(path) => for { + path <- makePath(path) + _ <- addTriple(node, sh_resultPath, path) + } yield () + } + } yield () + + private def makePath(path: SHACLPath): RDFSaver[RDFNode] = path match { + case PredicatePath(iri) => State.pure(iri) + case InversePath(p) => for { + node <- createBNode + pathNode <- makePath(p) + _ <- addTriple(node, sh_inversePath, pathNode) + } yield node + case ZeroOrOnePath(p) => for { + node <- createBNode + pathNode <- makePath(p) + _ <- addTriple(node, sh_zeroOrOnePath, pathNode) + } yield node + case ZeroOrMorePath(p) => for { + node <- createBNode + pathNode <- makePath(p) + _ <- addTriple(node, sh_zeroOrMorePath, pathNode) + } yield node + case OneOrMorePath(p) => for { + node <- createBNode + pathNode <- makePath(p) + _ <- addTriple(node, sh_oneOrMorePath, pathNode) + } yield node + /* case SequencePath(ps) => for { + list <- saveRDFList(ps, ) + pathNodes <- makePath(p) + _ <- addTriple(node,sh_oneOrMorePath,pathNode) + } yield node + case AlternativePath(ps) => for { + node <- createBNode + pathNodes <- makePath(p) + _ <- addTriple(node,sh_oneOrMorePath,pathNode) + } yield node */ + case _ => throw new Exception(s"Unimplemented conversion of path: $path") + } + + private def message(node: RDFNode)(msg: LiteralValue): RDFSaver[Unit] = for { + _ <- addTriple(node,sh_message,msg.literal) + } yield () + +} + +object ValidationReport2RDF { + + def apply(vr: ValidationReport, builder: RDFBuilder): RDFBuilder = + new ValidationReport2RDF().toRDF(vr,builder) + +} \ No newline at end of file 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 df9ea597..525608fb 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 @@ -1,17 +1,174 @@ package es.weso.shacl.report -import es.weso.rdf.nodes.RDFNode -import es.weso.rdf.path.SHACLPath +import es.weso.rdf.nodes._ +import es.weso.rdf.path._ +import es.weso.shacl.SHACLPrefixes.sh import es.weso.shacl._ +import es.weso.shacl.validator.Attempt abstract class AbstractResult -case class ValidationResult(focusNode: RDFNode, - resultSeverity: Severity, - sourceConstraintComponent: Component, - focusPath: Option[SHACLPath], - values: Seq[RDFNode], - sourceShape: Shape, - details: Seq[AbstractResult], - message: Seq[LiteralValue] - ) extends AbstractResult +class ValidationResult(val focusNode: RDFNode, + val resultSeverity: Severity, + val sourceConstraintComponent: IRI, + val focusPath: Option[SHACLPath], + val sourceShape: ShapeRef, + val values: Seq[RDFNode], + val message: Seq[LiteralValue], + val details: Seq[AbstractResult] + ) { + override def toString = s"Violation error on $focusNode: ${message.mkString(",")}" +} + +object ValidationResult { + + def basic(suffix: String, focusNode: RDFNode, attempt: Attempt, msg: String) = + new ValidationResult( + sourceConstraintComponent = sh + suffix, + focusNode = focusNode, + resultSeverity = Severity.defaultSeverity, + sourceShape = attempt.shapeRef, + values = Seq(), + focusPath = attempt.path, + message = Seq(LiteralValue( + StringLiteral(msg) + )), + details = Seq() + ) + + def notFoundShapeRef(node: RDFNode, attempt: Attempt, msg: String) = + basic("NotFoundShapeRef", node, attempt, msg) + + def expectedPropertyShape(node: RDFNode, attempt: Attempt, msg: String) = + basic("ExpectedPropertyShape", node, attempt, msg) + + def shapesFailed(node: RDFNode, shape: Shape, ps: Set[Shape], attempt: Attempt, msg: String) = + basic("ShapesFailed", node, attempt, msg) + + def regexError(node: RDFNode, attempt: Attempt, msg: String) = + basic("RegEx error", node, attempt, msg) + + def noSiblingsError(focusNode: RDFNode, p: PropertyShape, msg: String, attempt: Attempt) = + basic("noSiblingsError", focusNode, attempt, s"No siblings found for property shape $p in schema: $msg") + + def errorNode(node: RDFNode, shape: Shape, attempt: Attempt, msg: String): ValidationResult = + basic("NodeConstraintComponent", node, attempt, msg) + + def classError(focusNode: RDFNode, cls: RDFNode, attempt: Attempt) = + basic("ClassConstraintComponent", focusNode, attempt, s"Node $focusNode doesn't belong to class $cls") + + def datatypeError(focusNode: RDFNode, datatype: RDFNode, attempt: Attempt) = + basic("DatatypeConstraintComponent", focusNode, attempt, s"Node $focusNode doesn't have dataType $datatype") + + def unsupported(focusNode: RDFNode, attempt: Attempt, msg: String) = + basic("unsupported", focusNode, attempt, "Unsupported: " + msg) + + def notNumeric(focusNode: RDFNode, attempt: Attempt) = + basic("NotNumericConstraintComponent", focusNode, attempt, s"NotNumeric violation. Expected $focusNode to be a number") + + def minExclusiveError(focusNode: RDFNode, attempt: Attempt, n: Int) = + basic("MinExclusiveConstraintComponent", focusNode, attempt, s"minExclusive violation. Expected $focusNode > $n") + + def minInclusiveError(focusNode: RDFNode, attempt: Attempt, n: Int) = + basic("MinInclusiveConstraintComponent", focusNode, attempt, s"minInclusive violation. Expected $focusNode >= $n") + + def maxExclusiveError(focusNode: RDFNode, attempt: Attempt, n: Int) = + basic("MaxExclusiveConstraintComponent", focusNode, attempt, s"maxExclusive violation. Expected $focusNode < $n") + + def maxInclusiveError(focusNode: RDFNode, attempt: Attempt, n: Int) = + basic("MaxInclusiveConstraintComponent", focusNode, attempt, s"maxInclusive violation. Expected $focusNode <= $n") + + def minLengthError(focusNode: RDFNode, attempt: Attempt, n: Int) = + basic("MinLengthConstraintComponent", focusNode, attempt, s"minLength violation. Expected length($focusNode) >= $n") + + def maxLengthError(focusNode: RDFNode, attempt: Attempt, n: Int) = + basic("MaxLengthConstraintComponent", focusNode, attempt, s"maxLength violation. Expected length($focusNode) <= $n") + + def patternError(focusNode: RDFNode, attempt: Attempt, p: String, flags: Option[String]) = + basic("PatternConstraintComponent", focusNode, attempt, s"pattern violation. Expected $focusNode to match '$p'${flags.getOrElse("")}") + + def uniqueLangError(focusNode: RDFNode, attempt: Attempt, path: SHACLPath, vs: Seq[RDFNode]) = + basic("UniqueLangConstraintComponent", focusNode, attempt, s"uniqueLang violation. Expected $focusNode to have a unique language for path $path with values $vs") + + def languageInError(focusNode: RDFNode, attempt: Attempt, langs: List[String]) = + basic("LanguageInConstraintComponent", focusNode, attempt, s"languageIn violation. Expected $focusNode to match 'languageIn(${langs.mkString(",")})'") + + def equalsError(focusNode: RDFNode, attempt: Attempt, p: IRI, vs: Set[RDFNode]) = + comparisonError("EqualsConstraintComponent", focusNode, attempt, p, vs) + + def disjointError(focusNode: RDFNode, attempt: Attempt, p: IRI, vs: Set[RDFNode]) = + comparisonError("DisjointConstraintComponent", focusNode, attempt, p, vs) + + def lessThanError(focusNode: RDFNode, attempt: Attempt, p: IRI, vs: Set[RDFNode]) = + comparisonError("LessThanConstraintComponent", focusNode, attempt, p, vs) + + def lessThanOrEqualsError(focusNode: RDFNode, attempt: Attempt, p: IRI, vs: Set[RDFNode]) = + comparisonError("LessThanOrEqualsConstraintComponent", focusNode, attempt, p, vs) + + def comparisonError(name: String, focusNode: RDFNode, attempt: Attempt, p: IRI, vs: Set[RDFNode]) = + basic(s"${name}ConstraintComponent", focusNode, attempt, s"$name violation. Expected $focusNode to match $name '$p', values: $vs") + + def minCountError(focusNode: RDFNode, attempt: Attempt, minCount: Int, count: Int) = + basic("MinCountConstraintComponent", focusNode, attempt, s"MinCount violation. Expected $minCount, obtained: $count") + + def maxCountError(focusNode: RDFNode, attempt: Attempt, maxCount: Int, count: Int) = + basic("MaxCountConstraintComponent", focusNode, attempt, s"MaxCount violation. Expected $maxCount, obtained: $count") + + def iriKindError(focusNode: RDFNode, attempt: Attempt) = + basic("IriConstraintComponent", focusNode, attempt, s"Node $focusNode is not an IRI") + + def literalKindError(focusNode: RDFNode, attempt: Attempt) = + basic("LiteralConstraintComponent", focusNode, attempt, s"Node $focusNode is not a Literal") + + def bNodeKindError(focusNode: RDFNode, attempt: Attempt) = + basic("BNodeConstraintComponent", focusNode, attempt, s"Node $focusNode is not a blank node") + + def bNodeOrIRIKindError(focusNode: RDFNode, attempt: Attempt) = + basic("BNodeOrIRIConstraintComponent", focusNode, attempt, s"Node $focusNode is not a blank node or an IRI") + + def bNodeOrLiteralKindError(focusNode: RDFNode, attempt: Attempt) = + basic("BNodeOrLiteralConstraintComponent", focusNode, attempt, s"Node $focusNode is not a blank node or a Literal") + + 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) = + basic("NotConstraintComponent", focusNode, attempt, s"Not violation. Expected $focusNode not to satisfy ${shape.showId}") + + def andError(focusNode: RDFNode, attempt: Attempt, shapes: List[ShapeRef]) = + 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]) = + 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]) = + 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]) = + basic("QualifiedShapeConstraintComponent", focusNode, attempt, s"qualified shape error. Expected $focusNode to satisfy qualifiedValueShape. Value = ${value}, min: ${min.map(_.toString).getOrElse("-")}, max: ${max.map(_.toString).getOrElse("-")}") + + def hasValueError(focusNode: RDFNode, attempt: Attempt, value: Value) = + basic("HasValueConstraintComponent", focusNode, attempt, s"HasValue error. Expected $focusNode to be $value") + + def hasValueErrorNoValue(focusNode: RDFNode, attempt: Attempt, value: Value, path: SHACLPath) = + basic("HasValueConstraintComponent", focusNode, attempt, s"HasValue error. Missing value for path $path on $focusNode. Value must be $value") + + def hasValueErrorMoreThanOne(focusNode: RDFNode, attempt: Attempt, value: Value, path: SHACLPath, n: Int) = + basic("HasValueConstraintComponent", focusNode, attempt, s"HasValue error. More than one value ($n) for path $path on $focusNode. Value must be $value") + + 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) = + basic("notShape", focusNode, attempt, s"Not failed because $focusNode has shape $shapeRef and it should not have") + + def closedError( + focusNode: RDFNode, + attempt: Attempt, + allowedProperties: List[IRI], + ignoredProperties: List[IRI], + notAllowed: List[IRI]) = + basic("ClosedConstraintComponent", focusNode, attempt, + s"closed violation. $focusNode has more properties than $allowedProperties. Extra properties found: $notAllowed, ignoredProperties: $ignoredProperties") + +} diff --git a/modules/shacl/src/main/scala/es/weso/shacl/showShacl.scala b/modules/shacl/src/main/scala/es/weso/shacl/showShacl.scala index d8d841e3..5b08be19 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/showShacl.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/showShacl.scala @@ -1,7 +1,7 @@ package es.weso.shacl import cats._ import es.weso.rdf.nodes._ -import es.weso.shacl.validator.ViolationError +import es.weso.shacl.report.ValidationResult object showShacl { @@ -11,9 +11,9 @@ object showShacl { } } - implicit def showError = new Show[ViolationError] { - def show(ve: ViolationError): String = { - s"Violation Error(${ve.id}). Node(${ve.focusNode}) ${ve.message.getOrElse("")}" + implicit def showError = new Show[ValidationResult] { + def show(ve: ValidationResult): String = { + s"Violation Error(${ve.sourceConstraintComponent}). Node(${ve.focusNode}) ${ve.message.mkString(",")}" } } diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/CheckResult.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/CheckResult.scala index e31144c7..d1b5f43e 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/validator/CheckResult.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/validator/CheckResult.scala @@ -3,6 +3,7 @@ package es.weso.shacl.validator import cats._ import cats.implicits._ + case class CheckResult[E: Show, A: Show, Log: Show](r: (Log, Either[E, A])) { def result: Either[E, A] = r._2 diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/SHACLChecker.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/SHACLChecker.scala index a61d313b..37e70c8d 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/validator/SHACLChecker.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/validator/SHACLChecker.scala @@ -3,24 +3,16 @@ package es.weso.shacl.validator import cats._ import cats.implicits._ import es.weso.rdf._ -import es.weso.rdf.nodes._ import es.weso.checking.CheckerCats -import es.weso.shacl._ -import es.weso.shacl.validator.Validator.ShapeTyping -import es.weso.typing._ -import es.weso.shacl.showShacl._ +import es.weso.shacl.report.ValidationResult object SHACLChecker extends CheckerCats { type Config = RDFReader type Env = ShapeTyping - type Err = ViolationError - // type Evidence = (NodeShapePair, String) + type Err = ValidationResult type Log = List[Evidence] - implicit val envMonoid: Monoid[Env] = new Monoid[Env] { - def combine(e1: Env, e2: Env): Env = e1.combineTyping(e2) - def empty: Env = Typing.empty - } + implicit val envMonoid: Monoid[Env] = Monoid[ShapeTyping] implicit val logMonoid: Monoid[Log] = new Monoid[Log] { def combine(l1: Log, l2: Log): Log = l1 ++ l2 def empty: Log = List() @@ -28,7 +20,4 @@ object SHACLChecker extends CheckerCats { implicit val logShow: Show[Log] = new Show[Log] { def show(l: Log): String = l.map(_.show).mkString("\n") } - implicit val typingShow: Show[ShapeTyping] = new Show[ShapeTyping] { - def show(t: ShapeTyping): String = Typing.showTyping[RDFNode,Shape,ViolationError,String].show(t) - } } diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/ShapeTyping.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/ShapeTyping.scala new file mode 100644 index 00000000..cbe9b0c1 --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/validator/ShapeTyping.scala @@ -0,0 +1,70 @@ +package es.weso.shacl.validator +import cats._ +import es.weso.rdf.nodes.RDFNode +import es.weso.shacl.Shape +import es.weso.shacl.report.{ValidationReport, ValidationResult} +import es.weso.typing._ + +case class ShapeTyping(t: Typing[RDFNode, Shape, ValidationResult, String]) { + + def getMap : Map[RDFNode, Map[Shape, TypingResult[ValidationResult, String]]] = + t.getMap + + def hasType(node: RDFNode, shape: Shape): Boolean = + t.hasType(node,shape) + + def addType(node: RDFNode, shape: Shape): ShapeTyping = + ShapeTyping(t.addType(node,shape)) + + def addEvidence(node: RDFNode, shape: Shape, msg: String): ShapeTyping = + ShapeTyping(t.addEvidence(node, shape, msg)) + + def addNotEvidence(node: RDFNode, shape: Shape, e: ValidationResult): ShapeTyping = + ShapeTyping(t.addNotEvidence(node,shape,e)) + + def getFailedValues(node: RDFNode): Set[Shape] = + t.getFailedValues(node) + + def getOkValues(node: RDFNode): Set[Shape] = + t.getOkValues(node) + + def toValidationReport: ValidationReport = { + ValidationReport( + conforms = t.allOk, + results = { + val rs: Seq[(RDFNode, Shape, TypingResult[ValidationResult, String])] = + t.getMap.toSeq.map { + case (node,valueMap) => valueMap.toSeq.map { + case (shape, result) => (node, shape, result) + } + }.flatten + rs.map(_._3.getErrors.toList.flatten).flatten + }, + shapesGraphWellFormed = true + ) + } + +} + +object ShapeTyping { + def empty: ShapeTyping = ShapeTyping(Typing.empty) + + def combineTypings(ts: Seq[ShapeTyping]): ShapeTyping = + ShapeTyping(Typing.combineTypings(ts.map(_.t))) + + implicit def showShapeTyping: Show[ShapeTyping] = new Show[ShapeTyping] { + override def show(st: ShapeTyping): String = { + st.toString + // TODO + // Should be: st.t.show + } + } + + implicit def monoidShapeTyping: Monoid[ShapeTyping] = new Monoid[ShapeTyping] { + override def empty: ShapeTyping = ShapeTyping.empty + override def combine(t1: ShapeTyping, t2: ShapeTyping): ShapeTyping = + ShapeTyping(Typing.combineTypings(Seq(t1.t,t2.t))) + } + +} + diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/TestValidator.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/TestValidator.scala new file mode 100644 index 00000000..a8b290e2 --- /dev/null +++ b/modules/shacl/src/main/scala/es/weso/shacl/validator/TestValidator.scala @@ -0,0 +1,17 @@ +package es.weso.shacl.validator + +import es.weso.rdf.nodes.RDFNode +import es.weso.rdf.path.SHACLPath +import es.weso.shacl.Shape +import SHACLChecker._ + +object TestValidator { + type CheckTyping = Check[ShapeTyping] + type PropertyChecker = (Attempt, SHACLPath) => CheckTyping + type NodeChecker = Attempt => RDFNode => CheckTyping + type ShapeChecker = Shape => CheckTyping + type NodeShapeChecker = (RDFNode, Shape) => CheckTyping + + def getTyping: Check[ShapeTyping] = getEnv + +} 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 fef83bb2..a89e511c 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 @@ -1,20 +1,18 @@ package es.weso.shacl.validator import cats._ -import cats.data._ import cats.implicits._ import com.typesafe.scalalogging.LazyLogging import es.weso.rdf._ import es.weso.rdf.nodes._ import es.weso.rdf.path.SHACLPath -import ViolationError._ import es.weso.shacl._ -import es.weso.typing._ import es.weso.utils.RegEx -import Validator._ import es.weso.shacl.showShacl._ import SHACLChecker._ import es.weso.rdf.PREFIXES._ +import es.weso.shacl.report.ValidationResult +import es.weso.shacl.report.ValidationResult._ /** * This validator is implemented directly in Scala using cats library @@ -39,8 +37,8 @@ case class Validator(schema: Schema) extends LazyLogging { schema.targetNodeShapes } - def runCheck[A: Show](c: Check[A], rdf: RDFReader): CheckResult[ViolationError, A, Log] = { - val initial: ShapeTyping = Typing.empty + def runCheck[A: Show](c: Check[A], rdf: RDFReader): CheckResult[ValidationResult, A, Log] = { + val initial: ShapeTyping = ShapeTyping.empty val r = run(c)(rdf)(initial) CheckResult(r) } @@ -180,9 +178,11 @@ case class Validator(schema: Schema) extends LazyLogging { propertyShapes <- getPropertyShapeRefs(shape.propertyShapes.toList,attempt,node) } yield { val failedPropertyShapes = t.getFailedValues(node).intersect(propertyShapes.toSet) + // TODO: Remove the following condition? if (!failedPropertyShapes.isEmpty) { - val newt = t.addNotEvidence(node, shape, shapesFailed(node, shape, failedPropertyShapes, attempt, s"Failed property shapes")) - newt +// val newt = t.addNotEvidence(node, shape, shapesFailed(node, shape, failedPropertyShapes, attempt, s"Failed property shapes")) +// newt + t } else t @@ -241,16 +241,6 @@ case class Validator(schema: Schema) extends LazyLogging { private def checkComponent(c: Component): NodeChecker = component2Checker(c) - /* - private def validateNodeCheckers(attempt: Attempt, cs: Seq[NodeChecker]): Check[ShapeTyping] = { - val newAttempt = attempt.copy(path = None) - val xs = cs.map(c => c(newAttempt)(newAttempt.node)).toList - for { - ts <- checkAll(xs) - t <- combineTypings(ts) - } yield t - } */ - private def validatePathCheckers(attempt: Attempt, path: SHACLPath, cs: Seq[PropertyChecker]): Check[ShapeTyping] = { val newAttempt = attempt.copy(path = Some(path)) val xs = cs.map(c => c(newAttempt, path)).toList @@ -319,12 +309,11 @@ case class Validator(schema: Schema) extends LazyLogging { for { s <- getShapeRef(sref, attempt, node) typing <- getTyping - shape <- getShapeRef(sref, attempt, node) + // shape <- getShapeRef(sref, attempt, node) newTyping <- if (typing.getOkValues(node).contains(s)) getTyping else if (typing.getFailedValues(node).contains(s)) { - err(failedNodeShape( - node, s, attempt, + err(errorNode(node, s, attempt, s"Failed because $node doesn't match shape $s")) *> getTyping } else runLocal(nodeShape(node, s), _.addType(node, s)) @@ -375,10 +364,10 @@ case class Validator(schema: Schema) extends LazyLogging { } private def compareIntLiterals( - n: Literal, - f: (Int, Int) => Boolean, - err: (RDFNode, Attempt, Int) => ViolationError, - msg: String): NodeChecker = attempt => node => for { + n: Literal, + f: (Int, Int) => Boolean, + err: (RDFNode, Attempt, Int) => ValidationResult, + msg: String): NodeChecker = attempt => node => for { ctrolValue <- checkNumeric(n, attempt) value <- checkNumeric(node, attempt) t <- condition(f(ctrolValue, value), attempt, @@ -467,10 +456,10 @@ case class Validator(schema: Schema) extends LazyLogging { // TODO: Maybe add a check to see if the nodes are comparable // With current definition, if nodes are not comparable, always returns false without raising any error... private def comparison( - p: IRI, - name: String, - errorMaker: (RDFNode, Attempt, IRI, Set[RDFNode]) => ViolationError, - cond: (RDFNode, RDFNode) => Boolean): NodeChecker = + p: IRI, + name: String, + errorMaker: (RDFNode, Attempt, IRI, Set[RDFNode]) => ValidationResult, + cond: (RDFNode, RDFNode) => Boolean): NodeChecker = attempt => node => for { rdf <- getRDF subject = attempt.node @@ -485,17 +474,22 @@ case class Validator(schema: Schema) extends LazyLogging { shapes <- getShapeRefs(srefs.toList, attempt, node) es = shapes.map(nodeShape(node, _)) ts <- checkAll(es) - t1 <- addEvidence(attempt, s"$node passes and(${shapes.map(_.showId).mkString(",")})") - t <- combineTypings(t1 +: ts) - } yield t + t <- combineTypings(ts) + t1 <- condition(checkAnd(node, shapes, t), + attempt, + andError(node, attempt, srefs.toList), + s"and condition failed on $node") + } yield t1 + } + + private def checkAnd(node: RDFNode, shapes: List[Shape], t: ShapeTyping) : Boolean = { + t.getFailedValues(node).isEmpty } private def xone(sRefs: Seq[ShapeRef]): NodeChecker = attempt => node => { for { shapes <- getShapeRefs(sRefs.toList, attempt, node) es = shapes.map(nodeShape(node, _)) -/* t1 <- checkOneOf(es, xoneErrorNone(node, attempt, sRefs.toList), - xoneErrorMoreThanOne(node, attempt, sRefs.toList)) */ ts <- checkAll(es) t <- combineTypings(ts) t1 <- condition(checkXoneType(node, shapes, t), @@ -717,10 +711,10 @@ case class Validator(schema: Schema) extends LazyLogging { * @param evidence evidence to add to `attempt` in case `condition` is true */ private def condition( - condition: Boolean, - attempt: Attempt, - error: ViolationError, - evidence: String): CheckTyping = for { + condition: Boolean, + attempt: Attempt, + error: ValidationResult, + evidence: String): CheckTyping = for { t <- getTyping newType <- cond(validateCheck(condition, error), (_: Unit) => addEvidence(attempt, evidence), @@ -741,9 +735,9 @@ case class Validator(schema: Schema) extends LazyLogging { } private def addNotEvidence( - attempt: Attempt, - e: ViolationError, - msg: String): Check[ShapeTyping] = { + attempt: Attempt, + e: ValidationResult, + msg: String): Check[ShapeTyping] = { val node = attempt.node // val sref = attempt.shapeRef for { @@ -758,9 +752,9 @@ case class Validator(schema: Schema) extends LazyLogging { def runLocal[A](c: Check[A], f: ShapeTyping => ShapeTyping): Check[A] = local(f)(c) - private def getRDF: Check[RDFReader] = getConfig // ask[Comput,RDFReader] + private def getRDF: Check[RDFReader] = getConfig - private def getTyping: Check[ShapeTyping] = getEnv // ask[Comput,ShapeTyping] + private def getTyping: Check[ShapeTyping] = getEnv //////////////////////////////////////////// /** @@ -817,33 +811,10 @@ case class Validator(schema: Schema) extends LazyLogging { } private def combineTypings(ts: Seq[ShapeTyping]): Check[ShapeTyping] = { - ok(Typing.combineTypings(ts)) - } - - /* - // TODO - // Define a more general method? - // This method should validate some of the nodes/shapes not raising a whole error if one fails, - // but collecting the good ones and the errors... - def checkAny(xs: Seq[Check[(RDFNode, Shape)]]): Check[Seq[(RDFNode, Shape)]] = { - val zero: Check[Seq[(RDFNode, Shape)]] = ok(Seq()) - def next( - x: Check[(RDFNode, Shape)], - rest: Check[Seq[(RDFNode, Shape)]]): Check[Seq[(RDFNode, Shape)]] = ??? - /* for { - rs1 <- catchWrong(x.flatMap(v => Seq(x)))(_ => ok(Seq())) - rs2 <- rest - } rs1 ++ rs2 */ - xs.foldRight(zero)(next) - } */ - - // Fails if there is any error - def validateAll(rdf: RDFReader): CheckResult[ViolationError, ShapeTyping, Log] = { - /* implicit def showPair = new Show[(ShapeTyping, Evidences)] { - def show(e: (ShapeTyping, Evidences)): String = { - s"Typing: ${e._1}\n Evidences: ${e._2}" - } - } */ + ok(ShapeTyping.combineTypings(ts)) + } + + def validateAll(rdf: RDFReader): CheckResult[ValidationResult, ShapeTyping, Log] = { runCheck(checkSchemaAll, rdf) } @@ -854,14 +825,7 @@ case class Validator(schema: Schema) extends LazyLogging { object Validator { def empty = Validator(schema = Schema.empty) - type ShapeTyping = Typing[RDFNode, Shape, ViolationError, String] - - type Result[A] = Either[NonEmptyList[ViolationError], List[(A, Evidences)]] - - def isOK[A](r: Result[A]): Boolean = - r.isRight && r.toList.isEmpty == false - - def validate(schema: Schema, rdf: RDFReader): Either[ViolationError, ShapeTyping] = { + def validate(schema: Schema, rdf: RDFReader): Either[ValidationResult, ShapeTyping] = { Validator(schema).validateAll(rdf).result } diff --git a/modules/shacl/src/main/scala/es/weso/shacl/validator/ViolationError.scala b/modules/shacl/src/main/scala/es/weso/shacl/validator/ViolationError.scala deleted file mode 100644 index 784f908b..00000000 --- a/modules/shacl/src/main/scala/es/weso/shacl/validator/ViolationError.scala +++ /dev/null @@ -1,174 +0,0 @@ -package es.weso.shacl.validator - -import cats.Show -import es.weso.rdf.nodes._ -import es.weso.rdf.path.{PredicatePath, SHACLPath} -import es.weso.shacl.SHACLPrefixes.sh -import es.weso.shacl.{PropertyShape, Shape, ShapeRef, Value} - -case class ViolationError( - id: IRI, - focusNode: RDFNode, - subject: Option[RDFNode], - path: Option[SHACLPath], - obj: Option[RDFNode], - message: Option[String], - sourceConstraint: RDFNode) { - override def toString = s"Violation error on $focusNode: ${message.getOrElse("")}" -} - -object ViolationError { - - - def basic(suffix: String, focusNode: RDFNode, attempt: Attempt, msg: String) = - ViolationError( - id = sh + suffix, - focusNode = focusNode, - subject = None, - path = attempt.path, - obj = None, - message = Some(msg + s" Node: ${attempt.node}, Constraint: ${attempt.shapeId}, path: ${attempt.path.getOrElse(PredicatePath(IRI("")))}"), - sourceConstraint = attempt.shapeId) - - def notFoundShapeRef(node: RDFNode, attempt: Attempt, msg: String) = - basic("NotFoundShapeRef", node, attempt, msg) - - def expectedPropertyShape(node: RDFNode, attempt: Attempt, msg: String) = - basic("ExpectedPropertyShape", node, attempt, msg) - - def failedNodeShape(node: RDFNode, shape: Shape, attempt: Attempt, msg: String) = - basic("FailedNodeShape", node, attempt, msg) - - def shapesFailed(node: RDFNode, shape: Shape, ps: Set[Shape], attempt: Attempt, msg: String) = - basic("ShapesFailed", node, attempt, msg) - - def regexError(node: RDFNode, attempt: Attempt, msg: String) = - basic("RegEx error", node, attempt, msg) - - def noSiblingsError(focusNode: RDFNode, p: PropertyShape, msg: String, attempt: Attempt) = - basic("noSiblingsError", focusNode, attempt, s"No siblings found for property shape $p in schema: $msg") - - def classError(focusNode: RDFNode, cls: RDFNode, attempt: Attempt) = - basic("classError", focusNode, attempt, s"Node $focusNode doesn't belong to class $cls") - - def datatypeError(focusNode: RDFNode, datatype: RDFNode, attempt: Attempt) = - basic("dataTypeError", focusNode, attempt, s"Node $focusNode doesn't have dataType $datatype") - - def unsupported(focusNode: RDFNode, attempt: Attempt, msg: String) = - basic("unsupported", focusNode, attempt, "Unsupported: " + msg) - - def notNumeric(focusNode: RDFNode, attempt: Attempt) = - basic("notNumericError", focusNode, attempt, s"NotNumeric violation. Expected $focusNode to be a number") - - def minExclusiveError(focusNode: RDFNode, attempt: Attempt, n: Int) = - basic("minExclusiveError", focusNode, attempt, s"minExclusive violation. Expected $focusNode > $n") - - def minInclusiveError(focusNode: RDFNode, attempt: Attempt, n: Int) = - basic("minInclusiveError", focusNode, attempt, s"minInclusive violation. Expected $focusNode >= $n") - - def maxExclusiveError(focusNode: RDFNode, attempt: Attempt, n: Int) = - basic("maxExclusiveError", focusNode, attempt, s"maxExclusive violation. Expected $focusNode < $n") - - def maxInclusiveError(focusNode: RDFNode, attempt: Attempt, n: Int) = - basic("maxInclusiveError", focusNode, attempt, s"maxInclusive violation. Expected $focusNode <= $n") - - def minLengthError(focusNode: RDFNode, attempt: Attempt, n: Int) = - basic("minLengthError", focusNode, attempt, s"minLength violation. Expected length($focusNode) >= $n") - - def maxLengthError(focusNode: RDFNode, attempt: Attempt, n: Int) = - basic("maxLengthError", focusNode, attempt, s"maxLength violation. Expected length($focusNode) <= $n") - - def patternError(focusNode: RDFNode, attempt: Attempt, p: String, flags: Option[String]) = - basic("patternError", focusNode, attempt, s"pattern violation. Expected $focusNode to match '$p'${flags.getOrElse("")}") - - def uniqueLangError(focusNode: RDFNode, attempt: Attempt, path: SHACLPath, vs: Seq[RDFNode]) = - basic("uniqueLangError", focusNode, attempt, s"uniqueLang violation. Expected $focusNode to have a unique language for path $path with values $vs") - - def languageInError(focusNode: RDFNode, attempt: Attempt, langs: List[String]) = - basic("languageInError", focusNode, attempt, s"languageIn violation. Expected $focusNode to match 'languageIn(${langs.mkString(",")})'") - - def equalsError(focusNode: RDFNode, attempt: Attempt, p: IRI, vs: Set[RDFNode]) = - comparisonError("equals", focusNode, attempt, p, vs) - - def disjointError(focusNode: RDFNode, attempt: Attempt, p: IRI, vs: Set[RDFNode]) = - comparisonError("disjoint", focusNode, attempt, p, vs) - - def lessThanError(focusNode: RDFNode, attempt: Attempt, p: IRI, vs: Set[RDFNode]) = - comparisonError("lessThan", focusNode, attempt, p, vs) - - def lessThanOrEqualsError(focusNode: RDFNode, attempt: Attempt, p: IRI, vs: Set[RDFNode]) = - comparisonError("lessThanOrEquals", focusNode, attempt, p, vs) - - def comparisonError(name: String, focusNode: RDFNode, attempt: Attempt, p: IRI, vs: Set[RDFNode]) = - basic(s"${name}Error", focusNode, attempt, s"$name violation. Expected $focusNode to match $name '$p', values: $vs") - - def minCountError(focusNode: RDFNode, attempt: Attempt, minCount: Int, count: Int) = - basic("minCountError", focusNode, attempt, s"MinCount violation. Expected $minCount, obtained: $count") - - def maxCountError(focusNode: RDFNode, attempt: Attempt, maxCount: Int, count: Int) = - basic("maxCountError", focusNode, attempt, s"MaxCount violation. Expected $maxCount, obtained: $count") - - def iriKindError(focusNode: RDFNode, attempt: Attempt) = - basic("iriKindError", focusNode, attempt, s"Node $focusNode is not an IRI") - - def literalKindError(focusNode: RDFNode, attempt: Attempt) = - basic("literalKindError", focusNode, attempt, s"Node $focusNode is not a Literal") - - def bNodeKindError(focusNode: RDFNode, attempt: Attempt) = - basic("bNodeKindError", focusNode, attempt, s"Node $focusNode is not a blank node") - - def bNodeOrIRIKindError(focusNode: RDFNode, attempt: Attempt) = - basic("bNodeOrIRIKindError", focusNode, attempt, s"Node $focusNode is not a blank node or an IRI") - - def bNodeOrLiteralKindError(focusNode: RDFNode, attempt: Attempt) = - basic("bNodeOrLiteralKindError", focusNode, attempt, s"Node $focusNode is not a blank node or a Literal") - - def iriOrLiteralKindError(focusNode: RDFNode, attempt: Attempt) = - basic("iriOrLiteralKindError", focusNode, attempt, s"Node $focusNode is not a IRI or a Literal") - - def notError(focusNode: RDFNode, attempt: Attempt, shape: ShapeRef) = - basic("notError", focusNode, attempt, s"Not violation. Expected $focusNode not to satisfy ${shape.showId}") - - def andError(focusNode: RDFNode, attempt: Attempt, shapes: List[ShapeRef]) = - basic("andError", 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]) = - basic("orError", focusNode, attempt, s"Or violation. Expected $focusNode to satisfy some of the shapes ${shapes.map(_.showId).mkString(",")}") - -/* def xoneErrorNone(focusNode: RDFNode, attempt: Attempt, shapes: List[ShapeRef]) = - basic("xoneError", focusNode, attempt, s"Xone violation. Expected $focusNode to satisfy one and only one of the shapes ${shapes.map(_.showId).mkString(",")} but none satisfied") - - def xoneErrorMoreThanOne(focusNode: RDFNode, attempt: Attempt, shapes: List[ShapeRef])(ls: List[ShapeTyping]) = - basic("xoneError", focusNode, attempt, s"Xone violation. Expected $focusNode to satisfy one and only one of the shapes ${shapes.map(_.showId).mkString(",")} but more than one satisfied: $ls") */ - - def xoneError(focusNode: RDFNode, attempt: Attempt, shapes: Seq[ShapeRef]) = - basic("xoneError", 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]) = - basic("qualifiedShapeError", focusNode, attempt, s"qualified shape error. Expected $focusNode to satisfy qualifiedValueShape. Value = ${value}, min: ${min.map(_.toString).getOrElse("-")}, max: ${max.map(_.toString).getOrElse("-")}") - - def hasValueError(focusNode: RDFNode, attempt: Attempt, value: Value) = - basic("hasValueError", focusNode, attempt, s"HasValue error. Expected $focusNode to be $value") - - def hasValueErrorNoValue(focusNode: RDFNode, attempt: Attempt, value: Value, path: SHACLPath) = - basic("hasValueError", focusNode, attempt, s"HasValue error. Missing value for path $path on $focusNode. Value must be $value") - - def hasValueErrorMoreThanOne(focusNode: RDFNode, attempt: Attempt, value: Value, path: SHACLPath, n: Int) = - basic("hasValueError", focusNode, attempt, s"HasValue error. More than one value ($n) for path $path on $focusNode. Value must be $value") - - def inError(focusNode: RDFNode, attempt: Attempt, values: Seq[Value]) = - basic("inError", focusNode, attempt, s"In violation. Expected $focusNode to be in $values") - - def notShapeError(focusNode: RDFNode, shapeRef: ShapeRef, attempt: Attempt) = - basic("notShape", focusNode, attempt, s"Not failed because $focusNode has shape $shapeRef and it should not have") - - def closedError( - focusNode: RDFNode, - attempt: Attempt, - allowedProperties: List[IRI], - ignoredProperties: List[IRI], - notAllowed: List[IRI]) = - basic("closedError", focusNode, attempt, - s"closed violation. $focusNode has more properties than $allowedProperties. Extra properties found: $notAllowed, ignoredProperties: $ignoredProperties") - -} 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 8a17e71f..e1162a22 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/AbstractSyntaxTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/AbstractSyntaxTest.scala @@ -2,26 +2,12 @@ package es.weso.shacl import org.scalatest._ import es.weso.rdf.nodes._ -import es.weso.rdf.path.PredicatePath class AbstractSyntaxTest extends FunSpec with Matchers { describe("Abstract Syntax") { it("should be able to create a shape") { val x = BNode("x") - val c: PropertyShape = - PropertyShape( - id = x, - path = PredicatePath(IRI("http://example.org/p")), - components = - List( - NodeKind(IRIKind), - MinCount(1), - MaxCount(2)), - targets = Seq(), - propertyShapes = Seq(), - closed = false, - ignoredProperties = List()) val id = IRI("http://example.org/s") val shape = NodeShape( id = 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 662299e4..637a55c0 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/RDF2ShaclTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/RDF2ShaclTest.scala @@ -3,7 +3,6 @@ package es.weso.shacl import org.scalatest._ import es.weso.rdf.nodes._ import es.weso.rdf.jena.RDFAsJenaModel -import es.weso.rdf._ import es.weso.rdf.path.{ InversePath, PredicatePath } import es.weso.shacl.converter.RDF2Shacl @@ -46,7 +45,6 @@ class RDF2ShaclTest extends FunSpec with Matchers with TryValues with EitherValu |:T a sh:Shape . |""".stripMargin val s = ex + "S" - val t = ex + "T" val n1 = ex + "n1" val attempt = for { rdf <- RDFAsJenaModel.fromChars(str, "TURTLE") @@ -100,7 +98,6 @@ class RDF2ShaclTest extends FunSpec with Matchers with TryValues with EitherValu | sh:nodeKind sh:IRI . |""".stripMargin val S = ex + "S" - val p = ex + "p" val prop = ex + "prop" val attempt = for { rdf <- RDFAsJenaModel.fromChars(str, "TURTLE") @@ -153,7 +150,7 @@ class RDF2ShaclTest extends FunSpec with Matchers with TryValues with EitherValu pc.predicate should be(p) pc.components should contain only (NodeKind(IRIKind), MinCount(1), MaxCount(1)) } - case other => fail("Failed with $other") + case other => fail(s"Failed with $other") } } } diff --git a/modules/shacl/src/test/scala/es/weso/shacl/ShaclCoreTest.scala b/modules/shacl/src/test/scala/es/weso/shacl/ShaclCoreTest.scala index 18956604..7f4435a9 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/ShaclCoreTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/ShaclCoreTest.scala @@ -1,20 +1,12 @@ package es.weso.shacl import com.typesafe.config.{Config, ConfigFactory} -import java.io.File import java.nio.file.Paths - import org.scalatest._ -import es.weso.rdf.nodes._ import es.weso.rdf.jena.RDFAsJenaModel import es.weso.rdf._ - -import scala.io.Source import util._ -import es.weso.utils.FileUtils._ import es.weso.manifest.{Entry => ManifestEntry, Result => ManifestResult, _} -import java.net._ - import es.weso.shacl.converter.RDF2Shacl import es.weso.shacl.validator.Validator diff --git a/modules/shex/src/main/scala/es/weso/shex/shex.scala b/modules/shex/src/main/scala/es/weso/shex/shex.scala index 074f4ee0..d347c852 100644 --- a/modules/shex/src/main/scala/es/weso/shex/shex.scala +++ b/modules/shex/src/main/scala/es/weso/shex/shex.scala @@ -443,7 +443,7 @@ object Schema { } } - def serialize(schema: Schema, format: String): Either[String,String] = { + def serialize(schema: Schema, format: String, rdfBuilder: RDFBuilder): Either[String,String] = { val formatUpperCase = format.toUpperCase formatUpperCase match { case "SHEXC" => { @@ -456,8 +456,7 @@ object Schema { Right(schema.asJson.spaces2) } case _ if (rdfDataFormats.contains(formatUpperCase)) => { - val model = ShEx2RDF.shEx2Model(schema, None) - val rdf = RDFAsJenaModel(model) + val rdf = ShEx2RDF(schema, None, rdfBuilder.empty) rdf.serialize(formatUpperCase) } case _ => diff --git a/modules/shex/src/main/scala/es/weso/shex/shexR/ShEx2RDF.scala b/modules/shex/src/main/scala/es/weso/shex/shexR/ShEx2RDF.scala index 6f37203a..32c241e3 100644 --- a/modules/shex/src/main/scala/es/weso/shex/shexR/ShEx2RDF.scala +++ b/modules/shex/src/main/scala/es/weso/shex/shexR/ShEx2RDF.scala @@ -4,24 +4,24 @@ import es.weso.shex._ import PREFIXES._ import cats.implicits._ import com.typesafe.scalalogging.LazyLogging -import es.weso.rdf.jena._ import es.weso.rdf.nodes._ import es.weso.rdf.PREFIXES._ -import org.apache.jena.rdf.model.Model +import es.weso.rdf.RDFBuilder +import es.weso.rdf.saver.RDFSaver trait ShEx2RDF extends RDFSaver with LazyLogging { - def serialize(shex: Schema, node: Option[IRI], format: String): Either[String,String] = { - val rdf: RDFAsJenaModel = toRDF(shex, node, RDFAsJenaModel.empty) + def serialize(shex: Schema, node: Option[IRI], format: String, rdfBuilder: RDFBuilder): Either[String,String] = { + val rdf: RDFBuilder = toRDF(shex, node, rdfBuilder) rdf.serialize(format) } - def toRDF(s: Schema, node: Option[IRI], initial: RDFAsJenaModel): RDFAsJenaModel = { + def toRDF(s: Schema, node: Option[IRI], initial: RDFBuilder): RDFBuilder = { val result = schema(s, node).run(initial) result.value._1 } - def schema(s: Schema, id: Option[IRI]): RDFSaver[Unit] = { + private def schema(s: Schema, id: Option[IRI]): RDFSaver[Unit] = { for { node <- makeId(id) _ <- addPrefixMap(s.prefixMap) @@ -32,7 +32,7 @@ trait ShEx2RDF extends RDFSaver with LazyLogging { } yield () } - def shapeExpr(e: ShapeExpr): RDFSaver[RDFNode] = e match { + private def shapeExpr(e: ShapeExpr): RDFSaver[RDFNode] = e match { case ShapeAnd(id, shapeExprs) => for { node <- mkId(id) @@ -79,7 +79,7 @@ trait ShEx2RDF extends RDFSaver with LazyLogging { case ShapeRef(lbl) => label(lbl) } - def xsFacet(facet: XsFacet, node: RDFNode): RDFSaver[Unit] = facet match { + private def xsFacet(facet: XsFacet, node: RDFNode): RDFSaver[Unit] = facet match { case Length(n) => addTriple(node, sx_length, IntegerLiteral(n)) case MinLength(n) => addTriple(node, sx_minlength, IntegerLiteral(n)) case MaxLength(n) => addTriple(node, sx_maxlength, IntegerLiteral(n)) @@ -95,13 +95,13 @@ trait ShEx2RDF extends RDFSaver with LazyLogging { case TotalDigits(n) => addTriple(node, sx_totaldigits, IntegerLiteral(n)) } - def numericLiteral(n: NumericLiteral): RDFSaver[RDFNode] = n match { + private def numericLiteral(n: NumericLiteral): RDFSaver[RDFNode] = n match { case NumericInt(n) => ok(IntegerLiteral(n)) case NumericDouble(n,_) => ok(DoubleLiteral(n)) case NumericDecimal(n,_) => ok(DecimalLiteral(n)) } - def valueSetValue(x: ValueSetValue): RDFSaver[RDFNode] = x match { + private def valueSetValue(x: ValueSetValue): RDFSaver[RDFNode] = x match { case IRIValue(iri) => ok(iri) case StringValue(s) => ok(StringLiteral(s)) case DatatypeString(s, iri) => ok(DatatypeLiteral(s, iri)) @@ -115,48 +115,47 @@ trait ShEx2RDF extends RDFSaver with LazyLogging { case l: LiteralStemRange => literalStemRange(l) } - - def iriExclusion(iri: IRIExclusion): RDFSaver[RDFNode] = iri match { + private def iriExclusion(iri: IRIExclusion): RDFSaver[RDFNode] = iri match { case IRIRefExclusion(iri) => ok(iri) case IRIStemExclusion(stem) => iriStem(stem) } - def literalExclusion(le: LiteralExclusion): RDFSaver[RDFNode] = le match { + private def literalExclusion(le: LiteralExclusion): RDFSaver[RDFNode] = le match { case LiteralStringExclusion(str) => ok(StringLiteral(str)) case LiteralStemExclusion(stem) => literalStem(stem) } - def languageExclusion(le: LanguageExclusion): RDFSaver[RDFNode] = le match { + private def languageExclusion(le: LanguageExclusion): RDFSaver[RDFNode] = le match { case LanguageTagExclusion(Lang(str)) => ok(StringLiteral(str)) case LanguageStemExclusion(stem) => languageStem(stem) } - def iriStem(x: IRIStem): RDFSaver[RDFNode] = for { + private def iriStem(x: IRIStem): RDFSaver[RDFNode] = for { node <- createBNode() _ <- addTriple(node, rdf_type, sx_IriStem) _ <- addTriple(node, sx_stem, DatatypeLiteral(x.stem.str, xsd_anyUri)) } yield node - def languageStem(x: LanguageStem): RDFSaver[RDFNode] = for { + private def languageStem(x: LanguageStem): RDFSaver[RDFNode] = for { node <- createBNode() _ <- addTriple(node, rdf_type, sx_LanguageStem) _ <- addTriple(node, sx_stem, StringLiteral(x.stem.lang)) } yield node - def literalStem(x: LiteralStem): RDFSaver[RDFNode] = for { + private def literalStem(x: LiteralStem): RDFSaver[RDFNode] = for { node <- createBNode() _ <- addTriple(node, rdf_type, sx_LiteralStem) _ <- addTriple(node, sx_stem, StringLiteral(x.stem)) } yield node - def iriStemRange(range: IRIStemRange): RDFSaver[RDFNode] = for { + private def iriStemRange(range: IRIStemRange): RDFSaver[RDFNode] = for { node <- createBNode() _ <- addTriple(node, rdf_type, sx_IriStemRange) _ <- addContent(range.stem, node, sx_stem, iriStemRangeValue) _ <- maybeAddStarContent(range.exclusions, node, sx_exclusion, iriExclusion) } yield node - def iriStemRangeValue(x: IRIStemRangeValue): RDFSaver[RDFNode] = x match { + private def iriStemRangeValue(x: IRIStemRangeValue): RDFSaver[RDFNode] = x match { case IRIStemValueIRI(iri) => ok(DatatypeLiteral(iri.str, xsd_anyUri)) case IRIStemWildcard() => for { node <- createBNode() @@ -164,14 +163,14 @@ trait ShEx2RDF extends RDFSaver with LazyLogging { } yield node } - def literalStemRange(range: LiteralStemRange): RDFSaver[RDFNode] = for { + private def literalStemRange(range: LiteralStemRange): RDFSaver[RDFNode] = for { node <- createBNode() _ <- addTriple(node, rdf_type, sx_LiteralStemRange) _ <- addContent(range.stem, node, sx_stem, literalStemRangeValue) _ <- maybeAddStarContent(range.exclusions, node, sx_exclusion, literalExclusion) } yield node - def literalStemRangeValue(x: LiteralStemRangeValue): RDFSaver[RDFNode] = x match { + private def literalStemRangeValue(x: LiteralStemRangeValue): RDFSaver[RDFNode] = x match { case LiteralStemRangeString(str) => ok(StringLiteral(str)) case LiteralStemRangeWildcard() => for { node <- createBNode() @@ -179,14 +178,14 @@ trait ShEx2RDF extends RDFSaver with LazyLogging { } yield node } - def languageStemRange(range: LanguageStemRange): RDFSaver[RDFNode] = for { + private def languageStemRange(range: LanguageStemRange): RDFSaver[RDFNode] = for { node <- createBNode() _ <- addTriple(node, rdf_type, sx_LanguageStemRange) _ <- addContent(range.stem, node, sx_stem, languageStemRangeValue) _ <- maybeAddStarContent(range.exclusions, node, sx_exclusion, languageExclusion) } yield node - def languageStemRangeValue(x: LanguageStemRangeValue): RDFSaver[RDFNode] = x match { + private def languageStemRangeValue(x: LanguageStemRangeValue): RDFSaver[RDFNode] = x match { case LanguageStemRangeLang(Lang(lang)) => ok(StringLiteral(lang)) case LanguageStemRangeWildcard() => for { node <- createBNode() @@ -194,7 +193,7 @@ trait ShEx2RDF extends RDFSaver with LazyLogging { } yield node } - def tripleExpr(te: TripleExpr): RDFSaver[RDFNode] = te match { + private def tripleExpr(te: TripleExpr): RDFSaver[RDFNode] = te match { case TripleConstraint(id, inverse, negated, pred, valueExpr, min, max, semActs, annotations) => for { teId <- mkId(id) _ <- addTriple(teId, rdf_type, sx_TripleConstraint) @@ -228,38 +227,38 @@ trait ShEx2RDF extends RDFSaver with LazyLogging { case Inclusion(lbl) => label(lbl) } - def semAct(x: SemAct): RDFSaver[RDFNode] = for { + private def semAct(x: SemAct): RDFSaver[RDFNode] = for { id <- createBNode() _ <- addTriple(id, rdf_type, sx_SemAct) _ <- addTriple(id, sx_name, x.name) _ <- maybeAddContent(x.code, id, sx_code, rdfString) } yield id - def rdfMax(x: Max): RDFSaver[RDFNode] = x match { + private def rdfMax(x: Max): RDFSaver[RDFNode] = x match { case IntMax(n) => rdfInt(n) case Star => ok(sx_INF) } - def annotation(x: Annotation): RDFSaver[RDFNode] = for { + private def annotation(x: Annotation): RDFSaver[RDFNode] = for { id <- createBNode() _ <- addTriple(id, rdf_type, sx_Annotation) _ <- addTriple(id, sx_predicate, x.predicate) _ <- addContent(x.obj, id, sx_object, objectValue) } yield id - def objectValue(x: ObjectValue): RDFSaver[RDFNode] = x match { + private def objectValue(x: ObjectValue): RDFSaver[RDFNode] = x match { case IRIValue(iri) => ok(iri) case StringValue(s) => ok(StringLiteral(s)) case DatatypeString(s, iri) => ok(DatatypeLiteral(s, iri)) case LangString(s, lang) => ok(LangLiteral(s, lang)) } - def label(lbl: ShapeLabel): RDFSaver[RDFNode] = lbl match { + private def label(lbl: ShapeLabel): RDFSaver[RDFNode] = lbl match { case IRILabel(iri) => ok(iri) case BNodeLabel(bnode) => ok(bnode) } - def nodeKind(nk: NodeKind): RDFSaver[RDFNode] = + private def nodeKind(nk: NodeKind): RDFSaver[RDFNode] = nk match { case IRIKind => ok(sx_iri) case BNodeKind => ok(sx_bnode) @@ -267,7 +266,7 @@ trait ShEx2RDF extends RDFSaver with LazyLogging { case NonLiteralKind => ok(sx_nonliteral) } - def mkId(id: Option[ShapeLabel]): RDFSaver[RDFNode] = id match { + private def mkId(id: Option[ShapeLabel]): RDFSaver[RDFNode] = id match { case None => createBNode case Some(IRILabel(iri)) => ok(iri) case Some(BNodeLabel(bNode)) => ok(bNode) @@ -277,9 +276,9 @@ trait ShEx2RDF extends RDFSaver with LazyLogging { object ShEx2RDF { - def shEx2Model(s: Schema, n: Option[IRI]): Model = { + def apply(s: Schema, n: Option[IRI], builder: RDFBuilder): RDFBuilder = { val srdf = new ShEx2RDF {} - srdf.toRDF(s, n, RDFAsJenaModel.empty).model + srdf.toRDF(s, n, builder) } } \ No newline at end of file diff --git a/modules/shex/src/test/scala/es/weso/shex/shexR/RDF2ShexTest.scala b/modules/shex/src/test/scala/es/weso/shex/shexR/RDF2ShexTest.scala index b194f11b..890fc243 100644 --- a/modules/shex/src/test/scala/es/weso/shex/shexR/RDF2ShexTest.scala +++ b/modules/shex/src/test/scala/es/weso/shex/shexR/RDF2ShexTest.scala @@ -41,12 +41,13 @@ class RDF2ShExTest extends FunSpec with Matchers with EitherValues with TryValue result match { case Right(schema) => { - val model1 = ShEx2RDF.shEx2Model(schema, Some(IRI("http://example.org/x"))) - val model2 = ShEx2RDF.shEx2Model(expected, Some(IRI("http://example.org/x"))) - if (model1.isIsomorphicWith(model2)) { + val builder = RDFAsJenaModel.empty + val rdf1 = ShEx2RDF(schema, Some(IRI("http://example.org/x")),builder) + val rdf2 = ShEx2RDF(expected, Some(IRI("http://example.org/x")),builder) + if (rdf1.isIsomorphicWith(rdf2).getOrElse(false)) { info(s"Models are isomorphic") } else { - info(s"Schema obtained: ${model1}\nSchema expected: ${model2} are not isomorphic") + info(s"Schema obtained: ${rdf1.serialize("TURTLE")}\nSchema expected: ${rdf2.serialize("TURTLE")} are not isomorphic") fail("Schemas are not isomorphic") } } 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 c3d3bdce..74ebd237 100644 --- a/modules/srdf/src/main/scala/es/weso/rdf/RDFBuilder.scala +++ b/modules/srdf/src/main/scala/es/weso/rdf/RDFBuilder.scala @@ -4,7 +4,7 @@ import es.weso.rdf.triples._ import es.weso.rdf.nodes._ import PREFIXES._ -trait RDFBuilder { +trait RDFBuilder extends RDFReader { type Rdf <: RDFBuilder @@ -14,6 +14,8 @@ trait RDFBuilder { def createBNode: (RDFNode, Rdf) + def mkBNode: Either[String, (RDFNode, Rdf)] = Right(createBNode) + def addTriples(triples: Set[RDFTriple]): Either[String,Rdf] def addTriple(triple: RDFTriple): 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 e679830c..dfcff99b 100644 --- a/modules/srdf/src/main/scala/es/weso/rdf/RDFReader.scala +++ b/modules/srdf/src/main/scala/es/weso/rdf/RDFReader.scala @@ -170,5 +170,7 @@ trait RDFReader { def getNumberOfStatements(): Either[String,Int] + def isIsomorphicWith(other: RDFReader): Either[String,Boolean] + } diff --git a/modules/srdfJena/src/main/scala/es/weso/rdf/jena/RDFSaver.scala b/modules/srdf/src/main/scala/es/weso/rdf/saver/RDFSaver.scala similarity index 93% rename from modules/srdfJena/src/main/scala/es/weso/rdf/jena/RDFSaver.scala rename to modules/srdf/src/main/scala/es/weso/rdf/saver/RDFSaver.scala index 7e915814..1d5dd05a 100644 --- a/modules/srdfJena/src/main/scala/es/weso/rdf/jena/RDFSaver.scala +++ b/modules/srdf/src/main/scala/es/weso/rdf/saver/RDFSaver.scala @@ -1,14 +1,14 @@ -package es.weso.rdf.jena +package es.weso.rdf.saver -import cats.data.{ State, _ } +import cats.data.{State, _} import cats.implicits._ -import es.weso.rdf.PrefixMap -import es.weso.rdf.PREFIXES.{ rdf_first, rdf_nil, rdf_rest} +import es.weso.rdf.PREFIXES.{rdf_first, rdf_nil, rdf_rest} import es.weso.rdf.nodes._ import es.weso.rdf.triples.RDFTriple +import es.weso.rdf.{PrefixMap, RDFBuilder} trait RDFSaver { - type RDFSaver[A] = State[RDFAsJenaModel, A] + type RDFSaver[A] = State[RDFBuilder, A] def ok[A](x: A): RDFSaver[A] = StateT.pure(x) @@ -38,9 +38,9 @@ trait RDFSaver { State.modify(_.addPrefix(alias, value)) def createBNode(): RDFSaver[RDFNode] = for { - rdf <- State.get[RDFAsJenaModel] + rdf <- State.get[RDFBuilder] (bNode, newRdf) = rdf.createBNode - _ <- State.set[RDFAsJenaModel](newRdf) + _ <- State.set[RDFBuilder](newRdf) } yield bNode def makeId(v: Option[IRI]): RDFSaver[RDFNode] = v match { 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 d051d2b9..4d6ba7f8 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 @@ -8,7 +8,7 @@ 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 org.eclipse.rdf4j.model.util.{ModelBuilder, Statements} +import org.eclipse.rdf4j.model.util.{ModelBuilder, Models} import org.eclipse.rdf4j.rio.RDFFormat._ import org.eclipse.rdf4j.rio.{RDFFormat, Rio} import org.apache.commons.io.input.CharSequenceInputStream @@ -16,7 +16,6 @@ import org.apache.commons.io.input.CharSequenceInputStream import scala.util._ import scala.collection.JavaConverters._ import RDF4jMapper._ -import cats._ import cats.implicits._ case class RDFAsRDF4jModel(model: Model) @@ -215,7 +214,7 @@ case class RDFAsRDF4jModel(model: Model) */ } - def availableInferenceEngines: List[String] = List(NONE, RDFS, OWL) + override def availableInferenceEngines: List[String] = List(NONE, RDFS, OWL) override def querySelect(queryStr: String): Either[String, List[Map[String,RDFNode]]] = Left(s"Not implemented querySelect for RDf4j yet") @@ -223,11 +222,14 @@ case class RDFAsRDF4jModel(model: Model) override def queryAsJson(queryStr: String): Either[String, Json] = Left(s"Not implemented queryAsJson for RDf4j") - - override def getNumberOfStatements(): Either[String,Int] = Right(model.size) + def isIsomorphicWith(other: RDFReader): Either[String,Boolean] = other match { + case o: RDFAsRDF4jModel => Right(Models.isomorphic(model,o.model)) + case _ => Left(s"Cannot compare RDFAsJenaModel with reader of different type: ${other.getClass.toString}") + } + } 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 961243a8..195093a8 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 @@ -160,7 +160,7 @@ case class Endpoint(endpoint: String) extends RDFReader with RDFReasoner { } } - def availableInferenceEngines: List[String] = List("NONE") + override def availableInferenceEngines: List[String] = List("NONE") override def querySelect(queryStr: String): Either[String, List[Map[String,RDFNode]]] = { val query = QueryFactory.create(queryStr) @@ -168,7 +168,7 @@ case class Endpoint(endpoint: String) extends RDFReader with RDFReasoner { qExec.getQuery.getQueryType match { case Query.QueryTypeSelect => { val result = qExec.execSelect() - val varNames = result.getResultVars + // val varNames = result.getResultVars val ls: List[Map[String,RDFNode]] = result.asScala.toList.map(qs => { val qsm = new QuerySolutionMap() qsm.addAll(qs) @@ -192,7 +192,7 @@ case class Endpoint(endpoint: String) extends RDFReader with RDFReasoner { parse(jsonStr).leftMap(f => f.getMessage) } case Query.QueryTypeConstruct => { - val result = qExec.execConstruct() + // val result = qExec.execConstruct() Left(s"Unimplemented CONSTRUCT queries yet") } case Query.QueryTypeAsk => { @@ -208,14 +208,15 @@ case class Endpoint(endpoint: String) extends RDFReader with RDFReasoner { } }.toEither.fold(f => Left(f.getMessage), es => es) - def getNumberOfStatements(): Either[String,Int] = { + override def getNumberOfStatements(): Either[String,Int] = { Try{ val resultSet = QueryExecutionFactory.sparqlService(endpoint, countStatements).execSelect() resultSet.asScala.map(qs => qs.get("c").asLiteral().getInt).toList.head }.toEither.leftMap(_.getMessage) } - + override def isIsomorphicWith(other: RDFReader): Either[String,Boolean] = + Left(s"Unimplemented isIsomorphicWith between endpoints") } diff --git a/modules/srdfJena/src/main/scala/es/weso/rdf/jena/JenaMapper.scala b/modules/srdfJena/src/main/scala/es/weso/rdf/jena/JenaMapper.scala index 51c41bb6..1cf3ca5a 100644 --- a/modules/srdfJena/src/main/scala/es/weso/rdf/jena/JenaMapper.scala +++ b/modules/srdfJena/src/main/scala/es/weso/rdf/jena/JenaMapper.scala @@ -70,31 +70,14 @@ object JenaMapper { def rdfNode2JenaNode(n: RDFNode, m: JenaModel): JenaRDFNode = createRDFNode(m, n) - /*{ - n match { - case i: IRI => m.getResource(i.str) - case BNodeId(id) => { - // Creates the BNode if it doesn't exist - m.createResource(new AnonId(id)) - } - case IntegerLiteral(n) => { - val i: Integer = n - m.createTypedLiteral(i) - } - case DecimalLiteral(d) => m.createTypedLiteral(d) - // case BooleanLiteral(b) => m.createLiteral(b) - case LangLiteral(str, Lang(lang)) => m.createLiteral(str, lang) - case _ => throw new Exception("rdfNode2JenaNode: unexpected node " + n) - } - } */ // TODO: Change this code to return an Either[String,RDFNode] def jenaNode2RDFNode(r: JenaRDFNode): RDFNode = { if (r.isURIResource()) { - // println(s"jenaNode2RDFNode: URI = ${r.asResource().getURI()}") IRI(r.asResource().getURI) } else if (r.isAnon) { - BNode(r.asResource().getId.getLabelString) + val b = BNode(r.asResource().getId.getLabelString) + b } else if (r.isLiteral) { val lit = r.asLiteral() if (lit.getLanguage() != "") { 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 8408ba35..78fad359 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 @@ -300,9 +300,14 @@ case class RDFAsJenaModel(model: Model) } }.toEither.fold(f => Left(f.getMessage), es => es) - def getNumberOfStatements(): Either[String,Int] = + override def getNumberOfStatements(): Either[String,Int] = Right(model.size.toInt) + override def isIsomorphicWith(other: RDFReader): Either[String,Boolean] = other match { + case o: RDFAsJenaModel => Right(model.isIsomorphicWith(o.model)) + case _ => Left(s"Cannot compare RDFAsJenaModel with reader of different type: ${other.getClass.toString}") + } + } 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 c084b82c..47f455be 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 @@ -146,6 +146,9 @@ case class RDFFromWeb() extends RDFReader { override def querySelect(queryStr: String): Either[String, List[Map[String,RDFNode]]] = Left(s"Unimplemented query on RDFFromWeb") override def queryAsJson(queryStr: String): Either[String, Json] = Left(s"Unimplemented query on RDFFromWeb") - def getNumberOfStatements(): Either[String,Int] = Left(s"Unimplemented number of statements of endpoint") + override def getNumberOfStatements(): Either[String,Int] = Left(s"Unimplemented number of statements of endpoint") + + override def isIsomorphicWith(other: RDFReader) = Left(s"Unimplemented isomorphic test in RDFFromWeb") + } \ No newline at end of file diff --git a/modules/srdfJena/src/main/scala/es/weso/utils/JenaUtils.scala b/modules/srdfJena/src/main/scala/es/weso/utils/JenaUtils.scala index de2a8d58..5a38e4e5 100644 --- a/modules/srdfJena/src/main/scala/es/weso/utils/JenaUtils.scala +++ b/modules/srdfJena/src/main/scala/es/weso/utils/JenaUtils.scala @@ -1,9 +1,7 @@ package es.weso.utils -import org.apache.jena.rdf.model.Model -import org.apache.jena.rdf.model.Resource +import org.apache.jena.rdf.model.{Literal, Model, ModelFactory, Property, RDFNode, Resource, SimpleSelector} import org.apache.jena.sparql.syntax.ElementPathBlock -import org.apache.jena.rdf.model.ModelFactory import org.apache.jena.riot.system.IRIResolver import java.io.ByteArrayInputStream @@ -11,9 +9,6 @@ import org.apache.jena.query.Query import org.apache.jena.query.QueryExecutionFactory import org.apache.jena.query.QueryFactory import java.io.StringWriter - -import org.apache.jena.rdf.model.RDFNode -import org.apache.jena.rdf.model.Property import java.net.URI import java.net.URL import java.io.InputStream @@ -22,22 +17,14 @@ import java.io.FileOutputStream import org.apache.jena.atlas.AtlasException import org.apache.jena.riot.RiotException import org.apache.jena.query.ResultSet -import org.apache.jena.rdf.model.Literal import scala.collection.JavaConverters._ -import org.apache.jena.query.ParameterizedSparqlString import org.apache.jena.reasoner.ReasonerRegistry import org.apache.jena.sparql.core.{TriplePath, Var} import org.apache.jena.sparql.path.Path import org.apache.jena.util.{FileUtils => FileJenaUtils} -sealed abstract class ParserReport[+A, +B] - -final case class Parsed[A](info: A) - extends ParserReport[A, Nothing] - -final case class NotParsed[B](error: B) - extends ParserReport[Nothing, B] +import scala.annotation.tailrec object JenaUtils { @@ -49,7 +36,7 @@ object JenaUtils { lazy val N3 = "N3" // In Jena selectors, null represents any node - lazy val any: RDFNode = null + lazy val any: Resource = null def extractModel(resource: Resource, model: Model): Model = { val nModel = ModelFactory.createDefaultModel() @@ -72,29 +59,6 @@ object JenaUtils { inner(resource) } - /*def statementAsString(statement: Statement, model: Model, preffix: Boolean): String = { - val resource = try { - val uri = statement.getResource.toString - val preffixUri = statement.getResource.getNameSpace - val preffixNS = model.getNsURIPrefix(statement.getResource.getNameSpace) - val suffix = statement.getResource.getLocalName - if (preffix && preffixUri != null) - preffixNS + ":" + suffix - else uri - } catch { - case _: ResourceRequiredException => null - } - if (resource == null) { - try { - if (preffix) - statement.getLiteral().getValue().toString - else statement.getLiteral().toString - } catch { - case _: LiteralRequiredException => null - } - } else resource - } */ - def dereferenceURI(uri: String): InputStream = { val url = new URL(uri) val urlCon = url.openConnection() @@ -237,26 +201,100 @@ object JenaUtils { * Given a class `cls`, obtains all nodes such as `node rdf:type/rdfs:subClassOf* cls` */ def getSHACLInstances(cls: RDFNode, model: Model): Seq[RDFNode] = { + /* val pss: ParameterizedSparqlString = new ParameterizedSparqlString() pss.setNsPrefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#") pss.setNsPrefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#") pss.setCommandText("SELECT ?n { ?n rdf:type/rdfs:subClassOf* ?c . }") pss.setParam("c", cls) val result = QueryExecutionFactory.create(pss.asQuery, model).execSelect - result.asScala.toSeq.map(qs => qs.get("n")) + result.asScala.toSeq.map(qs => qs.get("n")) */ + (getDirectInstances(cls,model) ++ + getAllSubClasses(cls,model).map(c => getDirectInstances(c,model)).flatten).toSeq } /** * Checks is a `node rdf:type/rdfs:subClassOf* cls` */ def hasClass(n: RDFNode, c: RDFNode, model: Model): Boolean = { - val pss: ParameterizedSparqlString = new ParameterizedSparqlString() - pss.setNsPrefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#") - pss.setNsPrefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#") - pss.setCommandText("ASK { ?n rdf:type/rdfs:subClassOf* ?c . }") - pss.setParam("n", n) - pss.setParam("c", c) - QueryExecutionFactory.create(pss.asQuery, model).execAsk + getSHACLTypes(n, model).exists(sameNodeAs(_,c)) + } + + lazy val rdfTypeUrl = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" + lazy val subClassOfUrl = "http://www.w3.org/2000/01/rdf-schema#subClassOf" + + def getSHACLTypes(n: RDFNode, model: Model): Set[RDFNode] = { + extendWithSuperClasses(getDirectTypes(n,model).toList,model) + } + + def getDirectTypes(n: RDFNode, model: Model): Set[RDFNode] = { + if (n.isResource) { + val rdfType = model.getProperty(rdfTypeUrl) + val selector = new SimpleSelector(n.asResource(), rdfType, any) + model.listStatements(selector).asScala.map(_.getObject).toSet + } else Set() + } + + def getDirectInstances(n: RDFNode, model: Model): Set[RDFNode] = { + val rdfType = model.getProperty(rdfTypeUrl) + val selector = new SimpleSelector(any, rdfType, n) + model.listStatements(selector).asScala.map(_.getSubject).toSet + } + + def getSuperClasses(c: RDFNode, model: Model): Set[RDFNode] = { + if (c.isResource) { + val subClassOf = model.getProperty(subClassOfUrl) + val selector = new SimpleSelector(c.asResource(), subClassOf, any) + model.listStatements(selector).asScala.map(_.getObject).toSet + } else Set() + } + + private def extendWithSuperClasses(types: List[RDFNode], model: Model): Set[RDFNode] = { + extendWithSuperClassesAux(types, model, Set[RDFNode]()) + } + + + private def getAllSubClasses(cls: RDFNode,model: Model): Set[RDFNode] = { + val directSubclasses = getDirectSubclasses(cls,model) + getAllSubClassesAux(directSubclasses.toList,model, Set()) + } + + private def getDirectSubclasses(cls: RDFNode, model: Model): Set[RDFNode] = { + val subClassOf = model.getProperty(subClassOfUrl) + val selector = new SimpleSelector(any, subClassOf, cls) + model.listStatements(selector).asScala.map(_.getSubject).toSet + } + + @tailrec + private def getAllSubClassesAux(cls: List[RDFNode], model: Model, visited: Set[RDFNode]): Set[RDFNode] = { + cls match { + case Nil => visited + case c :: cs => if (visited.contains(c)) + getAllSubClassesAux(cs,model,visited) + else + getAllSubClassesAux(getDirectSubclasses(c,model).toList,model,visited + c) + } + } + + @tailrec + private def extendWithSuperClassesAux(types: List[RDFNode], model: Model, visited: Set[RDFNode]): Set[RDFNode] = { + types match { + case Nil => visited + case t :: ts => + if (visited.contains(t)) + extendWithSuperClassesAux(ts, model, visited) + else + extendWithSuperClassesAux(getSuperClasses(t, model).toList ++ ts, model, visited + t) + } + } + + def sameNodeAs(v1: RDFNode, v2: RDFNode): Boolean = { + (v1,v2) match { + case (r1: Resource, r2: Resource) if r1.isURIResource && r2.isURIResource => r1.getURI == r2.getURI + case (r1:Resource,r2: Resource) if r1.isAnon && r2.isAnon => r1.getId == r2.getId + case (b1: Literal, b2: Literal) => b1.getLexicalForm == b2.getLexicalForm + case (_,_) => false + } } def getNodesFromPath(path: Path, model: Model): Seq[(RDFNode, RDFNode)] = { diff --git a/modules/srdfJena/src/main/scala/es/weso/utils/ParseReport.scala b/modules/srdfJena/src/main/scala/es/weso/utils/ParseReport.scala new file mode 100644 index 00000000..ed39d302 --- /dev/null +++ b/modules/srdfJena/src/main/scala/es/weso/utils/ParseReport.scala @@ -0,0 +1,9 @@ +package es.weso.utils + +sealed abstract class ParserReport[+A, +B] + +final case class Parsed[A](info: A) + extends ParserReport[A, Nothing] + +final case class NotParsed[B](error: B) + extends ParserReport[Nothing, B] diff --git a/modules/srdfJena/src/test/scala/es/weso/utils/JenaUtilsTest.scala b/modules/srdfJena/src/test/scala/es/weso/utils/JenaUtilsTest.scala index 81e5865d..71b2d1c6 100644 --- a/modules/srdfJena/src/test/scala/es/weso/utils/JenaUtilsTest.scala +++ b/modules/srdfJena/src/test/scala/es/weso/utils/JenaUtilsTest.scala @@ -1,13 +1,16 @@ package es.weso.utils -import org.apache.jena.sparql.path.{ P_Link, P_OneOrMoreN } +import es.weso.rdf.jena.JenaMapper +import es.weso.rdf.nodes.IRI +import es.weso.rdf.path.PredicatePath +import org.apache.jena.sparql.path.{P_Link, P_OneOrMoreN} import org.scalatest._ class JenaUtilsTest extends FunSpec with Matchers { describe("hasClass") { it("check hasClass") { - val ex = "http://example.org" + val ex = "http://example.org/" val rdfStr =s"""|@prefix : <$ex> . |@prefix rdfs: . |@prefix rdf: . @@ -50,17 +53,53 @@ class JenaUtilsTest extends FunSpec with Matchers { JenaUtils.hasClass(dog1, _Dog, model) should be(true) JenaUtils.hasClass(any, _Dog, model) should be(false) JenaUtils.hasClass(any, _Any, model) should be(false) - JenaUtils.hasClass(dog1, _Any, model) should be( - false) + JenaUtils.hasClass(dog1, _Any, model) should be(false) } case Left(msg) => fail(msg) } } + + it(s"Check hasClass with blank nodes") { + val rdfStr = + s"""|@prefix : + |_:x a :A, :B, _:C ; + | :x 1 . # To identify it as _:x + |_:y a :B, _:C ; + | :y 1 . + |:z a :B . + |_:C :c 1 . + """.stripMargin + JenaUtils.parseFromString(rdfStr) match { + case Right(model) => { + val ex = IRI("http://example.org/") + val px = JenaMapper.path2JenaPath(PredicatePath(ex + "x"), model) + val py = JenaMapper.path2JenaPath(PredicatePath(ex + "y"), model) + val pc = JenaMapper.path2JenaPath(PredicatePath(ex + "c"), model) + val bx = JenaUtils.getNodesFromPath(px, model).head._1 + val by = JenaUtils.getNodesFromPath(py, model).head._1 + + val a = JenaMapper.rdfNode2JenaNode(ex+"A",model) + val b = JenaMapper.rdfNode2JenaNode(ex+"B",model) + val z = JenaMapper.rdfNode2JenaNode(ex+"z",model) + val bc = JenaUtils.getNodesFromPath(pc, model).head._1 + + JenaUtils.hasClass(bx,a,model) should be(true) + JenaUtils.hasClass(bx,b,model) should be(true) + JenaUtils.hasClass(by,a,model) should be(false) + JenaUtils.hasClass(by,b,model) should be(true) + JenaUtils.hasClass(bx,bc,model) should be(true) + JenaUtils.hasClass(by,bc,model) should be(true) + JenaUtils.hasClass(z,bc,model) should be(false) + } + case Left(str) => fail(str) + } + + } } describe(s"HasSHACLInstances") { it(s"hasSHACLinstances should obtain SHACL instances") { - val ex = "http://example.org" + val ex = "http://example.org/" val rdfStr = s"""|@prefix : <$ex> . |@prefix rdfs: . |@prefix rdf: . @@ -77,7 +116,6 @@ class JenaUtilsTest extends FunSpec with Matchers { val teacher1 = model.createResource(ex + "teacher1") val teacher2 = model.createResource(ex + "teacher2") val dog1 = model.createResource(ex + "dog1") - val any = model.createResource(ex + "any") val _Person = model.createResource(ex + "Person") val _Teacher = model.createResource(ex + "Teacher") val _UniversityTeacher = model.createResource(ex + "UniversityTeacher") @@ -111,16 +149,13 @@ class JenaUtilsTest extends FunSpec with Matchers { val homer = model.createResource(ex + "homer") val herb = model.createResource(ex + "herb") val bart = model.createResource(ex + "bart") - val lisa = model.createResource(ex + "lisa") - val maggie =model.createResource(ex + "maggie") - + val lisa = model.createResource(ex + "lisa") + val maggie = model.createResource(ex + "maggie") val parent = model.createResource(ex + "parent") - val parent1 = new P_Link(parent.asNode) val parentPlus = new P_OneOrMoreN(parent1) JenaUtils.objectsFromPath(abraham, parent1, model) should contain only (herb, homer) JenaUtils.objectsFromPath(abraham, parentPlus, model) should contain only (bart, lisa, maggie, herb, homer - ) } case Left(msg) => fail(msg) diff --git a/modules/typing/src/main/scala/es/weso/typing/Typing.scala b/modules/typing/src/main/scala/es/weso/typing/Typing.scala index afcf4002..08f3b9c5 100644 --- a/modules/typing/src/main/scala/es/weso/typing/Typing.scala +++ b/modules/typing/src/main/scala/es/weso/typing/Typing.scala @@ -6,11 +6,15 @@ abstract class Typing[Key, Value, Err, Evidence] { type Evidences = List[Evidence] + def allOk: Boolean + def hasType(key: Key, value: Value): Boolean = getOkValues(key) contains value def getValues(key: Key): Map[Value, TypingResult[Err, Evidence]] + def getKeys: Seq[Key] + def getOkValues(key: Key): Set[Value] def getEvidences(key: Key, value: Value): Option[List[Evidence]] diff --git a/modules/typing/src/main/scala/es/weso/typing/TypingMap.scala b/modules/typing/src/main/scala/es/weso/typing/TypingMap.scala index 58799d59..699daecf 100644 --- a/modules/typing/src/main/scala/es/weso/typing/TypingMap.scala +++ b/modules/typing/src/main/scala/es/weso/typing/TypingMap.scala @@ -7,6 +7,13 @@ case class TypingMap[Key, Value, Err, Evidence]( m: Map[Key, Map[Value, TypingResult[Err, Evidence]]]) extends Typing[Key, Value, Err, Evidence] { + override def allOk: Boolean = { + m.keys.map(key => getFailedValues(key).isEmpty).forall(identity) + } + + override def getKeys: Seq[Key] = + m.keys.toSeq + override def getValues(key: Key): Map[Value, TypingResult[Err, Evidence]] = m.get(key).getOrElse(Map()) diff --git a/notes/0.0.69.md b/notes/0.0.69.md new file mode 100644 index 00000000..7074e828 --- /dev/null +++ b/notes/0.0.69.md @@ -0,0 +1,16 @@ +# New features + +- First version with support for SHACL style validation report [issue #60](https://github.com/labra/shaclex/issues/60) + +TODOs +----- + +- SHACL support using RDf4j (add SHACL paths to SRDF4j) + +- Check SHACL test-suite and create report + +- Check ShEx test-suite with shape maps and update report + +- Conversion from ShEx to SHACL + +- Conversion from SHACL to ShEx diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 00000000..e4982aa2 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,23 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + logs/shaclex.log + + + %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n + + + + + + + + + diff --git a/src/main/resources/logbackOld.xml b/src/main/resources/logbackOld.xml deleted file mode 100644 index b1f9bfe2..00000000 --- a/src/main/resources/logbackOld.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - diff --git a/src/main/scala/es/weso/shaclex/Main.scala b/src/main/scala/es/weso/shaclex/Main.scala index 81be946d..a4cb89c7 100644 --- a/src/main/scala/es/weso/shaclex/Main.scala +++ b/src/main/scala/es/weso/shaclex/Main.scala @@ -81,13 +81,8 @@ object Main extends App with LazyLogging { if (opts.showLog()) { logger.info("Show log info = true") - logger.info(s"Result: ${result.show}") logger.info(s"JSON result: ${result.toJsonString2spaces}") - logger.info(s"Plain result: ${result.toString}") } - println(s"JSON result: ${result.toJsonString2spaces}") - println(s"Plain result: ${result.toString}") - if (opts.showResult() || opts.outputFile.isDefined) { val resultSerialized = result.serialize(opts.resultFormat()) @@ -96,6 +91,17 @@ object Main extends App with LazyLogging { FileUtils.writeFile(opts.outputFile(), resultSerialized) } + if (opts.showValidationReport()) { + val vr = result.validationReport + (for { + rdf <- vr + str <- rdf.serialize(opts.validationReportFormat()) + } yield str).fold( + e => println(s"Error: $e"), + println(_) + ) + } + if (opts.cnvEngine.isDefined) { logger.error("Conversion between engines don't implemented yet") } @@ -143,7 +149,6 @@ object Main extends App with LazyLogging { def getRDFReader(opts: MainOpts, baseFolder: Path): Either[String, RDFReader] = { val base = Some(FileUtils.currentFolderURL) - println(s"RDF Reader base = $base") if (opts.data.isDefined) { val path = baseFolder.resolve(opts.data()) for { @@ -167,7 +172,6 @@ object Main extends App with LazyLogging { def getSchema(opts: MainOpts, baseFolder: Path, rdf: RDFReader): Either[String, Schema] = { val base = Some(FileUtils.currentFolderURL) - println(s"Schema base = $base") if (opts.schema.isDefined) { val path = baseFolder.resolve(opts.schema()) val schema = Schemas.fromFile(path.toFile(), opts.schemaFormat(), opts.engine(), base) diff --git a/src/main/scala/es/weso/shaclex/MainOpts.scala b/src/main/scala/es/weso/shaclex/MainOpts.scala index fd5c378a..3b1bf4c6 100644 --- a/src/main/scala/es/weso/shaclex/MainOpts.scala +++ b/src/main/scala/es/weso/shaclex/MainOpts.scala @@ -22,6 +22,8 @@ class MainOpts( lazy val defaultResultFormat = Result.defaultResultFormat lazy val defaultShapeMapFormat = ShapeMap.defaultFormat lazy val shapeMapFormats = ShapeMap.formats + lazy val defaultValidationReportFormat = "TURTLE" + lazy val validationReportFormats = RDFAsJenaModel.availableFormats.map(_.toUpperCase).distinct banner("""| shaclex: SHACL/ShEx processor | Options: @@ -68,6 +70,21 @@ class MainOpts( validate = isMemberOf(shapeMapFormats), noshort = true) + val showValidationReport = toggle( + name = "showValidationReport", + prefix = "no-", + default = Some(false), + descrYes = "show SHACL validation report", + descrNo = "don't show SHACL validation report", + noshort = true + ) + + val validationReportFormat = opt[String]( + "validationReportFormat", + default = Some(defaultValidationReportFormat), + descr = s"Engine. Default ($defaultValidationReportFormat). Possible values: ${showLs(validationReportFormats)}", + validate = isMemberOf(validationReportFormats)) + val engine = opt[String]( "engine", default = Some(defaultEngine), @@ -86,7 +103,8 @@ class MainOpts( default = Some(false), descrYes = "show more extra info about validation process", descrNo = "don't show extra info", - noshort = true) + noshort = true + ) val showResult = toggle( "showResult", diff --git a/version.sbt b/version.sbt index 8610db9f..08327086 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.0.68" \ No newline at end of file +version in ThisBuild := "0.0.69" \ No newline at end of file