diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..5400d08f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,44 @@ +# Guide for contributors + +This project follows a standard [fork and pull][fork-and-pull] model for accepting contributions via +GitHub pull requests: + +0. [Pick (or report) an issue](#pick-or-report-an-issue) +1. [Write code](#write-code) +2. [Write tests](#write-tests) +3. [Submit a pull request](#submit-a-pull-request) + +## Pick or report an issue + +We always welcome bug reports and feature requests—please don't feel like you need to have time to +contribute a fix or implementation for your issue to be appreciated. + +## Write code + +We prefer functional programming for the code. + +* Code and comments should be formatted to a width no greater than 100 columns. +* Files should not contain trailing spaces. +* Imports should be sorted alphabetically. + +When in doubt, please run `sbt scalastyle` and let us know if you have any questions. + +## Write tests + +Shaclex uses [ScalaTest][scalatest] for testing. + +## Submit a pull request + +* Pull requests should be submitted from a separate branch (e.g. using + `git checkout -b "username/fix-123"`). +* In general we discourage force pushing to an active pull-request branch that other people are + commenting on or contributing to, and suggest using `git merge master` during development. + Once development is complete, use `git rebase master` and force push to [clean up the history][squash]. +* The first line of a commit message should be no more than 72 characters long (to accommodate + formatting in various environments). +* Commit messages should general use the present tense, normal sentence capitalization, and no final + punctuation. +* If a pull request decreases code coverage more than by 2%, please file an issue to make sure that + tests get added. + +This guide for contributors is inspired by [circe's guide](https://github.com/circe/circe/blob/master/CONTRIBUTING.md). diff --git a/build.sbt b/build.sbt index d683e723..37e8dc18 100644 --- a/build.sbt +++ b/build.sbt @@ -37,6 +37,7 @@ lazy val http4sVersion = "0.18.0-M8" lazy val scalatagsVersion = "0.6.7" lazy val scallopVersion = "3.1.1" lazy val jenaVersion = "3.6.0" +lazy val rdf4jVersion = "2.2.4" lazy val jgraphtVersion = "1.1.0" lazy val diffsonVersion = "2.2.5" lazy val xercesVersion = "2.11.0" @@ -59,6 +60,9 @@ lazy val jgraphtCore = "org.jgrapht" % "jgrapht-core" lazy val antlr4 = "org.antlr" % "antlr4" % antlrVersion lazy val xercesImpl = "xerces" % "xercesImpl" % xercesVersion lazy val jenaArq = "org.apache.jena" % "jena-arq" % jenaVersion +lazy val rdf4j = "org.eclipse.rdf4j" % "rdf4j" % rdf4jVersion +lazy val rdf4jModel = "org.eclipse.rdf4j" % "rdf4j-model" % rdf4jVersion +lazy val rdf4j_rioTurtle = "org.eclipse.rdf4j" % "rdf4j-rio-turtle" % rdf4jVersion lazy val scalaLogging = "com.typesafe.scala-logging" %% "scala-logging" % loggingVersion lazy val scallop = "org.rogach" %% "scallop" % scallopVersion lazy val scalactic = "org.scalactic" %% "scalactic" % scalacticVersion @@ -94,8 +98,8 @@ lazy val shaclex = project // buildInfoPackage := "es.weso.shaclex.buildinfo" // ) .settings(commonSettings, packagingSettings, publishSettings, ghPagesSettings, wixSettings) - .aggregate(schema, shacl, shex, manifest, srdfJena, srdf, utils, converter, rbe, typing, validating, server, shapeMaps, depGraphs) - .dependsOn(schema, shacl, shex, manifest, srdfJena, srdf, utils, converter, rbe, typing, validating, server, shapeMaps, depGraphs) + .aggregate(schema, shacl, shex, manifest, srdfJena, srdf4j, srdf, utils, converter, rbe, typing, validating, server, shapeMaps, depGraphs) + .dependsOn(schema, shacl, shex, manifest, srdfJena, srdf4j, srdf, utils, converter, rbe, typing, validating, server, shapeMaps, depGraphs) .settings( unidocProjectFilter in (ScalaUnidoc, unidoc) := inAnyProject -- inProjects(noDocProjects: _*), libraryDependencies ++= Seq( @@ -132,7 +136,12 @@ lazy val shacl = project .in(file("modules/shacl")) .disablePlugins(RevolverPlugin) .settings(commonSettings, publishSettings) - .dependsOn(srdfJena, manifest, utils, typing, validating) + .dependsOn(srdf, + manifest, + utils, + typing, + validating, + srdfJena % Test) .settings( logBuffered in Test := false, parallelExecution in Test := false, @@ -164,7 +173,18 @@ lazy val shex = project testOptions in Test := Seq(Tests.Filter(testFilter)), testOptions in CompatTest := Seq(Tests.Filter(compatFilter)), ) - .dependsOn(srdfJena, srdf, typing, utils % "test -> test; compile -> compile", validating, shapeMaps, rbe, manifest, depGraphs) + .dependsOn( + srdf, + typing, + utils % "test -> test; compile -> compile", + validating, + shapeMaps, + rbe, + manifest, + depGraphs, + srdfJena % Test, + srdf4j % Test + ) .settings( libraryDependencies ++= Seq( typesafeConfig % Test, @@ -181,10 +201,13 @@ lazy val shapeMaps = project .enablePlugins(Antlr4Plugin) .disablePlugins(RevolverPlugin) .settings(commonSettings, publishSettings, antlrSettings("es.weso.shapeMaps.parser")) - .dependsOn(srdfJena) + .dependsOn( + srdf, + utils, + srdfJena % Test) .settings( libraryDependencies ++= Seq( - sext, + sext % Test, scalaLogging, catsCore, catsKernel, @@ -221,7 +244,7 @@ lazy val manifest = project .in(file("modules/manifest")) .disablePlugins(RevolverPlugin) .settings(commonSettings, publishSettings) - .dependsOn(srdfJena, utils) + .dependsOn(srdf, utils, srdfJena) .settings( libraryDependencies ++= Seq( typesafeConfig % Test, @@ -278,6 +301,23 @@ lazy val srdfJena = project ) ) +lazy val srdf4j = project + .in(file("modules/srdf4j")) + .disablePlugins(RevolverPlugin) + .dependsOn(srdf, utils) + .settings(commonSettings, publishSettings) + .settings( + libraryDependencies ++= Seq( + logbackClassic, + scalaLogging, + typesafeConfig % Test, + rdf4j, rdf4jModel, rdf4j_rioTurtle, + catsCore, + catsKernel, + catsMacros + ) + ) + lazy val typing = project .in(file("modules/typing")) .disablePlugins(RevolverPlugin) @@ -312,7 +352,7 @@ lazy val utils = project lazy val validating = project .in(file("modules/validating")) .disablePlugins(RevolverPlugin) - .dependsOn(srdfJena, utils % "test -> test; compile -> compile") + .dependsOn(srdf, srdfJena % Test, utils % "test -> test; compile -> compile") .settings(commonSettings, publishSettings) .settings( libraryDependencies ++= Seq( diff --git a/modules/converter/README.md b/modules/converter/README.md new file mode 100644 index 00000000..4412542d --- /dev/null +++ b/modules/converter/README.md @@ -0,0 +1,3 @@ +# Converter module + +Converts between ShEx and SHACL \ No newline at end of file diff --git a/modules/converter/src/main/scala/es/weso/shacl/converter/shacl2ShEx.scala b/modules/converter/src/main/scala/es/weso/shacl/converter/shacl2ShEx.scala index 43f4509b..2ff21fd1 100644 --- a/modules/converter/src/main/scala/es/weso/shacl/converter/shacl2ShEx.scala +++ b/modules/converter/src/main/scala/es/weso/shacl/converter/shacl2ShEx.scala @@ -67,6 +67,6 @@ object Shacl2ShEx extends Converter { shex.IRILabel(iri) def mkBNodeLabel(n: Int): shex.ShapeLabel = - shex.BNodeLabel(BNodeId(n.toString)) + shex.BNodeLabel(BNode(n.toString)) } \ No newline at end of file diff --git a/modules/converter/src/main/scala/es/weso/shex/converter/shex2shacl.scala b/modules/converter/src/main/scala/es/weso/shex/converter/shex2shacl.scala index f3d52d7f..c2bf8694 100644 --- a/modules/converter/src/main/scala/es/weso/shex/converter/shex2shacl.scala +++ b/modules/converter/src/main/scala/es/weso/shex/converter/shex2shacl.scala @@ -18,7 +18,7 @@ object ShEx2Shacl extends Converter { def newBNode: RDFNode = { bNodeCounter += 1 - BNodeId("shape" + (bNodeCounter)) + BNode("shape" + (bNodeCounter)) } var shapesMap: Map[shacl.ShapeRef, shacl.Shape] = Map() diff --git a/modules/depGraphs/README.md b/modules/depGraphs/README.md new file mode 100644 index 00000000..661cefa6 --- /dev/null +++ b/modules/depGraphs/README.md @@ -0,0 +1,3 @@ +# Dependency Graphs module + +Checks if there are cycles in dependency graphs \ No newline at end of file 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 0a6665da..24676fdb 100644 --- a/modules/manifest/src/main/scala/es/weso/manifest/RDF2Manifest.scala +++ b/modules/manifest/src/main/scala/es/weso/manifest/RDF2Manifest.scala @@ -117,7 +117,7 @@ case class RDF2Manifest(base: Option[IRI], case iri: IRI => if (noType(iri, rdf)) Right(IRIResult(iri)) else compoundResult(iri, rdf) - case bNode: BNodeId => compoundResult(bNode, rdf) + case bNode: BNode => compoundResult(bNode, rdf) case _ => parseFail("Unexpected type of result " + n) } } diff --git a/modules/rbe/README.md b/modules/rbe/README.md new file mode 100644 index 00000000..264f54f4 --- /dev/null +++ b/modules/rbe/README.md @@ -0,0 +1,3 @@ +# RBE (Regular Bag Expressions) implementation + +This module implements Regular Bag Expressions \ No newline at end of file diff --git a/modules/schema/README.md b/modules/schema/README.md new file mode 100644 index 00000000..aeb7e2e7 --- /dev/null +++ b/modules/schema/README.md @@ -0,0 +1,4 @@ +# Schema + +This module defines a generic Schema interface which can be used to validate elements. +We provide two implementations of this interface, for ShEx and for SHACL. \ No newline at end of file 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 e829b6e4..04d17750 100644 --- a/modules/schema/src/main/scala/es/weso/schema/ShaclexSchema.scala +++ b/modules/schema/src/main/scala/es/weso/schema/ShaclexSchema.scala @@ -59,7 +59,7 @@ case class ShaclexSchema(schema: ShaclSchema) extends Schema { private def cnvShape(s: Shape): ShapeMapLabel = { s.id match { case iri: IRI => IRILabel(iri) - case bnode: BNodeId => BNodeLabel(bnode) + case bnode: BNode => BNodeLabel(bnode) case _ => throw new Exception(s"cnvShape: unexpected ${s.id}") } } diff --git a/modules/server/README.md b/modules/server/README.md new file mode 100644 index 00000000..eaccc6d0 --- /dev/null +++ b/modules/server/README.md @@ -0,0 +1,11 @@ +# Server module + +This module implements a simple server based on the [http4s](http://http4s.org/) library. + +The server contains a simple REST API defined [here](https://github.com/labra/shaclex/blob/master/modules/server/src/main/scala/es/weso/server/APIService.scala) and a web service that calls the REST API. +The web service has been implemented using [Twirl templates](https://www.playframework.com/documentation/2.6.x/ScalaTemplates) +which are defined [in this folder](https://github.com/labra/shaclex/tree/master/modules/server/src/main/twirl/es/weso). +Some parts of the web service are implemennted in plain Javascript [here](https://github.com/labra/shaclex/tree/master/modules/server/src/main/resources/staticviews/js). +In the future, it would be better to replace that Javascript code by ScalaJs. + +The server is deployed at [shaclex](http://shaclex.validatingrdf.com). \ No newline at end of file diff --git a/modules/shacl/README.md b/modules/shacl/README.md new file mode 100644 index 00000000..e3be6d9e --- /dev/null +++ b/modules/shacl/README.md @@ -0,0 +1,3 @@ +# SHACL implementation + +This module implements [SHACL](https://www.w3.org/TR/shacl/) on top of the [SRDF] interface so it can be used by [Apache Jena](https://jena.apache.org/) or [RDF4j](http://rdf4j.org/). \ No newline at end of file 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 27a4994f..5455f356 100644 --- a/modules/shacl/src/main/scala/es/weso/shacl/Shacl.scala +++ b/modules/shacl/src/main/scala/es/weso/shacl/Shacl.scala @@ -128,7 +128,7 @@ sealed abstract class Shape { def showId: String = id match { case iri: IRI => iri.str - case bnode: BNodeId => bnode.toString + case bnode: BNode => bnode.toString } def targetNodes: Seq[RDFNode] = 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 81d9346f..36b6044c 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 @@ -238,7 +238,7 @@ object RDF2Shacl extends RDFParser with LazyLogging { def parsePath: RDFParser[SHACLPath] = (n, rdf) => { n match { case iri: IRI => Right(PredicatePath(iri)) - case bnode: BNodeId => someOf( + case bnode: BNode => someOf( inversePath, oneOrMorePath, zeroOrMorePath, 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 21953ffd..9bee1e97 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 @@ -783,7 +783,7 @@ case class Validator(schema: Schema) extends LazyLogging { private def equalsNode(n1: RDFNode, n2: RDFNode): Boolean = (n1, n2) match { case (l1: Literal, l2: Literal) => l1 == l2 case (i1: IRI, i2: IRI) => i1 == i2 - case (b1: BNodeId, b2: BNodeId) => b1 == b2 + case (b1: BNode, b2: BNode) => b1 == b2 case (_, _) => false } @@ -796,7 +796,7 @@ case class Validator(schema: Schema) extends LazyLogging { case (DatatypeLiteral(n1, d1), DatatypeLiteral(n2, d2)) => d1 == d2 && n1 < n2 case (LangLiteral(n1, l1), LangLiteral(n2, l2)) => n1 < n2 case (i1: IRI, i2: IRI) => i1.str < i2.str - case (b1: BNodeId, b2: BNodeId) => b1.id < b2.id + case (b1: BNode, b2: BNode) => b1.id < b2.id case (_, _) => false } private def lessThanOrEqualNode(n1: RDFNode, n2: RDFNode): Boolean = (n1, n2) match { @@ -807,7 +807,7 @@ case class Validator(schema: Schema) extends LazyLogging { case (DatatypeLiteral(n1, d1), DatatypeLiteral(n2, d2)) => d1 == d2 && n1 <= n2 case (LangLiteral(n1, l1), LangLiteral(n2, l2)) => n1 <= n2 case (i1: IRI, i2: IRI) => i1.str <= i2.str - case (b1: BNodeId, b2: BNodeId) => b1.id <= b2.id + case (b1: BNode, b2: BNode) => b1.id <= b2.id case (_, _) => false } 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 d5500d82..8a17e71f 100644 --- a/modules/shacl/src/test/scala/es/weso/shacl/AbstractSyntaxTest.scala +++ b/modules/shacl/src/test/scala/es/weso/shacl/AbstractSyntaxTest.scala @@ -8,7 +8,7 @@ class AbstractSyntaxTest extends FunSpec with Matchers { describe("Abstract Syntax") { it("should be able to create a shape") { - val x = BNodeId("x") + val x = BNode("x") val c: PropertyShape = PropertyShape( id = x, diff --git a/modules/shapeMaps/README.md b/modules/shapeMaps/README.md new file mode 100644 index 00000000..6358a217 --- /dev/null +++ b/modules/shapeMaps/README.md @@ -0,0 +1,3 @@ +# Shape maps implementation + +Implementation of [shape maps](http://shex.io/shape-map/) which can be used to trigger validation. Although shape maps were defined by the ShEx community group they can also be used in SHACL. \ No newline at end of file diff --git a/modules/shapeMaps/src/main/scala/es/weso/shapeMaps/Association.scala b/modules/shapeMaps/src/main/scala/es/weso/shapeMaps/Association.scala index c15da3d3..f0a19e24 100644 --- a/modules/shapeMaps/src/main/scala/es/weso/shapeMaps/Association.scala +++ b/modules/shapeMaps/src/main/scala/es/weso/shapeMaps/Association.scala @@ -5,7 +5,7 @@ import io.circe.syntax._ import NodeSelector._ import ShapeMapLabel._ import es.weso.json.DecoderUtils._ -import es.weso.rdf.nodes.{ BNodeId, IRI, RDFNode } +import es.weso.rdf.nodes.{ BNode, IRI, RDFNode } case class Association(node: NodeSelector, shape: ShapeMapLabel, info: Info = Info()) { @@ -44,7 +44,7 @@ object Association { s => Left(DecodingFailure(s, Nil)), node => node match { case iri: IRI => Right(IRILabel(iri)) - case bnode: BNodeId => Right(BNodeLabel(bnode)) + case bnode: BNode => Right(BNodeLabel(bnode)) case _ => Left(DecodingFailure(s"Cannot parse shapeMapLabel $node", Nil)) })) } diff --git a/modules/shapeMaps/src/main/scala/es/weso/shapeMaps/ShapeMapLabel.scala b/modules/shapeMaps/src/main/scala/es/weso/shapeMaps/ShapeMapLabel.scala index 9b565a2b..9b4c483a 100644 --- a/modules/shapeMaps/src/main/scala/es/weso/shapeMaps/ShapeMapLabel.scala +++ b/modules/shapeMaps/src/main/scala/es/weso/shapeMaps/ShapeMapLabel.scala @@ -1,5 +1,5 @@ package es.weso.shapeMaps -import es.weso.rdf.nodes.{ BNodeId, IRI } +import es.weso.rdf.nodes.{ BNode, IRI } import io.circe.{ Encoder, Json } abstract class ShapeMapLabel { @@ -14,7 +14,7 @@ abstract class ShapeMapLabel { } case class IRILabel(iri: IRI) extends ShapeMapLabel -case class BNodeLabel(bnode: BNodeId) extends ShapeMapLabel +case class BNodeLabel(bnode: BNode) extends ShapeMapLabel case object Start extends ShapeMapLabel object ShapeMapLabel { diff --git a/modules/shex/README.md b/modules/shex/README.md new file mode 100644 index 00000000..07cc9cff --- /dev/null +++ b/modules/shex/README.md @@ -0,0 +1,5 @@ +# ShEx library + +ShEx 2.0 implementation. + +The implementation is defined in terms of the [SRDF](https://github.com/labra/shaclex/tree/master/modules/srdf) interface so it can work with both [Apache Jena](https://jena.apache.org/) and [RDF4j](http://rdf4j.org/). \ No newline at end of file diff --git a/modules/shex/src/main/scala/es/weso/shex/compact/SchemaMaker.scala b/modules/shex/src/main/scala/es/weso/shex/compact/SchemaMaker.scala index 1b486033..14137a26 100644 --- a/modules/shex/src/main/scala/es/weso/shex/compact/SchemaMaker.scala +++ b/modules/shex/src/main/scala/es/weso/shex/compact/SchemaMaker.scala @@ -1170,8 +1170,8 @@ class SchemaMaker extends ShExDocBaseVisitor[Any] with LazyLogging { } } - override def visitBlankNode(ctx: BlankNodeContext): Builder[BNodeId] = { - ok(BNodeId(removeUnderscore(ctx.BLANK_NODE_LABEL().getText()))) + override def visitBlankNode(ctx: BlankNodeContext): Builder[BNode] = { + ok(BNode(removeUnderscore(ctx.BLANK_NODE_LABEL().getText()))) } def removeUnderscore(str: String): String = diff --git a/modules/shex/src/main/scala/es/weso/shex/implicits/decoderShex.scala b/modules/shex/src/main/scala/es/weso/shex/implicits/decoderShex.scala index c65b82a6..46d9b975 100644 --- a/modules/shex/src/main/scala/es/weso/shex/implicits/decoderShex.scala +++ b/modules/shex/src/main/scala/es/weso/shex/implicits/decoderShex.scala @@ -37,8 +37,8 @@ object decoderShEx { /* implicit lazy val keyDecoderShapeLabel: KeyDecoder[ShapeLabel] = KeyDecoder.instance { str => parseShapeLabel(str).toOption } */ - implicit lazy val decodeBNodeId: Decoder[BNodeId] = - Decoder[String].map(BNodeId(_)) + implicit lazy val decodeBNodeId: Decoder[BNode] = + Decoder[String].map(BNode(_)) implicit lazy val decodeIRI: Decoder[IRI] = Decoder[String].emap(parseIRI(_)) @@ -412,7 +412,7 @@ object decoderShEx { def parseShapeLabel(str: String): Either[String, ShapeLabel] = { str match { // Be careful with the order... - case bNodeRegex(bNodeId) => Either.right(BNodeLabel(BNodeId(bNodeId))) + case bNodeRegex(bNodeId) => Either.right(BNodeLabel(BNode(bNodeId))) case iriRegex(i) => parseIRI(i).map(iri => IRILabel(iri)) case _ => Either.left(s"$str doesn't match IRI or BNode") } 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 fad83606..074f4ee0 100644 --- a/modules/shex/src/main/scala/es/weso/shex/shex.scala +++ b/modules/shex/src/main/scala/es/weso/shex/shex.scala @@ -410,7 +410,7 @@ abstract sealed trait ShapeLabel { } } case class IRILabel(iri: IRI) extends ShapeLabel -case class BNodeLabel(bnode: BNodeId) extends ShapeLabel +case class BNodeLabel(bnode: BNode) extends ShapeLabel object Schema { diff --git a/modules/shex/src/main/scala/es/weso/shex/shexR/RDF2ShEx.scala b/modules/shex/src/main/scala/es/weso/shex/shexR/RDF2ShEx.scala index bd6e72c7..d3b82019 100644 --- a/modules/shex/src/main/scala/es/weso/shex/shexR/RDF2ShEx.scala +++ b/modules/shex/src/main/scala/es/weso/shex/shexR/RDF2ShEx.scala @@ -68,7 +68,7 @@ trait RDF2ShEx extends RDFParser with LazyLogging { private def mkId(n: RDFNode): Option[ShapeLabel] = n match { case iri: IRI => Some(IRILabel(iri)) - case bnode: BNodeId => Some(BNodeLabel(bnode)) + case bnode: BNode => Some(BNodeLabel(bnode)) case _ => None // TODO: Raise an exception? } diff --git a/modules/shex/src/main/scala/es/weso/shex/validator/ShapeTyping.scala b/modules/shex/src/main/scala/es/weso/shex/validator/ShapeTyping.scala index 0fb9c79d..c1d85f49 100644 --- a/modules/shex/src/main/scala/es/weso/shex/validator/ShapeTyping.scala +++ b/modules/shex/src/main/scala/es/weso/shex/validator/ShapeTyping.scala @@ -50,7 +50,7 @@ case class ShapeTyping(t: Typing[RDFNode, ShapeType, ViolationError, String]) ex case None => Left(s"Can't create Result shape map for a shape expression without label. ShapeExpr: ${s.shape}") case Some(lbl) => lbl.toRDFNode match { case i: IRI => Either.right(IRIMapLabel(i)) - case b: BNodeId => Either.right(BNodeLabel(b)) + case b: BNode => Either.right(BNodeLabel(b)) case _ => Left(s"Can't create Result shape map for a shape expression with label: $lbl") } } diff --git a/modules/shex/src/main/scala/es/weso/shex/validator/ShowValidator.scala b/modules/shex/src/main/scala/es/weso/shex/validator/ShowValidator.scala index 4b4a6500..6c495641 100644 --- a/modules/shex/src/main/scala/es/weso/shex/validator/ShowValidator.scala +++ b/modules/shex/src/main/scala/es/weso/shex/validator/ShowValidator.scala @@ -17,7 +17,7 @@ class ShowValidator(schema: Schema) { n match { case i: IRI => i.show case l: Literal => l.getLexicalForm - case b: BNodeId => "_:" + b.getLexicalForm + case b: BNode => "_:" + b.getLexicalForm } } } diff --git a/modules/shex/src/main/scala/es/weso/shex/validator/Validator.scala b/modules/shex/src/main/scala/es/weso/shex/validator/Validator.scala index e820a004..5d9c3e7c 100644 --- a/modules/shex/src/main/scala/es/weso/shex/validator/Validator.scala +++ b/modules/shex/src/main/scala/es/weso/shex/validator/Validator.scala @@ -96,7 +96,7 @@ case class Validator(schema: Schema) extends ShowValidator(schema) with LazyLogg private[validator] def mkShapeLabel(n: RDFNode): Check[ShapeLabel] = { n match { case i: IRI => ok(IRILabel(i)) - case b: BNodeId => ok(BNodeLabel(b)) + case b: BNode => ok(BNodeLabel(b)) case _ => { errStr(s"mkShapeLabel: Node ${n.show} can't be a shape") } diff --git a/modules/shex/src/test/scala/es/weso/shex/shexCodecTest.scala b/modules/shex/src/test/scala/es/weso/shex/shexCodecTest.scala index c8352d8c..ff6c60da 100644 --- a/modules/shex/src/test/scala/es/weso/shex/shexCodecTest.scala +++ b/modules/shex/src/test/scala/es/weso/shex/shexCodecTest.scala @@ -34,7 +34,7 @@ class shexCodecTest extends FunSpec with Matchers with EitherValues { describe("Shape Label") { codecValueTest[IRI](IRI("x")) codecValueTest[ShapeLabel](IRILabel(IRI("http://example.org/"))) - codecValueTest[ShapeLabel](BNodeLabel(BNodeId("x"))) + codecValueTest[ShapeLabel](BNodeLabel(BNode("x"))) } describe("Max codec") { diff --git a/modules/shex/src/test/scala/rdf4j/es/weso/shex/validator/ShapeMapValidatorTest.scala b/modules/shex/src/test/scala/rdf4j/es/weso/shex/validator/ShapeMapValidatorTest.scala new file mode 100644 index 00000000..91a04f1c --- /dev/null +++ b/modules/shex/src/test/scala/rdf4j/es/weso/shex/validator/ShapeMapValidatorTest.scala @@ -0,0 +1,257 @@ +package es.weso.shex.validator + +import es.weso.rdf.rdf4j._ +import es.weso.shapeMaps.ShapeMap +import es.weso.shex._ +import org.scalatest._ + +import scala.util._ + +class ShapeMapValidator_RDF4jTest extends FunSpec with Matchers with EitherValues { + + describe("Simple Shape") { + val shexStr = + """ + |prefix : + |prefix xsd: + | + |:S { :p . } + |:CanVote xsd:integer MinInclusive 18 + """.stripMargin + val rdfStr = + """|prefix : + |:a :p :b . + |:c :p 1 .""".stripMargin + + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:S", ":a@:S") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:S,:b@:S", ":a@:S,:b@!:S") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:S,:b@:S,:c@:S", ":a@:S,:b@!:S,:c@:S") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:S,:a@:T", ":a@:S,:a@!:T") + shouldValidateWithShapeMap(rdfStr, shexStr, "23@:CanVote", "23@:CanVote") + } + + describe("Recursive shape") { + val shexStr = + """ + |prefix : + |:S { :p @:S } + """.stripMargin + val rdfStr = + """|prefix : + |:a :p :b . + |:b :p :a . + |:c :p :c . + |:d :p 1 .""".stripMargin + + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:S", ":a@:S,:b@:S") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:S,:b@:S,:c@:S", ":a@:S,:b@:S,:c@:S") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:S,:b@:S,:c@:S,:d@:S", ":a@:S,:b@:S,:c@:S,:d@!:S") + shouldValidateWithShapeMap(rdfStr, shexStr, ":d@:S", ":d@!:S") + } + + describe("Two recursive shapes") { + val shexStr = + """ + |prefix : + |:S { :p @:T } + |:T { :q @:S } + """.stripMargin + val rdfStr = + """|prefix : + |:a :p :b . + |:b :q :a . + |:c :p :c . + |:d :p 1 .""".stripMargin + + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:S", ":a@:S,:b@:T") + shouldValidateWithShapeMap(rdfStr, shexStr, ":b@:T", ":a@:S,:b@:T") + } + + describe("Regular expressions") { + val shexStr = + """ + |prefix : + |:A { :p /\\d{2}/ } + """.stripMargin + val rdfStr = + """|prefix : + |:a :p "23" . + |""".stripMargin + + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:A", ":a@:A") + } + describe("Shape with EXTRA") { + val shexStr = + """ + |prefix : + |prefix xsd: + | + |:S EXTRA :p { :p [ 1 ] } + """.stripMargin + val rdfStr = + """|prefix : + |:a :p 1, 2 . + |:b :p 1 . + |:bad :p 2 . + |""".stripMargin + + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:S", ":a@:S") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:S,:b@:S", ":a@:S,:b@:S") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:S,:b@:S,:bad@:S", ":a@:S,:b@:S,:bad@!:S") + } + + describe("Shape with EXTRA and CLOSED") { + val shexStr = + """ + |prefix : + |prefix xsd: + | + |:S CLOSED EXTRA :p { + | :p [ 1 2 3]; + | :p [ 3 4 5] + |} + """.stripMargin + val rdfStr = + """|prefix : + |:a :p 1, 3 . + |:b :p 2, 5, 7 . + |:bad1 :p 2 . + |""".stripMargin + + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:S,:b@:S,:bad1@:S", ":a@:S,:b@:S,:bad1@!:S") + } + describe("Shape with inverse arcs") { + val shexStr = + """ + |prefix : + |prefix xsd: + | + |:S { ^:p @:T* } + |:T { :q . } + """.stripMargin + val rdfStr = + """|prefix : + |:t1 :p :s1; :q "a" . + |""".stripMargin + + shouldValidateWithShapeMap(rdfStr, shexStr, ":s1@:S", ":s1@:S,:t1@:T") + } + + describe("Language stem") { + val shexStr = + """ + |prefix : + |:A { :p [@es] } + """.stripMargin + val rdfStr = + """|prefix : + |:a :p "Hola"@es . + |:x :p "Hi"@en . + |:y :p "Hi" . + |:z :p 23 . + |""".stripMargin + + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:A", ":a@:A") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:A,:x@:A,:y@:A,:z@:A", ":a@:A,:x@!:A,:y@!:A,:z@!:A") + } + + describe("IRI stem") { + val shexStr = + """ + |prefix : + |:A { :p [ ~ ] } + """.stripMargin + val rdfStr = + """|prefix : + |:a :p . + |:b :p :x . + |:x :p . + |:y :p "Hi" . + |:z :p 23 . + |""".stripMargin + + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:A", ":a@:A") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:A,:b@:A", ":a@:A,:b@:A") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:A,:b@:A,:x@:A,:y@:A,:z@:A", ":a@:A,:b@:A,:x@!:A,:y@!:A,:z@!:A") + } + describe("Closed list") { + val shexStr = + """ + |prefix : + |PREFIX rdf: + |:A { :p @:List } + |:List CLOSED { + | rdf:first @:B ; + | rdf:rest [rdf:nil] OR @:List + |} + |:B { :q . } + """.stripMargin + val rdfStr = + """|prefix : + |PREFIX rdf: + |:a :p (:b1 :b2) . + |:b1 :q 1 . + |:b2 :q 2 . + |:ls rdf:first :b1; rdf:rest rdf:nil. + |:x :p (:b1 :c1) . + |:c1 :r 1 . + |""".stripMargin + + shouldValidateWithShapeMap(rdfStr, shexStr, ":b1@:B", ":b1@:B") + shouldValidateWithShapeMap(rdfStr, shexStr, ":ls@:List", ":ls@:List,:b1@:B") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:A,:ls@:List", ":a@:A,:ls@:List,:b1@:B,:b2@:B") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:A,:x@:A,:ls@:List", ":a@:A,:ls@:List,:b1@:B,:b2@:B,:x@!:A") + } + describe("Labeled triple constraints") { + val shexStr = + """ + |prefix : + |:A { $ (:p .; :q .) } + |:B { :r . ; & } + |""".stripMargin + val rdfStr = + """|prefix : + |:a :p 1 ; :q 1 . + |:b :p 1 ; :q 1; :r 1 . + |""".stripMargin + + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:A", ":a@:A") + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@:A,:b@:B", ":a@:A,:b@:B") + } + describe("Relative uris with base") { + val shexStr = + """ + |prefix : + |base + | { :p . } + |""".stripMargin + val rdfStr = + """|prefix : + |:a :p 1 . + |""".stripMargin + + shouldValidateWithShapeMap(rdfStr, shexStr, ":a@", ":a@") + } + def shouldValidateWithShapeMap( + rdfStr: String, + shexStr: String, + shapeMapStr: String, + expected: String): Unit = { + it(s"Should validate ${shexStr} with ${rdfStr} and ${shapeMapStr} and result $expected") { + val validate = for { + rdf <- RDFAsRDF4jModel.fromChars(rdfStr, "Turtle") + shex <- Schema.fromString(shexStr, "ShExC", None) + shapeMap <- ShapeMap.fromCompact(shapeMapStr, None, rdf.getPrefixMap, shex.prefixMap) + fixedShapeMap <- ShapeMap.fixShapeMap(shapeMap, rdf, rdf.getPrefixMap, shex.prefixMap) + result <- Validator.validate(shex, fixedShapeMap, rdf) + expectedShapeMap <- ShapeMap.parseResultMap(expected, None, rdf, shex.prefixMap) + compare <- result.compareWith(expectedShapeMap) + } yield compare + validate match { + case Left(msg) => fail(s"Error: $msg") + case Right(v) => v should be(true) + } + } + } + +} diff --git a/modules/srdf/README.md b/modules/srdf/README.md new file mode 100644 index 00000000..bd20f363 --- /dev/null +++ b/modules/srdf/README.md @@ -0,0 +1,28 @@ +# Simple RDF + +This module represents a generic and simple RDF interface. +The ShEx/SHACL library uses only methods provided by this interface so it can be used by different libraries. + +It defines 3 traits: +- [RDFReader](https://github.com/labra/shaclex/blob/master/modules/srdf/src/main/scala/es/weso/rdf/RDFReader.scala) declares methods to read data from an RDF model +- [RDFBuilder](https://github.com/labra/shaclex/blob/master/modules/srdf/src/main/scala/es/weso/rdf/RDFBuilder.scala) contains methods to build RDF models. Generate an empty model, add or remove triples. +- [RDFReasoner](https://github.com/labra/shaclex/blob/master/modules/srdf/src/main/scala/es/weso/rdf/RDFReasoner.scala) extends an RDFReader with the possibility of extending the model with the triples inferred by some inference engine. + +It also defines the main components of RDF which are: +- [RDFTriple](https://github.com/labra/shaclex/blob/master/modules/srdf/src/main/scala/es/weso/rdf/triples/RDFTriple.scala) declares an RDF triple. +- [RDFNode](https://github.com/labra/shaclex/blob/master/modules/srdf/src/main/scala/es/weso/rdf/nodes/RDFNode.scala) declares RDF nodes (subjects or objects) +- [IRI](https://github.com/labra/shaclex/blob/master/modules/srdf/src/main/scala/es/weso/rdf/nodes/IRI.scala) declares resources which are IRIs +- [BNode]((https://github.com/labra/shaclex/blob/master/modules/srdf/src/main/scala/es/weso/rdf/nodes/BNode.scala)) declares Blank nodes +- [Literal](https://github.com/labra/shaclex/blob/master/modules/srdf/src/main/scala/es/weso/rdf/nodes/Literal.scala) declares RDF literals +- [Lang](https://github.com/labra/shaclex/blob/master/modules/srdf/src/main/scala/es/weso/rdf/nodes/Lang.scala) handles Languages + +Some utilities to work with RDF are: + +- [Prefixes](https://github.com/labra/shaclex/blob/master/modules/srdf/src/main/scala/es/weso/rdf/PrefixMap.scala) declares a prefix map (a map associating aliases with IRIs) +- [RDFParser](https://github.com/labra/shaclex/blob/master/modules/srdf/src/main/scala/es/weso/rdf/parser/RDFParser.scala) a reader monad to parse RDF with some common utilities +- [SHACLPath](https://github.com/labra/shaclex/blob/master/modules/srdf/src/main/scala/es/weso/rdf/path/SHACLPath.scala) declares SHACL paths + +This interface has two implementations: + +- [SRDFJena](https://github.com/labra/shaclex/tree/master/modules/srdfJena) for [Apache Jena](https://jena.apache.org/) +- [SRDF4j](https://github.com/labra/shaclex/tree/master/modules/srdf4j) for [RDF4j](http://rdf4j.org/) diff --git a/modules/srdf/src/main/scala/es/weso/rdf/nodes/BNode.scala b/modules/srdf/src/main/scala/es/weso/rdf/nodes/BNode.scala new file mode 100644 index 00000000..f6d3083a --- /dev/null +++ b/modules/srdf/src/main/scala/es/weso/rdf/nodes/BNode.scala @@ -0,0 +1,19 @@ +package es.weso.rdf.nodes + +case class BNode(id: String) extends RDFNode { + + // @deprecated + def newBNodeId: BNode = { + val n = id.drop(1).toInt + 1 + BNode("b" + n) + } + + override def toString: String = { + "_:" + id + } + + override def getLexicalForm = id + +} + + diff --git a/modules/srdf/src/main/scala/es/weso/rdf/nodes/Lang.scala b/modules/srdf/src/main/scala/es/weso/rdf/nodes/Lang.scala new file mode 100644 index 00000000..1ad9ca9a --- /dev/null +++ b/modules/srdf/src/main/scala/es/weso/rdf/nodes/Lang.scala @@ -0,0 +1,33 @@ +package es.weso.rdf.nodes + +case class Lang(lang: String) { + + // This should be the right regular expression for lang. + // We don't use this expression because the specification does not also. + val langtag_ex: String = "(\\A[xX]([\\x2d]\\p{Alnum}{1,8})*\\z)" + + "|(((\\A\\p{Alpha}{2,8}(?=\\x2d|\\z)){1}" + + "(([\\x2d]\\p{Alpha}{3})(?=\\x2d|\\z)){0,3}" + + "([\\x2d]\\p{Alpha}{4}(?=\\x2d|\\z))?" + + "([\\x2d](\\p{Alpha}{2}|\\d{3})(?=\\x2d|\\z))?" + + "([\\x2d](\\d\\p{Alnum}{3}|\\p{Alnum}{5,8})(?=\\x2d|\\z))*)" + + "(([\\x2d]([a-wyzA-WYZ](?=\\x2d))([\\x2d](\\p{Alnum}{2,8})+)*))*" + + "([\\x2d][xX]([\\x2d]\\p{Alnum}{1,8})*)?)\\z" + + // TODO. Specification defines other ways to match languages + def matchLanguage(other: Lang) = + this.lang.toLowerCase == other.lang.toLowerCase + + override def toString = lang match { + case "" => "" + case ls => "@" + ls + } + + // The following code has been inspired by: + // http://stackoverflow.com/questions/7681183/how-can-i-define-a-custom-equality-operation-that-will-be-used-by-immutable-set + override def equals(o: Any) = o match { + case that: Lang => that.lang.toLowerCase == this.lang.toLowerCase + case _ => false + } + + override def hashCode = lang.toLowerCase.hashCode +} diff --git a/modules/srdf/src/main/scala/es/weso/rdf/nodes/Literal.scala b/modules/srdf/src/main/scala/es/weso/rdf/nodes/Literal.scala index 2defb21e..ef7f79a2 100644 --- a/modules/srdf/src/main/scala/es/weso/rdf/nodes/Literal.scala +++ b/modules/srdf/src/main/scala/es/weso/rdf/nodes/Literal.scala @@ -111,34 +111,3 @@ case class LangLiteral(lexicalForm: String, lang: Lang) extends Literal { override def getLexicalForm = lexicalForm } -case class Lang(lang: String) { - - // This should be the right regular expression for lang. - // We don't use this expression because the specification does not also. - val langtag_ex: String = "(\\A[xX]([\\x2d]\\p{Alnum}{1,8})*\\z)" + - "|(((\\A\\p{Alpha}{2,8}(?=\\x2d|\\z)){1}" + - "(([\\x2d]\\p{Alpha}{3})(?=\\x2d|\\z)){0,3}" + - "([\\x2d]\\p{Alpha}{4}(?=\\x2d|\\z))?" + - "([\\x2d](\\p{Alpha}{2}|\\d{3})(?=\\x2d|\\z))?" + - "([\\x2d](\\d\\p{Alnum}{3}|\\p{Alnum}{5,8})(?=\\x2d|\\z))*)" + - "(([\\x2d]([a-wyzA-WYZ](?=\\x2d))([\\x2d](\\p{Alnum}{2,8})+)*))*" + - "([\\x2d][xX]([\\x2d]\\p{Alnum}{1,8})*)?)\\z" - - // TODO. Specification defines other ways to match languages - def matchLanguage(other: Lang) = - this.lang.toLowerCase == other.lang.toLowerCase - - override def toString = lang match { - case "" => "" - case ls => "@" + ls - } - - // The following code has been inspired by: - // http://stackoverflow.com/questions/7681183/how-can-i-define-a-custom-equality-operation-that-will-be-used-by-immutable-set - override def equals(o: Any) = o match { - case that: Lang => that.lang.toLowerCase == this.lang.toLowerCase - case _ => false - } - - override def hashCode = lang.toLowerCase.hashCode -} diff --git a/modules/srdf/src/main/scala/es/weso/rdf/nodes/RDFNode.scala b/modules/srdf/src/main/scala/es/weso/rdf/nodes/RDFNode.scala index 12f1b7e0..aa2ce5a6 100644 --- a/modules/srdf/src/main/scala/es/weso/rdf/nodes/RDFNode.scala +++ b/modules/srdf/src/main/scala/es/weso/rdf/nodes/RDFNode.scala @@ -9,7 +9,7 @@ abstract class RDFNode { } def isBNode = this match { - case _: BNodeId => true + case _: BNode => true case _ => false } @@ -31,24 +31,6 @@ abstract class RDFNode { } -case class BNodeId(id: String) extends RDFNode { - - // @deprecated - def newBNodeId: BNodeId = { - val n = id.drop(1).toInt + 1 - BNodeId("b" + n) - } - - override def toString: String = { - "_:" + id - } - - override def getLexicalForm = id - -} - -object InitialBNodeId extends BNodeId("b0") - object RDFNode { val xsd = "http://www.w3.org/2001/XMLSchema#" val rdfSyntax = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" @@ -81,7 +63,7 @@ object RDFNode { val integerRegex = raw"(\d*)".r s match { case iriRegex(iri) => Right(IRI(iri)) - case bNodeRegex(bnodeId) => Right(BNodeId(bnodeId)) + case bNodeRegex(bnodeId) => Right(BNode(bnodeId)) case literalRegex(str) => Right(StringLiteral(str)) case integerRegex(s) => { try Right(IntegerLiteral(s.toInt)) diff --git a/modules/srdf/src/main/scala/es/weso/rdf/triples/RDFTriple.scala b/modules/srdf/src/main/scala/es/weso/rdf/triples/RDFTriple.scala index 9492207a..8a3ef145 100644 --- a/modules/srdf/src/main/scala/es/weso/rdf/triples/RDFTriple.scala +++ b/modules/srdf/src/main/scala/es/weso/rdf/triples/RDFTriple.scala @@ -9,9 +9,9 @@ case class RDFTriple(subj: RDFNode, pred: IRI, obj: RDFNode) { def hasObject(node: RDFNode): Boolean = obj == node def hasPredicate(p: IRI): Boolean = pred == p - def extractBNode(node: RDFNode): Set[BNodeId] = { + def extractBNode(node: RDFNode): Set[BNode] = { node match { - case b @ BNodeId(_) => Set(b) + case b @ BNode(_) => Set(b) case _ => Set() } } @@ -23,7 +23,7 @@ case class RDFTriple(subj: RDFNode, pred: IRI, obj: RDFNode) { } } - def bNodes: Set[BNodeId] = { + def bNodes: Set[BNode] = { extractBNode(subj) ++ extractBNode(obj) } @@ -61,8 +61,8 @@ object RDFTriple { /** * collects BNodes in a set of triples */ - def collectBNodes(triples: Set[RDFTriple]): Set[BNodeId] = { - triples.foldLeft(Set[BNodeId]())((set, triple) => + def collectBNodes(triples: Set[RDFTriple]): Set[BNode] = { + triples.foldLeft(Set[BNode]())((set, triple) => set ++ triple.bNodes) } diff --git a/modules/srdf/src/main/scala/es/weso/utils/PrefixMapUtils.scala b/modules/srdf/src/main/scala/es/weso/utils/PrefixMapUtils.scala index 3f696322..2aa6ea87 100644 --- a/modules/srdf/src/main/scala/es/weso/utils/PrefixMapUtils.scala +++ b/modules/srdf/src/main/scala/es/weso/utils/PrefixMapUtils.scala @@ -22,7 +22,7 @@ object PrefixMapUtils { def showRDFNode(n: RDFNode)(pm: PrefixMap): String = { n match { case i: IRI => showIRI(i)(pm) - case b: BNodeId => b.toString + case b: BNode => b.toString case s: StringLiteral => escapeLexicalForm(s.lexicalForm) case s: DatatypeLiteral => escapeLexicalForm(s.lexicalForm) + "^^" + s.dataType.toString case l: Literal => l.toString diff --git a/modules/srdf4j/LICENSE b/modules/srdf4j/LICENSE new file mode 100644 index 00000000..4e15fa53 --- /dev/null +++ b/modules/srdf4j/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Jose Emilio Labra Gayo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/modules/srdf4j/README.md b/modules/srdf4j/README.md new file mode 100644 index 00000000..3cd1d3cf --- /dev/null +++ b/modules/srdf4j/README.md @@ -0,0 +1,3 @@ +# SRDFj + +Implementation of the [Simple RDF](https://github.com/labra/shaclex/tree/master/modules/srdf) interface based on [SRDFj](http://rdf4j.org/) \ No newline at end of file diff --git a/modules/srdf4j/src/main/scala/es/weso/rdf/rdf4j/RDF4jMapper.scala b/modules/srdf4j/src/main/scala/es/weso/rdf/rdf4j/RDF4jMapper.scala new file mode 100644 index 00000000..a2018f8f --- /dev/null +++ b/modules/srdf4j/src/main/scala/es/weso/rdf/rdf4j/RDF4jMapper.scala @@ -0,0 +1,131 @@ +package es.weso.rdf.rdf4j + +import es.weso.rdf.nodes._ +import es.weso.rdf.triples._ + +import scala.collection.JavaConverters._ +import org.eclipse.rdf4j.model.{BNode => BNode_RDF4j, IRI => IRI_RDF4j, Literal => Literal_RDF4j, _} +import org.eclipse.rdf4j.model.impl.{SimpleValueFactory, BooleanLiteral => BooleanLiteral_RDF4j, DecimalLiteral => DecimalLiteral_RDF4j, IntegerLiteral => IntegerLiteral_RDF4j} +import cats.implicits._ +import org.eclipse.rdf4j.model.vocabulary.XMLSchema + +import scala.util.{Failure, Success, Try} + +object RDF4jMapper { + + lazy val valueFactory = SimpleValueFactory.getInstance; + + private[rdf4j] def literal2Literal(lit: Literal_RDF4j): Literal = { + lit match { + case bl: BooleanLiteral_RDF4j => BooleanLiteral(bl.booleanValue()) + case il: IntegerLiteral_RDF4j => IntegerLiteral(il.intValue()) + case dl: DecimalLiteral_RDF4j => DecimalLiteral(dl.decimalValue()) + case _ if (lit.getDatatype == XMLSchema.STRING) => StringLiteral(lit.stringValue()) + case _ if (lit.getLanguage.isPresent) => LangLiteral(lit.stringValue, Lang(lit.getLanguage.get())) + case _ => DatatypeLiteral(lit.stringValue(), iri2iri(lit.getDatatype)) + } + } + + private[rdf4j] def iri2iri(iri: IRI_RDF4j): IRI = IRI(iri.toString) + + private[rdf4j] def bnode2Bnode(b: BNode_RDF4j): BNode = BNode(b.getID) + + private[rdf4j] def value2RDFNode(value: Value): RDFNode = { + value match { + case lit: Literal_RDF4j => literal2Literal(lit) + case bnode: BNode_RDF4j => bnode2Bnode(bnode) + case iri: IRI_RDF4j => iri2iri(iri) + } + } + + private[rdf4j] def statement2RDFTriple(s: Statement): RDFTriple = { + RDFTriple(resource2RDFNode(s.getSubject), iri2iri(s.getPredicate), value2RDFNode(s.getObject)) + } + + private[rdf4j] def resource2RDFNode(r: Resource): RDFNode = { + r match { + case iri: IRI_RDF4j => iri2iri(iri) + case bnode: BNode_RDF4j => bnode2Bnode(bnode) + case lit: Literal_RDF4j => literal2Literal(lit) + } + } + + private[rdf4j] def iri2Property(iri: IRI): IRI_RDF4j = { + valueFactory.createIRI(iri.str) + } + + private[rdf4j] def rdfNode2Resource(r: RDFNode): Either[String, Resource] = { + r match { + case iri: IRI => Right(valueFactory.createIRI(iri.str)) + case bnode: BNode => Right(valueFactory.createBNode(bnode.id)) + case _ => Left(s"Cannot convert rdfNode: $r to Resource") + } + } + + private[rdf4j] def rdfNode2Value(r: RDFNode): Value = r match { + case iri: IRI => iri2Property(iri) + case bnode: BNode => valueFactory.createBNode(bnode.id) + case StringLiteral(str) => valueFactory.createLiteral(str) + case BooleanLiteral(b) => valueFactory.createLiteral(b) + case IntegerLiteral(i) => valueFactory.createLiteral(i) + case DecimalLiteral(d) => valueFactory.createLiteral(d.bigDecimal) + case DoubleLiteral(d) => valueFactory.createLiteral(d) + case DatatypeLiteral(l,d) => valueFactory.createLiteral(l,iri2Property((d))) + case LangLiteral(l,Lang(lang)) => valueFactory.createLiteral(l,lang) + } + + private[rdf4j] def newBNode(): BNode_RDF4j = valueFactory.createBNode() + + private[rdf4j] def statements2RDFTriples(statements: Set[Statement]): Set[RDFTriple] = { + statements.map(statement2RDFTriple(_)) + } + + private[rdf4j] def triplesSubject(resource: Resource, model: Model): Set[Statement] = { + model.filter(resource, null, null).asScala.toSet + } + + private[rdf4j] def triplesPredicate(iri: IRI_RDF4j, model: Model): Set[Statement] = { + model.filter(null, iri, null).asScala.toSet + } + + private[rdf4j] def triplesObject(value: Value, model: Model): Set[Statement] = { + model.filter(null, null, value).asScala.toSet + } + + private[rdf4j] def triplesPredicateObject(iri: IRI_RDF4j, obj: Value, model: Model): Set[Statement] = { + model.filter(null, iri, obj).asScala.toSet + } + + private[rdf4j] def rdfTriples2Model(triples: Set[RDFTriple]): Either[String, Model] = for { + ss <- triples.map(rdfTriple2Statement(_)).toList.sequence + } yield { + + ??? + } + + private[rdf4j] def rdfTriple2Statement(triple: RDFTriple): Either[String, Statement] = { + val pred = iri2Property(triple.pred) + val obj = rdfNode2Value(triple.obj) + for { + subj <- rdfNode2Resource(triple.subj) + } yield valueFactory.createStatement(subj, pred, obj) + } + + // TODO: Check rules of datatype + private[rdf4j] def wellTypedDatatype(node: RDFNode, expectedDatatype: IRI): Either[String,Boolean] = node match { + case l: Literal => Try { + val datatypeIRI = valueFactory.createIRI(l.dataType.str) + val rdf4jLiteral = valueFactory.createLiteral(l.getLexicalForm, datatypeIRI) + val x = rdf4jLiteral.getLabel + rdf4jLiteral.getDatatype + } match { + case Success(iri) => { + Right(iri.stringValue == expectedDatatype.str) + } + case Failure(e) => Left(e.getMessage) + } + // case DatatypeLiteral(_,dt) => Right(dt == expectedDatatype) + case _ => Right(false) + } + +} \ No newline at end of file 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 new file mode 100644 index 00000000..939e4450 --- /dev/null +++ b/modules/srdf4j/src/main/scala/es/weso/rdf/rdf4j/RDFAsRDF4jModel.scala @@ -0,0 +1,315 @@ +package es.weso.rdf.rdf4j + +import java.io._ +import es.weso.rdf._ +import es.weso.rdf.path.SHACLPath +import es.weso.rdf.triples.RDFTriple +import io.circe.Json +import org.eclipse.rdf4j.model.{ + IRI => IRI_RDF4j, + BNode => _, + Literal => _, + _ +} +import es.weso.rdf.nodes._ +import org.eclipse.rdf4j.model.util.ModelBuilder +import org.eclipse.rdf4j.rio.RDFFormat._ +import org.eclipse.rdf4j.rio.{RDFFormat, Rio} +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) + extends RDFReader + with RDFBuilder + with RDFReasoner { + + type Rdf = RDFAsRDF4jModel + + override def fromString(cs: CharSequence, + format: String, + base: Option[String] = None): Either[String, Rdf] = { + val builder = new ModelBuilder() + val baseURI = base.getOrElse("") + for { + format <- getRDFFormat(format) + model <- Try { + val is : InputStream = new CharSequenceInputStream(cs,"UTF-8") + Rio.parse(is, baseURI, format) + }.fold(e => Left(s"Exception: ${e.getMessage}\nBase:$base, format: $format\n$cs"), + Right(_) + ) + } yield RDFAsRDF4jModel(model) + } + + private def getRDFFormat(name: String): Either[String,RDFFormat] = { + name.toUpperCase match { + case "TURTLE" => Right(TURTLE) + case "JSONLD" => Right(JSONLD) + case "RDFXML" => Right(RDFXML) + case x => Left(s"Unsupported syntax $x") + } + } + + override def serialize(formatName: String): Either[String, String] = for { + format <- getRDFFormat(formatName) + str <- Try { + val out: StringWriter = new StringWriter() + Rio.write(model,out,format) + out.toString + }.fold(e => Left(s"Error serializing RDF to format $formatName: $e"), + Right(_) + ) + } yield str + + private def extend_rdfs: Rdf = { + this + // TODO: Check how to add inference in RDF4j + /* val infModel = ModelFactory.createRDFSModel(model) + RDFAsJenaModel(infModel) */ + } + + // TODO: this implementation only returns subjects + override def iris(): Set[IRI] = { + val resources: Set[Resource] = model.subjects().asScala.toSet + resources.filter(_.isInstanceOf[IRI_RDF4j]).map(_.asInstanceOf[IRI_RDF4j].toString).map(IRI(_)) + } + + override def subjects(): Set[RDFNode] = { + val resources: Set[Resource] = model.subjects().asScala.toSet + resources.map(r => resource2RDFNode(r)) + } + + override def rdfTriples(): Set[RDFTriple] = { + model.asScala.toSet.map(statement2RDFTriple(_)) + } + + override def triplesWithSubject(node: RDFNode): Set[RDFTriple] = { + val maybeResource = rdfNode2Resource(node).toOption + val empty: Set[RDFTriple] = Set() + maybeResource.fold(empty) { + case resource => + val statements: Set[Statement] = triplesSubject(resource, model) + statements2RDFTriples(statements) + } + } + + /** + * return the SHACL instances of a node `cls` + * A node `node` is a shacl instance of `cls` if `node rdf:type/rdfs:subClassOf* cls` + */ + override def getSHACLInstances(c: RDFNode): Seq[RDFNode] = { + ??? + /* + val cJena: JenaRDFNode = JenaMapper.rdfNode2JenaNode(c, model) + JenaUtils.getSHACLInstances(cJena, model).map(n => JenaMapper.jenaNode2RDFNode(n)) + */ + } + + override def hasSHACLClass(n: RDFNode, c: RDFNode): Boolean = { + /* + val nJena: JenaRDFNode = JenaMapper.rdfNode2JenaNode(n, model) + val cJena: JenaRDFNode = JenaMapper.rdfNode2JenaNode(c, model) + JenaUtils.hasClass(nJena, cJena, model) + */ + ??? + } + + override def nodesWithPath(path: SHACLPath): Set[(RDFNode, RDFNode)] = { + /* + val jenaPath: Path = JenaMapper.path2JenaPath(path, model) + val pairs = JenaUtils.getNodesFromPath(jenaPath, model). + map(p => (JenaMapper.jenaNode2RDFNode(p._1), JenaMapper.jenaNode2RDFNode(p._2))) + pairs.toSet */ + ??? + } + + override def objectsWithPath(subj: RDFNode, path: SHACLPath): Set[RDFNode] = { + ??? + /* + val jenaNode: JenaRDFNode = JenaMapper.rdfNode2JenaNode(subj, model) + val jenaPath: Path = JenaMapper.path2JenaPath(path, model) + val nodes = JenaUtils.objectsFromPath(jenaNode, jenaPath, model).map(n => JenaMapper.jenaNode2RDFNode(n)) + nodes.toSet + */ + } + + override def subjectsWithPath(path: SHACLPath, obj: RDFNode): Set[RDFNode] = { + ??? + /* + val jenaNode: JenaRDFNode = JenaMapper.rdfNode2JenaNode(obj, model) + val jenaPath: Path = JenaMapper.path2JenaPath(path, model) + val nodes = JenaUtils.subjectsFromPath(jenaNode, jenaPath, model).map(n => JenaMapper.jenaNode2RDFNode(n)) + nodes.toSet + */ + } + + override def triplesWithPredicate(iri: IRI): Set[RDFTriple] = { + val pred = iri2Property(iri) + statements2RDFTriples(triplesPredicate(pred, model)) + } + + override def triplesWithObject(node: RDFNode): Set[RDFTriple] = { + val obj = rdfNode2Resource(node).toOption + val empty: Set[RDFTriple] = Set() + obj.fold(emptySet) { o => { + statements2RDFTriples(triplesObject(o, model)) + } + } + } + + private lazy val emptySet: Set[RDFTriple] = Set() + + override def triplesWithPredicateObject(p: IRI, o: RDFNode): Set[RDFTriple] = { + val prop = iri2Property(p) + val maybeObj = rdfNode2Resource(o).toOption + maybeObj.fold(emptySet) { obj => + statements2RDFTriples(triplesPredicateObject(prop,obj, model)) + } + } + + override def getPrefixMap: PrefixMap = { + PrefixMap { + val nsSet: Set[Namespace] = model.getNamespaces.asScala.toSet + nsSet.map(ns => (Prefix(ns.getPrefix), IRI(ns.getName))).toMap + } + } + + override def addPrefixMap(pm: PrefixMap): Rdf = { + pm.pm.foreach { + case (Prefix(prefix),value) => model.setNamespace(prefix,value.str) + } + this + } + + override def addTriples(triples: Set[RDFTriple]): Rdf = { + ??? +/* + val newModel = JenaMapper.RDFTriples2Model(triples, model) + model.add(newModel) + this +*/ + } + + // TODO: This is not efficient + override def rmTriple(triple: RDFTriple): Rdf = { + ??? +/* + val empty = ModelFactory.createDefaultModel + val model2delete = JenaMapper.RDFTriples2Model(Set(triple), empty) + model.difference(model2delete) + this +*/ + } + + override def createBNode: (RDFNode, Rdf) = { + (BNode(newBNode.getID), this) + } + + override def addPrefix(alias: String, iri: String): Rdf = { + model.setNamespace(alias,iri) + this + } + + /*def qName(str: String): IRI = { + IRI(model.expandPrefix(str)) + } +*/ + override def empty: Rdf = { + RDFAsRDF4jModel.empty + } + + override def checkDatatype(node: RDFNode, datatype: IRI): Either[String, Boolean] = + wellTypedDatatype(node,datatype) + + /*private def resolveString(str: String): Either[String,IRI] = { + Try(IRIResolver.resolveString(str)).fold( + e => Left(e.getMessage), + iri => Right(IRI(iri)) + ) + }*/ + private val NONE = "NONE" + private val RDFS = "RDFS" + private val OWL = "OWL" + + override def applyInference(inference: String): Either[String, Rdf] = { + Right(this) // TODO (as it is doesn't apply inference) +/* + inference.toUpperCase match { + case `NONE` => Right(this) + case `RDFS` => JenaUtils.inference(model, RDFS).map(RDFAsJenaModel(_)) + case `OWL` => JenaUtils.inference(model, OWL).map(RDFAsJenaModel(_)) + case other => Left(s"Unsupported inference $other") + } +*/ + } + + 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") + + override def queryAsJson(queryStr: String): Either[String, Json] = + Left(s"Not implemented queryAsJson for RDf4j") + + + + override def getNumberOfStatements(): Either[String,Int] = + Right(model.size) + +} + + +object RDFAsRDF4jModel { + + def apply(): RDFAsRDF4jModel = { + RDFAsRDF4jModel.empty + } + + lazy val empty: RDFAsRDF4jModel = { + val builder = new ModelBuilder() + RDFAsRDF4jModel(builder.build) + } + + def fromURI(uri: String, format: String = "TURTLE", base: Option[String] = None): Either[String,RDFAsRDF4jModel] = { + ??? +/* + val baseURI = base.getOrElse(FileUtils.currentFolderURL) + Try { + val m = ModelFactory.createDefaultModel() + RDFDataMgr.read(m, uri, baseURI, shortnameToLang(format)) + RDFAsJenaModel(JenaUtils.relativizeModel(m)) + }.fold(e => Left(s"Exception accessing uri $uri: ${e.getMessage}"), + Right(_) + ) +*/ + } + + def fromFile(file: File, format: String, base: Option[String] = None): Either[String, RDFAsRDF4jModel] = { + ??? +/* + val baseURI = base.getOrElse("") + Try { + val m = ModelFactory.createDefaultModel() + val is: InputStream = new FileInputStream(file) + RDFDataMgr.read(m, is, baseURI, shortnameToLang(format)) + RDFAsJenaModel(JenaUtils.relativizeModel(m)) + }.fold(e => Left(s"Exception parsing RDF from file ${file.getName}: ${e.getMessage}"), + Right(_)) +*/ + } + + def fromChars(cs: CharSequence, format: String, base: Option[String] = None): Either[String,RDFAsRDF4jModel] = { + RDFAsRDF4jModel.empty.fromString(cs, format, base) + } + + def availableFormats: List[String] = { + val formats = List(TURTLE,JSONLD,RDFXML) + formats.map(_.getName) + } + + +} diff --git a/modules/srdf4j/src/test/scala/es/weso/rdf/rdf4j/RDF4jParserTest.scala b/modules/srdf4j/src/test/scala/es/weso/rdf/rdf4j/RDF4jParserTest.scala new file mode 100644 index 00000000..ad1a692f --- /dev/null +++ b/modules/srdf4j/src/test/scala/es/weso/rdf/rdf4j/RDF4jParserTest.scala @@ -0,0 +1,78 @@ +package es.weso.rdf.rdf4j + +import es.weso.rdf.{Prefix, PrefixMap} +import es.weso.rdf.nodes._ +import es.weso.rdf.parser._ +import org.scalatest._ + +import scala.util._ + +class RDF4jParserTest extends FunSpec with Matchers with EitherValues with OptionValues { + + describe("RDF4jParser") { + it(s"Should parse simple Turtle string") { + val str = + """prefix : + |:x :p :y . + |:y a 1 . + """.stripMargin + val mayberdf = RDFAsRDF4jModel.fromChars(str,"Turtle",None) + mayberdf match { + case Left(str) => fail(s"Error parsing: $str") + case Right(rdf) => rdf.getNumberOfStatements().right.value should be(2) + } + } + } + + describe(s"RDF4j API as SRDF") { + it(s"Should be able to parse prefix maps") { + val str = + """prefix : + |:x :p :y . + |:y a 1 . + """.stripMargin + val mayberdf = RDFAsRDF4jModel.fromChars(str,"Turtle",None) + mayberdf match { + case Left(str) => fail(s"Error parsing: $str") + case Right(rdf) => { + val pm = rdf.getPrefixMap() + pm.getIRI("").value should be(IRI("http://example.org/")) + } + } + } + + it(s"Should be able to extend the prefix map") { + val str = + """prefix : + |:x :p :y . + |:y a 1 . + """.stripMargin + val mayberdf = RDFAsRDF4jModel.fromChars(str,"Turtle",None) + mayberdf match { + case Left(str) => fail(s"Error parsing: $str") + case Right(rdf) => { + rdf.addPrefixMap(PrefixMap(Map(Prefix("kiko") -> IRI("http://kiko.org")))) + rdf.getPrefixMap.getIRI("kiko").value should be(IRI("http://kiko.org")) + rdf.getPrefixMap.getIRI("pepe") should be(None) + } + } + } + + it(s"Should be able to get subjects") { + val str = + """prefix : + |:x :p :y . + |:y a 1 . + """.stripMargin + val mayberdf = RDFAsRDF4jModel.fromChars(str,"Turtle",None) + mayberdf match { + case Left(str) => fail(s"Error parsing: $str") + case Right(rdf) => { + val ts = rdf.triplesWithSubject(IRI("http://example.org/x")) + ts.size should be(1) + } + } + } + + } +} diff --git a/modules/srdfJena/README.md b/modules/srdfJena/README.md new file mode 100644 index 00000000..37ef124d --- /dev/null +++ b/modules/srdfJena/README.md @@ -0,0 +1,5 @@ +# srdfJena module + +Implements the [SRDF](https://github.com/labra/shaclex/tree/master/modules/srdf) interface. + +The main class is [RDFAsJenaModel](https://github.com/labra/shaclex/blob/master/modules/srdfJena/src/main/scala/es/weso/rdf/jena/RDFAsJenaModel.scala#L29) which can be used to declare RDFReader's and RDFBuilder's on top of Jena Models \ No newline at end of file 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 afe4e9ff..961243a8 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 @@ -134,7 +134,7 @@ case class Endpoint(endpoint: String) extends RDFReader with RDFReasoner { def jena2rdfnode(r: JenaRDFNode): RDFNode = { if (r.isAnon) { - BNodeId(r.asNode.getBlankNodeId.getLabelString) + BNode(r.asNode.getBlankNodeId.getLabelString) } else if (r.isURIResource) { IRI(r.asResource.getURI()) } else if (r.isLiteral) { 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 41fc8f3b..51c41bb6 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 @@ -60,7 +60,7 @@ object JenaMapper { def rdfNode2Resource(n: RDFNode, m: JenaModel): Option[Resource] = { n match { case i: IRI => Some(m.getResource(resolve(i))) - case BNodeId(id) => { + case BNode(id) => { // Creates the BNode if it doesn't exist Some(m.createResource(new AnonId(id))) } @@ -94,7 +94,7 @@ object JenaMapper { // println(s"jenaNode2RDFNode: URI = ${r.asResource().getURI()}") IRI(r.asResource().getURI) } else if (r.isAnon) { - BNodeId(r.asResource().getId.getLabelString) + BNode(r.asResource().getId.getLabelString) } else if (r.isLiteral) { val lit = r.asLiteral() if (lit.getLanguage() != "") { @@ -134,7 +134,7 @@ object JenaMapper { def createResource(m: JenaModel, node: RDFNode): Resource = { node match { - case BNodeId(id) => m.createResource(new AnonId(id.toString)) + case BNode(id) => m.createResource(new AnonId(id.toString)) case i: IRI => m.createResource(resolve(i)) case _ => throw new Exception("Cannot create a resource from " + node) } @@ -148,7 +148,7 @@ object JenaMapper { val xsdboolean = xsd + "boolean" node match { - case BNodeId(id) => + case BNode(id) => m.createResource(new AnonId(id.toString)) case i: IRI => m.createResource(resolve(i)) @@ -237,14 +237,14 @@ object JenaMapper { case l: es.weso.rdf.nodes.Literal => { Try { val jenaLiteral = emptyModel.createTypedLiteral(l.getLexicalForm, l.dataType.str) - jenaLiteral.getValue() // if it is ill-typed it raises an exception - (jenaLiteral.getDatatypeURI) + jenaLiteral.getValue // if it is ill-typed it raises an exception + jenaLiteral.getDatatypeURI } match { case Success(iri) => { // println(s"JenaMapper.welltypedDatatype, $node. Comparing $expectedDatatype with $iri") Right(iri == expectedDatatype.str) } - case Failure(e) => Left(e.getMessage()) + case Failure(e) => Left(e.getMessage) } } case _ => Right(false) 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 30ea3d4a..b0da5280 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 @@ -37,7 +37,7 @@ case class RDFAsJenaModel(model: Model) override def fromString(cs: CharSequence, format: String, - base: Option[String] = None): Either[String, RDFAsJenaModel] = { + base: Option[String] = None): Either[String, Rdf] = { Try { val m = ModelFactory.createDefaultModel val str_reader = new StringReader(cs.toString) @@ -49,14 +49,26 @@ case class RDFAsJenaModel(model: Model) ) } - override def serialize(format: String): Either[String, String] = { - // TODO: Check if format exists... - val out: StringWriter = new StringWriter() - model.write(out, format) - Right(out.toString) + private def getRDFFormat(formatName: String): Either[String,String] = { + val supportedFormats = RDFLanguages.getRegisteredLanguages() + formatName.toUpperCase match { + case format if supportedFormats.contains(format) => Right(format) + case unknown => Left(s"Unsupported format $unknown") + } } - def extend_rdfs: Rdf = { + override def serialize(formatName: String): Either[String, String] = for { + format <- getRDFFormat(formatName) + str <- Try { + val out: StringWriter = new StringWriter() + model.write(out, format) + out.toString + }.fold(e => Left(s"Error serializing RDF to format $formatName: $e"), + Right(_) + ) + } yield str + + private def extend_rdfs: Rdf = { val infModel = ModelFactory.createRDFSModel(model) RDFAsJenaModel(infModel) } @@ -122,7 +134,7 @@ case class RDFAsJenaModel(model: Model) nodes.toSet } - def toRDFTriples(ls: Set[Statement]): Set[RDFTriple] = { + private def toRDFTriples(ls: Set[Statement]): Set[RDFTriple] = { ls.map(st => statement2triple(st)) } @@ -152,18 +164,18 @@ case class RDFAsJenaModel(model: Model) } } - def model2triples(model: Model): Set[RDFTriple] = { + private def model2triples(model: Model): Set[RDFTriple] = { model.listStatements().asScala.map(st => statement2triple(st)).toSet } - def statement2triple(st: Statement): RDFTriple = { + private def statement2triple(st: Statement): RDFTriple = { RDFTriple( JenaMapper.jenaNode2RDFNode(st.getSubject), property2iri(st.getPredicate), JenaMapper.jenaNode2RDFNode(st.getObject)) } - def property2iri(p: Property): IRI = { + private def property2iri(p: Property): IRI = { IRI(p.getURI) } @@ -174,7 +186,7 @@ case class RDFAsJenaModel(model: Model) }) } - override def addPrefixMap(pm: PrefixMap): RDFAsJenaModel = { + override def addPrefixMap(pm: PrefixMap): Rdf = { val map: Map[String, String] = pm.pm.map { case (Prefix(str), iri) => (str, iri.str) } @@ -185,14 +197,14 @@ case class RDFAsJenaModel(model: Model) // TODO: Check that the last character is indeed : // private def removeLastColon(str: String): String = str.init - override def addTriples(triples: Set[RDFTriple]): RDFAsJenaModel = { + override def addTriples(triples: Set[RDFTriple]): Rdf = { val newModel = JenaMapper.RDFTriples2Model(triples, model) model.add(newModel) this } // TODO: This is not efficient - override def rmTriple(triple: RDFTriple): RDFAsJenaModel = { + override def rmTriple(triple: RDFTriple): Rdf = { val empty = ModelFactory.createDefaultModel val model2delete = JenaMapper.RDFTriples2Model(Set(triple), empty) model.difference(model2delete) @@ -201,19 +213,19 @@ case class RDFAsJenaModel(model: Model) override def createBNode: (RDFNode, RDFAsJenaModel) = { val resource = model.createResource - (BNodeId(resource.getId.getLabelString), this) + (BNode(resource.getId.getLabelString), this) } - override def addPrefix(alias: String, iri: String): RDFAsJenaModel = { + override def addPrefix(alias: String, iri: String): Rdf = { model.setNsPrefix(alias, iri) this } - def qName(str: String): IRI = { + private def qName(str: String): IRI = { IRI(model.expandPrefix(str)) } - def empty: Rdf = { + override def empty: Rdf = { RDFAsJenaModel.empty } @@ -226,9 +238,9 @@ case class RDFAsJenaModel(model: Model) iri => Right(IRI(iri)) ) }*/ - val NONE = "NONE" - val RDFS = "RDFS" - val OWL = "OWL" + private val NONE = "NONE" + private val RDFS = "RDFS" + private val OWL = "OWL" override def applyInference(inference: String): Either[String, Rdf] = { inference.toUpperCase match { 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 70e97af3..c084b82c 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 @@ -124,7 +124,7 @@ case class RDFFromWeb() extends RDFReader { def jena2rdfnode(r: JenaRDFNode): RDFNode = { if (r.isAnon) { - BNodeId(r.asNode.getBlankNodeId.getLabelString) + BNode(r.asNode.getBlankNodeId.getLabelString) } else if (r.isURIResource) { IRI(r.asResource.getURI()) } else if (r.isLiteral) { diff --git a/modules/srdfJena/src/test/scala/es/weso/rdf/jena/RDFJenaSpec.scala b/modules/srdfJena/src/test/scala/es/weso/rdf/jena/RDFJenaSpec.scala index fbb02367..ccbe1a5d 100644 --- a/modules/srdfJena/src/test/scala/es/weso/rdf/jena/RDFJenaSpec.scala +++ b/modules/srdfJena/src/test/scala/es/weso/rdf/jena/RDFJenaSpec.scala @@ -37,7 +37,7 @@ class RDFJenaSpec val pm: PrefixMap = PrefixMap(map) rdf.addPrefixMap(pm) rdf.addTriples(Set( - RDFTriple(IRI("http://example.org#a"), IRI("http://foaf.org#knows"), BNodeId("b" + 1)), RDFTriple(BNodeId("b" + 1), IRI("http://foaf.org#knows"), BNodeId("b" + 2)), RDFTriple(BNodeId("b" + 2), IRI("http://foaf.org#name"), StringLiteral("pepe")))) + RDFTriple(IRI("http://example.org#a"), IRI("http://foaf.org#knows"), BNode("b" + 1)), RDFTriple(BNode("b" + 1), IRI("http://foaf.org#knows"), BNode("b" + 2)), RDFTriple(BNode("b" + 2), IRI("http://foaf.org#name"), StringLiteral("pepe")))) val m2 = str2model("""|@prefix : . |@prefix foaf: . |:a foaf:knows _:x . diff --git a/modules/srdfJena/src/test/scala/es/weso/rdf/parser/RDFParserTest.scala b/modules/srdfJena/src/test/scala/es/weso/rdf/parser/RDFParserTest.scala index 9f87161e..c0bfbb92 100644 --- a/modules/srdfJena/src/test/scala/es/weso/rdf/parser/RDFParserTest.scala +++ b/modules/srdfJena/src/test/scala/es/weso/rdf/parser/RDFParserTest.scala @@ -42,8 +42,8 @@ class RDFParserTest extends FunSpec with Matchers with RDFParser with EitherValu |:x :p :T .""".stripMargin val try1 = for { rdf <- RDFAsJenaModel.fromChars(cs, "TURTLE") - val n: RDFNode = IRI("http://example.org/x") - val p: IRI = IRI("http://example.org/q") + n: RDFNode = IRI("http://example.org/x") + p: IRI = IRI("http://example.org/q") obj <- iriFromPredicate(p)(n, rdf) } yield (obj) try1 match { diff --git a/modules/srdfJena/src/test/scala/es/weso/rdftriple/jenaMapper/JenaMapperTest.scala b/modules/srdfJena/src/test/scala/es/weso/rdftriple/jenaMapper/JenaMapperTest.scala index 8648b8d6..4bb396f7 100644 --- a/modules/srdfJena/src/test/scala/es/weso/rdftriple/jenaMapper/JenaMapperTest.scala +++ b/modules/srdfJena/src/test/scala/es/weso/rdftriple/jenaMapper/JenaMapperTest.scala @@ -17,7 +17,7 @@ class JenaMapperTest describe("Jena Mapper") { it("Should compare one triple with 2 different bNodes") { - val ts = Set(RDFTriple(BNodeId("b" + 0), IRI("http://example.org#p"), BNodeId("b" + 1))) + val ts = Set(RDFTriple(BNode("b" + 0), IRI("http://example.org#p"), BNode("b" + 1))) val s = """[] [] .""" val empty = ModelFactory.createDefaultModel val model1 = RDFTriples2Model(ts, empty) @@ -26,7 +26,7 @@ class JenaMapperTest } it("Should compare one triple with a shared bNode") { - val ts = Set(RDFTriple(BNodeId("b" + 0), IRI("http://example.org#p"), BNodeId("b" + 0))) + val ts = Set(RDFTriple(BNode("b" + 0), IRI("http://example.org#p"), BNode("b" + 0))) val s = """_:a _:a .""" val empty = ModelFactory.createDefaultModel val model1 = RDFTriples2Model(ts, empty) @@ -35,7 +35,7 @@ class JenaMapperTest } it("Should compare one triple with a prefix decl") { - val ts = Set(RDFTriple(BNodeId("b" + 0), IRI("http://example.org#p"), BNodeId("b" + 0))) + val ts = Set(RDFTriple(BNode("b" + 0), IRI("http://example.org#p"), BNode("b" + 0))) val s = """|@prefix : . |_:a :p _:a .""".stripMargin val empty = ModelFactory.createDefaultModel @@ -45,7 +45,7 @@ class JenaMapperTest } it("Should compare one triple with an integer literal") { - val ts = Set(RDFTriple(BNodeId("b" + 0), IRI("http://example.org#p"), IntegerLiteral(1))) + val ts = Set(RDFTriple(BNode("b" + 0), IRI("http://example.org#p"), IntegerLiteral(1))) val s = """|@prefix : . |_:a :p 1 .""".stripMargin val empty = ModelFactory.createDefaultModel @@ -55,7 +55,7 @@ class JenaMapperTest } it("Should compare one triple with a decimal literal") { - val ts = Set(RDFTriple(BNodeId("b" + 0), IRI("http://example.org#p"), DecimalLiteral(1.2))) + val ts = Set(RDFTriple(BNode("b" + 0), IRI("http://example.org#p"), DecimalLiteral(1.2))) val s = """|@prefix : . |_:a :p 1.2 .""".stripMargin val empty = ModelFactory.createDefaultModel @@ -65,7 +65,7 @@ class JenaMapperTest } it("Should compare one triple with a boolean literal") { - val ts = Set(RDFTriple(BNodeId("b" + 0), IRI("http://example.org#p"), BooleanLiteral(true))) + val ts = Set(RDFTriple(BNode("b" + 0), IRI("http://example.org#p"), BooleanLiteral(true))) val s = """|@prefix : . |_:a :p true .""".stripMargin val empty = ModelFactory.createDefaultModel @@ -76,7 +76,7 @@ class JenaMapperTest // The following test fails probably for Double comparison ignore("Should compare one triple with a double literal") { - val ts = Set(RDFTriple(BNodeId("b" + 0), IRI("http://example.org#p"), DoubleLiteral(1.2e3))) + val ts = Set(RDFTriple(BNode("b" + 0), IRI("http://example.org#p"), DoubleLiteral(1.2e3))) val s = """|@prefix : . |_:a :p 1.2e3 .""".stripMargin val empty = ModelFactory.createDefaultModel @@ -87,9 +87,9 @@ class JenaMapperTest it("Should convert three triples") { val ts = Set( - RDFTriple(BNodeId("b" + 0), IRI("http://example.org#p"), BNodeId("b" + 0)), - RDFTriple(BNodeId("b" + 0), IRI("http://example.org#p"), IntegerLiteral(4)), - RDFTriple(BNodeId("b" + 0), IRI("http://example.org#p"), LangLiteral("pepe", Lang("es")))) + RDFTriple(BNode("b" + 0), IRI("http://example.org#p"), BNode("b" + 0)), + RDFTriple(BNode("b" + 0), IRI("http://example.org#p"), IntegerLiteral(4)), + RDFTriple(BNode("b" + 0), IRI("http://example.org#p"), LangLiteral("pepe", Lang("es")))) val empty = ModelFactory.createDefaultModel val m1 = RDFTriples2Model(ts, empty) val m2 = str2model("""|@prefix : . diff --git a/modules/typing/README.md b/modules/typing/README.md new file mode 100644 index 00000000..0054fb0d --- /dev/null +++ b/modules/typing/README.md @@ -0,0 +1,3 @@ +# Typing module + +A generic typing interface. A typing represents a maps from values to possible types that those values can have. \ No newline at end of file diff --git a/modules/validating/README.md b/modules/validating/README.md new file mode 100644 index 00000000..4e930dd7 --- /dev/null +++ b/modules/validating/README.md @@ -0,0 +1,3 @@ +# Validating module + +Validating defines a generic validating monad. \ No newline at end of file diff --git a/notes/0.0.67.md b/notes/0.0.67.md new file mode 100644 index 00000000..45735a88 --- /dev/null +++ b/notes/0.0.67.md @@ -0,0 +1,17 @@ +# New features + +- Added implementation of SRDF4j which allows the library to work also with RDf4j + +- Conversion between ShEx serialized compact syntax and Json + + +TODOs +----- + +- SHACL support using RDf4j (add SHACL paths to SRDF4j) + +- Generate SHACL validating report + +- Conversion from ShEx to SHACL + +- Conversion from SHACL to ShEx diff --git a/version.sbt b/version.sbt index 5b17c66d..3b627c86 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.0.66" \ No newline at end of file +version in ThisBuild := "0.0.67" \ No newline at end of file