-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
327 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
modules/converter/src/test/scala/es/weso/shacl/converter/shacl2ShapeMapTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package es.weso.shacl.converter | ||
|
||
import cats.implicits._ | ||
import es.weso._ | ||
import es.weso.rdf.jena.RDFAsJenaModel | ||
import es.weso.utils.IOUtils | ||
import org.scalatest.matchers.should._ | ||
import org.scalatest.funspec._ | ||
import es.weso.shapeMaps.Association | ||
import es.weso.shapeMaps.ShapeMapLabel | ||
import es.weso.shapeMaps.NodeSelector | ||
import es.weso.shapeMaps.ShapeMap | ||
|
||
class shacl2ShapeMapTest extends AnyFunSpec with Matchers { | ||
|
||
describe("shacl2ShapeMaps converter") { | ||
{ | ||
shouldConvertSHACLShapeMap( | ||
"""|prefix : <http://example.org/> | ||
|prefix sh: <http://www.w3.org/ns/shacl#> | ||
|:S a sh:NodeShape ; | ||
| sh:targetNode :x ; | ||
| sh:nodeKind sh:IRI . | ||
""".stripMargin, | ||
""":x@:S""".stripMargin) | ||
|
||
shouldConvertSHACLShapeMap( | ||
"""|prefix : <http://example.org/> | ||
|prefix sh: <http://www.w3.org/ns/shacl#> | ||
|prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> | ||
|prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> | ||
|:S a sh:NodeShape ; | ||
| sh:targetClass :C ; | ||
| sh:nodeKind sh:IRI . | ||
""".stripMargin, | ||
"""{FOCUS rdf:type/rdfs:subClassOf* :C}@:S""".stripMargin) | ||
|
||
shouldConvertSHACLShapeMap( | ||
"""|prefix : <http://example.org/> | ||
|prefix sh: <http://www.w3.org/ns/shacl#> | ||
|prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> | ||
|prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> | ||
|:S a sh:NodeShape ; | ||
| sh:targetSubjectsOf :p ; | ||
| sh:nodeKind sh:IRI . | ||
""".stripMargin, | ||
"""{FOCUS :p _}@:S""".stripMargin) | ||
|
||
shouldConvertSHACLShapeMap( | ||
"""|prefix : <http://example.org/> | ||
|prefix sh: <http://www.w3.org/ns/shacl#> | ||
|prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> | ||
|prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> | ||
|:S a sh:NodeShape ; | ||
| sh:targetObjectsOf :p ; | ||
| sh:nodeKind sh:IRI . | ||
""".stripMargin, | ||
"""{_ :p FOCUS}@:S""".stripMargin) | ||
} | ||
} | ||
|
||
private def getAssociationPair(a: Association): (NodeSelector,ShapeMapLabel) = (a.node,a.shape) | ||
|
||
private def getPairs(shapeMap: ShapeMap): List[(NodeSelector,ShapeMapLabel)] = shapeMap.associations.map(getAssociationPair) | ||
|
||
def shouldConvertSHACLShapeMap(strSHACL: String, expected: String): Unit = { | ||
it(s"Should convert: $strSHACL to ShapeMap and obtain: $expected") { | ||
val cmp = RDFAsJenaModel.fromString(strSHACL, "TURTLE", None).flatMap(_.use(shaclRDF => for { | ||
shacl <- RDF2Shacl.getShacl(shaclRDF) | ||
shapeMapConverted <- IOUtils.fromES(Shacl2ShEx.shacl2ShEx(shacl).leftMap(e => s"Error in Shacl2ShEx conversion: $e")) | ||
expectedShapeMap <- IOUtils.fromES(shapeMaps.ShapeMap.fromString(expected, "Compact", None,shacl.pm,shacl.pm).leftMap(e => s"Error in Shape maps parsing: $e")) | ||
} yield (shapeMapConverted, expectedShapeMap, shacl))) | ||
cmp.attempt.unsafeRunSync().fold( | ||
e => fail(s"Error: $e"), | ||
values => { | ||
val (converted, expected, shacl) = values | ||
val (schema,shapeMap) = converted | ||
getPairs(shapeMap) should contain theSameElementsAs getPairs(expected) | ||
} | ||
) | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
182 changes: 182 additions & 0 deletions
182
modules/schema/src/main/scala/es/weso/schema/ShaclTQ.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
package es.weso.schema | ||
|
||
import cats.implicits._ | ||
import es.weso.rdf._ | ||
import es.weso.rdf.nodes._ | ||
import es.weso.rdf.jena.RDFAsJenaModel | ||
import cats.effect._ | ||
import cats.effect.concurrent._ | ||
import scala.util.control.NoStackTrace | ||
import org.apache.jena.rdf.model.Model | ||
import org.apache.jena.rdf.model.ModelFactory | ||
import java.io.StringReader | ||
import org.apache.jena.riot._ | ||
import org.apache.jena.riot.Lang | ||
import org.apache.jena.rdf.model.ModelFactory | ||
import org.apache.jena.rdf.model.Resource; | ||
import org.apache.jena.graph.Graph | ||
import org.apache.jena.riot.system.{PrefixMap => _, _} | ||
import org.apache.jena.riot.RDFLanguages | ||
import es.weso.shapeMaps.ResultShapeMap | ||
import collection.JavaConverters._ | ||
import es.weso.shapeMaps.ShapeMap | ||
import java.io._ | ||
import es.weso.utils.JenaUtils | ||
import org.topbraid.shacl.validation.ValidationUtil | ||
import org.topbraid.jenax.util.JenaDatatypes | ||
import org.topbraid.shacl.vocabulary.SH | ||
import org.apache.jena.vocabulary.RDF | ||
|
||
case class ShaclTQException(msg: String) extends Exception(msg) with NoStackTrace | ||
|
||
|
||
case class ShaclTQ(shapesGraph: Model) extends Schema { | ||
override def name = "SHACL_TQ" | ||
|
||
override def formats: Seq[String] = DataFormats.formatNames ++ Seq(Lang.SHACLC.getName().toUpperCase()) | ||
|
||
override def defaultTriggerMode: ValidationTrigger = TargetDeclarations | ||
|
||
override def validate(rdf: RDFReader, trigger: ValidationTrigger, builder: RDFBuilder): IO[Result] = trigger match { | ||
case TargetDeclarations => validateTargetDecls(rdf).map(_.addTrigger(trigger)) | ||
case _ => IO(Result.errStr(s"Not implemented trigger ${trigger.name} for ${name} yet")) | ||
} | ||
|
||
private def validateTargetDecls(rdf: RDFReader): IO[Result] = rdf match { | ||
case rdfJena: RDFAsJenaModel => for { | ||
rdfModel <- rdfJena.getModel | ||
pm <- rdfJena.getPrefixMap | ||
shapesPm = shapesGraph.getNsPrefixMap() | ||
report <- IO { | ||
val report: Resource = ValidationUtil.validateModel(rdfModel, shapesGraph, true); | ||
|
||
// val report: ValidationReport = ShaclValidator.get().validate(shapesGraph.getGraph(), rdfModel.getGraph()) | ||
report | ||
} | ||
result <- report2Result(report, pm, prefixMapFromModel(shapesGraph)) | ||
} yield result | ||
case _ => IO.raiseError(ShaclTQException(s"Not Implemented Jena SHACL validation for ${rdf.rdfReaderName} yet")) | ||
} | ||
|
||
private def prefixMapFromModel(model: Model): PrefixMap = PrefixMap(model.getNsPrefixMap().asScala.toMap.map { | ||
case (alias, iri) => (Prefix(alias), IRI(iri)) | ||
}) | ||
|
||
private def report2Result( | ||
report: Resource, | ||
nodesPrefixMap: PrefixMap, | ||
shapesPrefixMap: PrefixMap | ||
): IO[Result] = for { | ||
eitherRdf <- report2reader(report.getModel()).attempt | ||
isValid <- conforms(report) | ||
numViolations <- countViolations(report) | ||
} yield { | ||
val message = if (isValid) s"Validated" | ||
else s"Number of violations: ${numViolations}" | ||
val shapesMap = report2ShapesMap() | ||
val errors: Seq[ErrorInfo] = report2errors() | ||
// val esRdf = eitherRdf.leftMap(_.getMessage()) | ||
Result(isValid = isValid, | ||
message = message, | ||
shapeMaps = Seq(shapesMap), | ||
validationReport = JenaShaclReport(report.getModel), | ||
errors = errors, | ||
trigger = Some(TargetDeclarations), | ||
nodesPrefixMap = nodesPrefixMap, | ||
shapesPrefixMap = shapesPrefixMap) | ||
} | ||
|
||
private def conforms(report: Resource): IO[Boolean] = | ||
IO { report.hasProperty(SH.conforms,JenaDatatypes.TRUE) } | ||
|
||
private def countViolations(report: Resource): IO[Int] = | ||
IO { report.getModel.listResourcesWithProperty(RDF.`type`,SH.Violation).toList.size } | ||
|
||
|
||
private def report2reader(model: Model): IO[RDFReader] = for { | ||
refModel <- Ref.of[IO, Model](model) | ||
} yield RDFAsJenaModel(refModel,None,None) | ||
|
||
|
||
private def report2errors(): Seq[ErrorInfo] = Seq() | ||
|
||
private def report2ShapesMap(): ResultShapeMap = { | ||
ResultShapeMap.empty | ||
} | ||
|
||
override def fromString(cs: CharSequence, | ||
format: String, | ||
base: Option[String] | ||
): IO[Schema] = for { | ||
model <- IO { | ||
val m : Model = ModelFactory.createDefaultModel() | ||
val str_reader = new StringReader(cs.toString) | ||
val g: Graph = m.getGraph | ||
val dest: StreamRDF = StreamRDFLib.graph(g) | ||
RDFParser.create.source(str_reader).lang(RDFLanguages.shortnameToLang(format)).parse(dest) | ||
m | ||
} | ||
} yield ShaclTQ(model) | ||
|
||
// private def err[A](msg:String): EitherT[IO,String, A] = EitherT.leftT[IO,A](msg) | ||
|
||
override def fromRDF(rdf: RDFReader): IO[es.weso.schema.Schema] = rdf match { | ||
case rdfJena: RDFAsJenaModel => for { | ||
_ <- IO { println(s"SHACL_TQ: Parsing Shapes graph from RDF data")} | ||
model <- rdfJena.getModel | ||
str <- rdfJena.serialize("TURTLE") | ||
_ <- IO { println(s"RDF to parse:\n${str}")} | ||
} yield ShaclTQ(model) | ||
case _ => IO.raiseError(ShaclTQException(s"Cannot obtain ${name} from RDFReader ${rdf.rdfReaderName} yet")) | ||
} | ||
|
||
override def serialize(format: String, base: Option[IRI]): IO[String] = | ||
if (formats.contains(format.toUpperCase)) IO { | ||
val out = new ByteArrayOutputStream() | ||
val relativizedModel = JenaUtils.relativizeModel(shapesGraph, base.map(_.uri)) | ||
relativizedModel.write(out, format) | ||
out.toString | ||
} | ||
else | ||
IO.raiseError(ShaclTQException(s"Format $format not supported to serialize $name. Supported formats=$formats")) | ||
|
||
override def empty: Schema = ShaclTQ.empty | ||
|
||
override def shapes: List[String] = { | ||
List() | ||
} | ||
|
||
override def pm: PrefixMap = prefixMapFromModel(shapesGraph) | ||
|
||
override def convert(targetFormat: Option[String], | ||
targetEngine: Option[String], | ||
base: Option[IRI] | ||
): IO[String] = { | ||
targetEngine.map(_.toUpperCase) match { | ||
case None => serialize(targetFormat.getOrElse(DataFormats.defaultFormatName)) | ||
case Some("SHACL") | Some("SHACLEX") => | ||
serialize(targetFormat.getOrElse(DataFormats.defaultFormatName)) | ||
case Some("SHEX") => | ||
IO.raiseError(ShaclTQException(s"Not implemented conversion between ${name} to ShEx yet")) | ||
case Some(other) => | ||
IO.raiseError(ShaclTQException(s"Conversion $name -> $other not implemented yet")) | ||
} | ||
} | ||
|
||
override def info: SchemaInfo = { | ||
// TODO: Check if shacl schemas are well formed | ||
SchemaInfo(name,"SHACLex", isWellFormed = true, List()) | ||
} | ||
|
||
override def toClingo(rdf: RDFReader, shapeMap: ShapeMap): IO[String] = | ||
IO.raiseError(ShaclTQException(s"Not implemented yet toClingo for $name")) | ||
|
||
} | ||
|
||
object ShaclTQ { | ||
def empty: ShaclTQ = { | ||
val m = ModelFactory.createDefaultModel() | ||
ShaclTQ(m) | ||
} | ||
|
||
} |
Oops, something went wrong.