diff --git a/codemeta.json b/codemeta.json index 8f405ce..30ab29d 100644 --- a/codemeta.json +++ b/codemeta.json @@ -135,18 +135,18 @@ "identifier" : "https://orcid.org/0000-0003-0765-7390" }, "dateCreated" : "2018-02-22", - "dateModified" : "2025-09-10", + "dateModified" : "2025-10-01", "description" : "A heterogeneous data mapping language based on Shape Expressions", "developmentStatus" : "active", "downloadUrl" : "https://api.github.com/repos/herminiogg/ShExML/downloads", - "identifier" : "https://doi.org/10.5281/zenodo.17092549", + "identifier" : "https://doi.org/10.5281/zenodo.17250444", "license" : "https://api.github.com/licenses/mit", "name" : "ShExML", "programmingLanguage" : "Scala", - "releaseNotes" : "## What's Changed\r\n- Added a parellelisation option in the RDF conversion. You can decide which parts of the execution you want to run in parallel and the number of threads to be used (or let the engine decide based on you hardware specs).\r\n- Stdin can be used as input for the mapping rules or as a input source.\r\n- Some minor fixes and stability improvements.\r\n\r\n**Full Changelog**: https://github.com/herminiogg/ShExML/compare/v0.5.4...v0.6.0", + "releaseNotes" : "## What's Changed\r\n* Fixed a bug preventing the correct processing of CSV files through the stdin.", "runtimePlatform" : "JVM", "softwareRequirements" : [ "http://example.org/jena-shacl", "http://example.org/Saxon-HE", "http://example.org/logback-classic", "http://example.org/jena-core", "http://example.org/scala-compiler", "http://example.org/mysql-connector-java", "http://example.org/stringdistance_2.13", "http://example.org/rmlmapper", "http://example.org/jena-base", "http://example.org/srdf_3", "http://example.org/scala-parallel-collections_3", "http://example.org/mssql-jdbc", "http://example.org/json-path", "http://example.org/picocli", "http://example.org/postgresql", "http://example.org/scala3-library_3", "http://example.org/shex_3", "http://example.org/scala-logging_3", "http://example.org/jena-arq", "http://example.org/srdf4j_3", "http://example.org/slf4j-nop", "http://example.org/antlr4", "http://example.org/mariadb-java-client", "http://example.org/sqlite-jdbc", "http://example.org/scalatest_3", "http://example.org/scala-csv_2.13", "http://example.org/scala-reflect" ], - "version" : "0.6.0", + "version" : "0.6.1", "issueTracker" : "https://api.github.com/repos/herminiogg/ShExML/issues", "referencePublication" : "https://doi.org/10.7717/peerj-cs.318" } diff --git a/films.shexml b/films.shexml index fb09425..87d28db 100644 --- a/films.shexml +++ b/films.shexml @@ -1,8 +1,8 @@ PREFIX : PREFIX dbr: PREFIX schema: -SOURCE films_xml_file -SOURCE films_json_file +SOURCE films_xml_file +SOURCE films_json_file ITERATOR film_xml { FIELD id <@id> FIELD name diff --git a/src/main/java/com/herminiogarcia/shexml/helper/LogRedErrorConverter.java b/src/main/java/com/herminiogarcia/shexml/helper/LogRedErrorConverter.java new file mode 100644 index 0000000..1234edd --- /dev/null +++ b/src/main/java/com/herminiogarcia/shexml/helper/LogRedErrorConverter.java @@ -0,0 +1,18 @@ +package com.herminiogarcia.shexml.helper; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.pattern.color.ANSIConstants; +import ch.qos.logback.core.pattern.color.ForegroundCompositeConverterBase; + +public class LogRedErrorConverter extends ForegroundCompositeConverterBase { + + @Override + protected String getForegroundColorCode(ILoggingEvent event) { + if (event.getLevel().equals(Level.ERROR)) { + return ANSIConstants.RED_FG; + } + return ANSIConstants.DEFAULT_FG; // default color for everything else + } + +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 402afa1..f0424bd 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,7 +1,10 @@ + + - %d{HH:mm:ss.SSS} %-5level[%thread] [%class] - %msg%n + %errorsInRed(%d{HH:mm:ss.SSS} %-5level[%thread] [%class] - %msg%n) diff --git a/src/main/scala/com/herminiogarcia/shexml/Main.scala b/src/main/scala/com/herminiogarcia/shexml/Main.scala index 22b56ba..4f66d73 100644 --- a/src/main/scala/com/herminiogarcia/shexml/Main.scala +++ b/src/main/scala/com/herminiogarcia/shexml/Main.scala @@ -1,10 +1,11 @@ package com.herminiogarcia.shexml -import com.herminiogarcia.shexml.helper.{ParallelExecutionConfigurator, PicocliLeftAlignedLayout} +import com.herminiogarcia.shexml.helper.{ParallelExecutionConfigurator, PicocliLeftAlignedLayout, ShExMLError} +import com.typesafe.scalalogging.Logger import picocli.CommandLine import picocli.CommandLine.{ArgGroup, Command, Option} -import java.io.{File, PrintWriter} +import java.io.{File, PrintWriter, StringWriter} import java.util.concurrent.Callable /** @@ -26,6 +27,8 @@ object Main { description = Array("Map and merge heterogeneous data sources with a Shape Expressions based syntax")) class Main extends Callable[Int] { + private val logger = Logger[MappingLauncher] + @Option(names = Array("-m", "--mapping"), required = true, description = Array("Path to the file with the mappings. If '-' is provided as the path the engine will read from the standard input.")) private var file: String = "" @@ -43,8 +46,8 @@ class Main extends Callable[Int] { val parallelExecutionConfiguration = if(generalTransformationOptions.parallel) ParallelExecutionConfigurator(generalTransformationOptions.parallelAspects, generalTransformationOptions.numberOfThreads) else ParallelExecutionConfigurator.empty + val fileContent = fileHandler.mkString try { - val fileContent = fileHandler.mkString val mappingLauncher = new MappingLauncher( generalTransformationOptions.username, generalTransformationOptions.password, @@ -76,6 +79,15 @@ class Main extends Callable[Int] { pw.close() } 0 // well finished + } catch { + case e: ShExMLError => + val sw = new StringWriter() + val pw = new PrintWriter(sw) + e.printStackTrace(pw) + val stackTrace = sw.toString + pw.close(); sw.close() + logger.error(s"${e.getEnrichedErrorMessage(fileContent)}\n$stackTrace") + 1 // badly finished } finally { fileHandler.close() } } } diff --git a/src/main/scala/com/herminiogarcia/shexml/MappingLauncher.scala b/src/main/scala/com/herminiogarcia/shexml/MappingLauncher.scala index 70b1c0d..be7917a 100644 --- a/src/main/scala/com/herminiogarcia/shexml/MappingLauncher.scala +++ b/src/main/scala/com/herminiogarcia/shexml/MappingLauncher.scala @@ -2,14 +2,15 @@ package com.herminiogarcia.shexml import com.herminiogarcia.shexml.antlr.{ShExMLLexer, ShExMLParser} import com.herminiogarcia.shexml.ast._ -import com.herminiogarcia.shexml.helper.{OrphanBNodeRemover, ParallelExecutionConfigurator, SourceHelper} +import com.herminiogarcia.shexml.helper.{LexerErrorListener, OrphanBNodeRemover, ParallelExecutionConfigurator, ParserErrorListener, SourceHelper} import com.herminiogarcia.shexml.parser.ASTCreatorVisitor import com.herminiogarcia.shexml.shex._ -import com.herminiogarcia.shexml.visitor.{PushedOrPoppedValueSearchVisitor, RDFGeneratorVisitor, RMLGeneratorVisitor, VarTableBuilderVisitor} +import com.herminiogarcia.shexml.visitor.{PushedOrPoppedValueSearchVisitor, RDFGeneratorVisitor, RMLGeneratorVisitor, SemanticCheckerVisitor, VarTableBuilderVisitor} import org.antlr.v4.runtime.{CharStreams, CommonTokenStream} import org.apache.jena.query.{Dataset, DatasetFactory} import org.apache.jena.riot.{RDFDataMgr, RDFFormat, RDFLanguages} import com.typesafe.scalalogging.Logger + import java.io.ByteArrayOutputStream import scala.collection.JavaConverters._ import java.util.concurrent.ConcurrentLinkedQueue @@ -47,6 +48,7 @@ class MappingLauncher(val username: String = "", val password: String = "", driv val parser = createParser(lexer) val ast = createAST(parser) val varTable = createVarTable(ast) + runSemanticAnalysis(ast, varTable) generateResultingRDF(ast, varTable) } @@ -58,6 +60,7 @@ class MappingLauncher(val username: String = "", val password: String = "", driv val ast = createAST(parser) val varTable = createVarTable(ast) val dataset = generateResultingRML(ast, varTable, prettify) + runSemanticAnalysis(ast, varTable) val outputStream = new ByteArrayOutputStream() if(prettify) new OrphanBNodeRemover().removeOrphanBNodes(dataset.getDefaultModel) RDFDataMgr.write(outputStream, dataset.getDefaultModel, RDFFormat.TURTLE_PRETTY) @@ -71,6 +74,7 @@ class MappingLauncher(val username: String = "", val password: String = "", driv val parser = createParser(lexer) val ast = createAST(parser) val varTable = createVarTable(ast) + runSemanticAnalysis(ast, varTable) val inferencesTable = new ConcurrentLinkedQueue[ShExMLInferredCardinalitiesAndDatatypes]() generateInferencesFromShExML(ast, varTable, inferencesTable) val shex = new ShExGeneratorVisitor(inferencesTable.asScala.toList).doVisit(ast, null) @@ -84,6 +88,7 @@ class MappingLauncher(val username: String = "", val password: String = "", driv val parser = createParser(lexer) val ast = createAST(parser) val varTable = createVarTable(ast) + runSemanticAnalysis(ast, varTable) val shapeMaps = generateShapeMaps(ast, varTable) new ShapeMapPrinter().print(shapeMaps) } @@ -96,6 +101,7 @@ class MappingLauncher(val username: String = "", val password: String = "", driv val ast = createAST(parser) val varTable = createVarTable(ast) val inferencesTable = new ConcurrentLinkedQueue[ShExMLInferredCardinalitiesAndDatatypes]() + runSemanticAnalysis(ast, varTable) generateInferencesFromShExML(ast, varTable, inferencesTable) logger.info(s"Executing ShEx extraction process as base for the SHACL conversion") val shex = new ShExGeneratorVisitor(inferencesTable.asScala.toList).doVisit(ast, null) @@ -115,6 +121,8 @@ class MappingLauncher(val username: String = "", val password: String = "", driv val lexer = createLexer(mappingCode) val parser = createParser(lexer) val ast = createAST(parser) + val varTable = createVarTable(ast) + runSemanticAnalysis(ast, varTable) precompiledMappingRules } catch { case e: Exception => @@ -126,12 +134,18 @@ class MappingLauncher(val username: String = "", val password: String = "", driv private def createLexer(mappingCode: String): ShExMLLexer = { val finalMappingRules = resolveImports(mappingCode) logger.info("Applying lexer to tokenize input mapping rules") - new ShExMLLexer(CharStreams.fromString(finalMappingRules)) + val lexer = new ShExMLLexer(CharStreams.fromString(finalMappingRules)) + lexer.removeErrorListeners() + lexer.addErrorListener(new LexerErrorListener) + lexer } private def createParser(lexer: ShExMLLexer): ShExMLParser = { logger.info("Parsing tokens from the lexer") - new ShExMLParser(new CommonTokenStream(lexer)) + val parser = new ShExMLParser(new CommonTokenStream(lexer)) + parser.removeErrorListeners() + parser.addErrorListener(new ParserErrorListener) + parser } private def createAST(parser: ShExMLParser): AST = { @@ -142,7 +156,7 @@ class MappingLauncher(val username: String = "", val password: String = "", driv private def createVarTable(ast: AST): mutable.HashMap[Variable, VarResult] = { logger.info(s"Building var table") val varTable = mutable.HashMap[Variable, VarResult]() - varTable += ((Var("rdf:"), URL("http://www.w3.org/1999/02/22-rdf-syntax-ns#"))) + varTable += ((Var("rdf:"), URL("http://www.w3.org/1999/02/22-rdf-syntax-ns#", UnknownParserInfo))) val optionalArgument = Map("variable" -> "", "query" -> "") new VarTableBuilderVisitor(varTable).visit(ast, optionalArgument) logger.debug(s"Var table contents:") @@ -150,6 +164,12 @@ class MappingLauncher(val username: String = "", val password: String = "", driv varTable } + private def runSemanticAnalysis(ast: AST, varTable: mutable.HashMap[Variable, VarResult]): Unit = { + logger.info(s"Running semantic analysis") + new SemanticCheckerVisitor(varTable.toMap).doVisit(ast, null) + logger.debug(s"Semantic analysis completed satisfactorily") + } + private def generateResultingRDF(ast: AST, varTable: mutable.HashMap[Variable, VarResult]): Dataset = { val dataset = DatasetFactory.create() diff --git a/src/main/scala/com/herminiogarcia/shexml/ast/AST.scala b/src/main/scala/com/herminiogarcia/shexml/ast/AST.scala index 6cfcded..0248145 100644 --- a/src/main/scala/com/herminiogarcia/shexml/ast/AST.scala +++ b/src/main/scala/com/herminiogarcia/shexml/ast/AST.scala @@ -3,30 +3,32 @@ package com.herminiogarcia.shexml.ast /** * Created by herminio on 20/12/17. */ -sealed trait AST +sealed trait AST { + val parserInfo: ParserInfo +} -case class ShExML(declaration: List[Declaration], graph: List[Graph], shape: List[Shape]) extends AST -case class Declaration(declarationStatement: DeclarationStatement) extends AST -case class Graph(graphName: GraphVar, shapes: List[Shape]) extends VarResult -case class Shape(shapeName: ShapeVar, action: ActionOrLiteral, predicateObjects: List[PredicateObject], holdingGraph: Option[Graph]) extends VarResult +case class ShExML(declaration: List[Declaration], graph: List[Graph], shape: List[Shape], parserInfo: ParserInfo) extends AST +case class Declaration(declarationStatement: DeclarationStatement, parserInfo: ParserInfo) extends AST +case class Graph(graphName: GraphVar, shapes: List[Shape], parserInfo: ParserInfo) extends VarResult +case class Shape(shapeName: ShapeVar, action: ActionOrLiteral, predicateObjects: List[PredicateObject], holdingGraph: Option[Graph], parserInfo: ParserInfo) extends VarResult sealed trait ActionOrLiteral extends AST -case class Action(shapePrefix: String, action: ExpOrVar, condition: Option[ExpOrVar]) extends ActionOrLiteral -case class LiteralSubject(prefix: Var, value: String) extends ActionOrLiteral +case class Action(shapePrefix: Var, action: ExpOrVar, condition: Option[ExpOrVar], parserInfo: ParserInfo) extends ActionOrLiteral +case class LiteralSubject(prefix: Var, value: String, parserInfo: ParserInfo) extends ActionOrLiteral sealed trait DeclarationStatement extends AST -case class Prefix(name: Var, url: URL) extends DeclarationStatement -case class Source(name: Var, path: FilePathOrStdin) extends DeclarationStatement -case class Query(name: Var, query: QueryOrURL) extends DeclarationStatement -case class Functions(name: Var, query: FilePath) extends DeclarationStatement -case class Iterator(name: Var, queryClause: QueryOrVar, fields: List[Field], iterators: List[NestedIterator]) extends Iterators with DeclarationStatement with VarResult -case class NestedIterator(name: Var, queryClause: QueryClause, fields: List[Field], iterators: List[NestedIterator]) extends Iterators with DeclarationStatement with VarResult -case class Field(name: Var, queryClause: QueryClause, pushed: Boolean, popped: Boolean) extends AST -case class Expression(name: Var, exp: Exp) extends DeclarationStatement -case class Matcher(replacedStrings: ReplacedStrings, replacement: String) extends AST -case class Matchers(name: Var, matchers: MatcherList) extends DeclarationStatement with VarResult -case class AutoIncrement(name: Var, from: Int, to: Int, by: Int, precedentString: Option[String], closingString: Option[String]) extends Iterable[Int] with DeclarationStatement with VarResult { +case class Prefix(name: Var, url: URL, parserInfo: ParserInfo) extends DeclarationStatement +case class Source(name: Var, path: FilePathOrStdin, parserInfo: ParserInfo) extends DeclarationStatement +case class Query(name: Var, query: QueryOrURL, parserInfo: ParserInfo) extends DeclarationStatement +case class Functions(name: Var, query: FilePath, parserInfo: ParserInfo) extends DeclarationStatement +case class Iterator(name: Var, queryClause: QueryOrVar, fields: List[Field], iterators: List[NestedIterator], parserInfo: ParserInfo) extends Iterators with DeclarationStatement with VarResult +case class NestedIterator(name: Var, queryClause: QueryClause, fields: List[Field], iterators: List[NestedIterator], parserInfo: ParserInfo) extends Iterators with DeclarationStatement with VarResult +case class Field(name: Var, queryClause: QueryClause, pushed: Boolean, popped: Boolean, parserInfo: ParserInfo) extends AST +case class Expression(name: Var, exp: Exp, parserInfo: ParserInfo) extends DeclarationStatement +case class Matcher(replacedStrings: ReplacedStrings, replacement: String, parserInfo: ParserInfo) extends AST +case class Matchers(name: Var, matchers: MatcherList, parserInfo: ParserInfo) extends DeclarationStatement with VarResult +case class AutoIncrement(name: Var, from: Int, to: Int, by: Int, precedentString: Option[String], closingString: Option[String], parserInfo: ParserInfo) extends Iterable[Int] with DeclarationStatement with VarResult { override val iterator: scala.Iterator[Int] = scala.Iterator.range(from, to, by) } @@ -36,26 +38,26 @@ sealed trait QueryClause extends QueryOrURL with QueryOrVar { val popped: Boolean = false } -case class JsonPath(query: String) extends QueryClause -case class XmlPath(query: String) extends QueryClause -case class CSVPerRow(query: String) extends QueryClause +case class JsonPath(query: String, parserInfo: ParserInfo) extends QueryClause +case class XmlPath(query: String, parserInfo: ParserInfo) extends QueryClause +case class CSVPerRow(query: String, parserInfo: ParserInfo) extends QueryClause sealed trait Sql extends QueryClause -case class SqlQuery(query: String) extends Sql -case class SqlColumn(query: String, column: String) extends Sql +case class SqlQuery(query: String, parserInfo: ParserInfo) extends Sql +case class SqlColumn(query: String, column: String, parserInfo: ParserInfo) extends Sql sealed trait Sparql extends QueryClause -case class SparqlQuery(query: String) extends Sparql -case class SparqlColumn(query: String, column: String) extends Sparql -case class FieldQuery(query: String, override val pushed: Boolean = false, override val popped: Boolean = false) extends QueryClause +case class SparqlQuery(query: String, parserInfo: ParserInfo) extends Sparql +case class SparqlColumn(query: String, column: String, parserInfo: ParserInfo) extends Sparql +case class FieldQuery(query: String, override val pushed: Boolean = false, override val popped: Boolean = false, parserInfo: ParserInfo) extends QueryClause sealed trait Exp extends ExpOrVar with VarResult sealed trait LeftUnion extends Exp sealed trait RightUnion extends Exp -case class Union(left: LeftUnion, right: RightUnion) extends RightUnion -case class StringOperation(left: IteratorQuery, right: IteratorQuery, unionString: String) extends LeftUnion with RightUnion -case class Substitution(leftUnion: IteratorQuery, rightUnion: IteratorQuery, joinClause: IteratorQuery) extends Exp -case class Join(leftUnion: IteratorQuery, rightUnion: IteratorQuery, leftJoinClause: IteratorQuery, rightJoinClause: IteratorQuery) extends Exp -case class IteratorQuery(firstVar: Var, composedVar: VarOrIteratorQuery, builtinFunction: Option[BuiltinFunction] = None) extends LeftUnion with RightUnion with VarOrIteratorQuery +case class Union(left: LeftUnion, right: RightUnion, parserInfo: ParserInfo) extends RightUnion +case class StringOperation(left: IteratorQuery, right: IteratorQuery, unionString: String, parserInfo: ParserInfo) extends LeftUnion with RightUnion +case class Substitution(leftUnion: IteratorQuery, rightUnion: IteratorQuery, joinClause: IteratorQuery, parserInfo: ParserInfo) extends Exp +case class Join(leftUnion: IteratorQuery, rightUnion: IteratorQuery, leftJoinClause: IteratorQuery, rightJoinClause: IteratorQuery, parserInfo: ParserInfo) extends Exp +case class IteratorQuery(firstVar: Var, composedVar: VarOrIteratorQuery, builtinFunction: Option[BuiltinFunction] = None, parserInfo: ParserInfo) extends LeftUnion with RightUnion with VarOrIteratorQuery sealed trait VarOrIteratorQuery extends AST sealed trait ExpOrVar extends AST @@ -63,32 +65,50 @@ sealed trait QueryOrURL extends VarResult sealed trait QueryOrVar extends VarResult sealed trait Variable extends ExpOrVar -case class Var(name: String) extends Variable with VarOrIteratorQuery with QueryOrVar -case class ShapeVar(name: String) extends Variable -case class GraphVar(prefix: String, name: String) extends Variable +case class Var(name: String, parserInfo: ParserInfo = UnknownParserInfo) extends Variable with VarOrIteratorQuery with QueryOrVar { + override def equals(obj: Any): Boolean = obj match { + case v: Var => v.name == name + case _ => false + } + override def hashCode(): Int = name.hashCode +} +case class ShapeVar(name: String, parserInfo: ParserInfo) extends Variable { + override def equals(obj: Any): Boolean = obj match { + case v: ShapeVar => v.name == name + case _ => false + } + override def hashCode(): Int = name.hashCode +} +case class GraphVar(prefix: String, name: String, parserInfo: ParserInfo) extends Variable { + override def equals(obj: Any): Boolean = obj match { + case v: GraphVar => v.prefix == prefix && v.name == name + case _ => false + } + override def hashCode(): Int = (prefix, name).hashCode +} -case class LiteralObject(prefix: Var, value: String) extends ObjectOrShapeLink -case class LiteralObjectValue(value: String) extends ObjectOrShapeLink +case class LiteralObject(prefix: Var, value: String, parserInfo: ParserInfo) extends ObjectOrShapeLink +case class LiteralObjectValue(value: String, parserInfo: ParserInfo) extends ObjectOrShapeLink -case class PredicateObject(predicate: Predicate, objectOrShapeLink: ObjectOrShapeLink) extends AST -case class Predicate(prefix: String, extension: String) extends AST +case class PredicateObject(predicate: Predicate, objectOrShapeLink: ObjectOrShapeLink, parserInfo: ParserInfo) extends AST +case class Predicate(prefix: String, extension: String, parserInfo: ParserInfo) extends AST -case class FunctionCalling(functionHub: Var, functionName: Var, arguments: Arguments) extends ExpOrVar -case class Arguments(arguments: List[ExpOrVar]) extends AST +case class FunctionCalling(functionHub: Var, functionName: Var, arguments: Arguments, parserInfo: ParserInfo) extends ExpOrVar +case class Arguments(arguments: List[ExpOrVar], parserInfo: ParserInfo) extends AST sealed trait ObjectOrShapeLink extends AST -case class ObjectElement(prefix: String, action: Option[ExpOrVar], literalValue: Option[LiteralObjectValue], matcher: Option[Var], filter: Option[ExpOrVar], - dataType: Option[DataType], langTag: Option[LangTag], rdfCollection: Option[RDFCollection]) extends ObjectOrShapeLink -case class ShapeLink(shape: ShapeVar) extends ObjectOrShapeLink +case class ObjectElement(prefix: Option[Var], action: Option[ExpOrVar], literalValue: Option[LiteralObjectValue], matcher: Option[Var], filter: Option[ExpOrVar], + dataType: Option[DataType], langTag: Option[LangTag], rdfCollection: Option[RDFCollection], parserInfo: ParserInfo) extends ObjectOrShapeLink +case class ShapeLink(shape: ShapeVar, parserInfo: ParserInfo) extends ObjectOrShapeLink sealed trait DataType extends AST -case class DataTypeGeneration(prefix: String, action: ExpOrVar, matcher: Option[Var]) extends DataType -case class DataTypeLiteral(value: String) extends DataType +case class DataTypeGeneration(prefix: Option[Var], action: ExpOrVar, matcher: Option[Var], parserInfo: ParserInfo) extends DataType +case class DataTypeLiteral(value: String, parserInfo: ParserInfo) extends DataType sealed trait LangTag extends AST -case class LangTagGeneration(action: ExpOrVar, matcher: Option[Var]) extends LangTag -case class LangTagLiteral(value: String) extends LangTag +case class LangTagGeneration(action: ExpOrVar, matcher: Option[Var], parserInfo: ParserInfo) extends LangTag +case class LangTagLiteral(value: String, parserInfo: ParserInfo) extends LangTag sealed trait VarResult extends AST sealed trait Iterators extends AST { @@ -103,19 +123,22 @@ sealed trait FilePathOrStdin extends VarResult sealed trait FilePath extends FilePathOrStdin { def value: String } -case class URL(value: String) extends FilePath with QueryOrURL -case class JdbcURL(value: String) extends FilePath -case class RelativePath(value: String) extends FilePath -case class Stdin() extends FilePathOrStdin -case class ReplacedStrings(strings: List[String]) extends AST -case class ComposedVariable(variables: List[Var]) extends AST -case class MatcherList(matchers: List[Matcher]) extends AST +case class URL(value: String, parserInfo: ParserInfo) extends FilePath with QueryOrURL +case class JdbcURL(value: String, parserInfo: ParserInfo) extends FilePath +case class RelativePath(value: String, parserInfo: ParserInfo) extends FilePath +case class Stdin(parserInfo: ParserInfo) extends FilePathOrStdin +case class ReplacedStrings(strings: List[String], parserInfo: ParserInfo) extends AST +case class ComposedVariable(variables: List[Var], parserInfo: ParserInfo) extends AST +case class MatcherList(matchers: List[Matcher], parserInfo: ParserInfo) extends AST sealed trait RDFCollection extends AST -case class RDFList() extends RDFCollection -case class RDFBag() extends RDFCollection -case class RDFAlt() extends RDFCollection -case class RDFSeq() extends RDFCollection +case class RDFList(parserInfo: ParserInfo) extends RDFCollection +case class RDFBag(parserInfo: ParserInfo) extends RDFCollection +case class RDFAlt(parserInfo: ParserInfo) extends RDFCollection +case class RDFSeq(parserInfo: ParserInfo) extends RDFCollection sealed trait BuiltinFunction extends AST -case class Index() extends BuiltinFunction \ No newline at end of file +case class Index(parserInfo: ParserInfo) extends BuiltinFunction + +class ParserInfo(val startLine: Option[Int], val startColumn: Option[Int], val endLine: Option[Int], val endColumn: Option[Int]) +object UnknownParserInfo extends ParserInfo(startLine = None, startColumn = None, endLine = None, endColumn = None) diff --git a/src/main/scala/com/herminiogarcia/shexml/helper/FunctionHubExecutor.scala b/src/main/scala/com/herminiogarcia/shexml/helper/FunctionHubExecutor.scala index 4e40b7d..a058bbd 100644 --- a/src/main/scala/com/herminiogarcia/shexml/helper/FunctionHubExecutor.scala +++ b/src/main/scala/com/herminiogarcia/shexml/helper/FunctionHubExecutor.scala @@ -4,6 +4,7 @@ import com.github.vickumar1981.stringdistance import com.github.vickumar1981.stringdistance.StringDistance.Levenshtein import com.github.vickumar1981.stringdistance.implicits._ import com.github.vickumar1981.stringdistance.StringConverter._ +import com.herminiogarcia.shexml.ast.ParserInfo import com.typesafe.scalalogging.Logger import scala.tools.reflect.ToolBox @@ -11,7 +12,7 @@ import scala.reflect.runtime._ import scala.reflect.runtime import scala.reflect.runtime.universe._ -class FunctionHubExecutor(val functionsCode: LoadedSource) { +class FunctionHubExecutor(val functionsCode: LoadedSource, val parserInfo: ParserInfo) { private val cm = universe.runtimeMirror(getClass.getClassLoader) private val toolBox = cm.mkToolBox() @@ -29,7 +30,9 @@ class FunctionHubExecutor(val functionsCode: LoadedSource) { name.levenshteinDist(a.getName) < name.levenshteinDist(b.getName) //Levenshtein.distance(name, a.getName)(LevenshteinDistance) < Levenshtein.distance(name, b.getName) }) - .head + .headOption.getOrElse( + throw RDFGenerationError(s"Function $name was not found in source code ${functionsCode.filepath}", parserInfo) + ) val parameterTypes = method.getParameterTypes.toSeq val finalArgs = for((arg, theType) <- args zip parameterTypes) yield { val typeName = @@ -59,7 +62,7 @@ class FunctionHubExecutor(val functionsCode: LoadedSource) { else if(typeName == "Short") classOf[Short] else if(typeName == "Byte") classOf[Byte] else if(typeName == "java.lang.String") classOf[String] - else throw new Exception(typeName + "is not a supported type") + else throw FunctionExecutionError(typeName + "is not a supported type") } } \ No newline at end of file diff --git a/src/main/scala/com/herminiogarcia/shexml/helper/ShExMLError.scala b/src/main/scala/com/herminiogarcia/shexml/helper/ShExMLError.scala new file mode 100644 index 0000000..cdb2da8 --- /dev/null +++ b/src/main/scala/com/herminiogarcia/shexml/helper/ShExMLError.scala @@ -0,0 +1,96 @@ +package com.herminiogarcia.shexml.helper + +import com.herminiogarcia.shexml.ast.{ParserInfo, UnknownParserInfo} +import org.antlr.v4.runtime.{BaseErrorListener, CommonToken, RecognitionException, Recognizer} + +import scala.collection.JavaConverters._ + +trait ShExMLError extends Exception { + val message: String + val parserInfo: ParserInfo + private val BOLD = "\u001B[1m" + private val UNDERLINE = "\u001B[4m" + private val RESET_BOLD = "\u001B[22m" + private val RESET_UNDERLINE = "\u001B[24m" + + def getEnrichedErrorMessage: String = { + s"$message between line ${parserInfo.startLine.getOrElse("unknown")} column ${parserInfo.endColumn.getOrElse("unknown")} and line ${parserInfo.endLine.getOrElse("unknown")} column ${parserInfo.endColumn.getOrElse("unknown")}" + } + + def getEnrichedErrorMessage(input: String): String = { + val inputLines = input.lines().toList.asScala.toList + parserInfo.startLine match { + case Some(sl) if sl > 0 => + parserInfo.endLine.flatMap(el => { + if(el == sl) { + val mainLine = inputLines(sl - 1) + parserInfo.startColumn.map(sc => { + val mainLineSplit = mainLine.splitAt(sc) + val endSplit = parserInfo.endColumn.map(ec => mainLineSplit._2.splitAt((ec - 1) - (mainLineSplit._1.length - 1))) + val highlightedLine = endSplit.map(es => generateHighlightedErrorExtract(sl, mainLineSplit._1, es._1, es._2)) + s"$message in line $sl between columns ${parserInfo.startColumn.getOrElse("unknown")} and ${parserInfo.endColumn.getOrElse("unknown")}\n\n${highlightedLine.getOrElse("")}\n" + }) + } else { + val mainLines = inputLines.slice(sl - 1 , el) + parserInfo.startColumn.map(sc => { + val firstLineSplit = mainLines.head.splitAt(sc) + val lastLineSplit = parserInfo.endColumn.map(ec => mainLines.last.splitAt(ec)) + val inBetweenLines = { + val lines = mainLines.tail.dropRight(1).mkString("\n") + if(lines.nonEmpty) lines + "\n" + else lines + } + val highlightedLines = lastLineSplit.map(lls => generateHighlightedErrorExtract(sl, firstLineSplit._1, firstLineSplit._2 + "\n" + inBetweenLines + lls._1, lls._2)) + s"$message between line $sl column ${parserInfo.startColumn.getOrElse("unknown")} and line $el and column ${parserInfo.endColumn.getOrElse("unknown")}\n\n${highlightedLines.getOrElse("")}\n" + }) + } + }).getOrElse(s"$message near line $sl and column ${parserInfo.startColumn.getOrElse("unknown")}\n") + case None => + this.getEnrichedErrorMessage + } + } + + private def generateHighlightedErrorExtract(firstLineNumber: Int, before: String, affectedFragment: String, after: String): String = { + val lineCounter = Iterator.from(firstLineNumber) + val output = before + BOLD + UNDERLINE + affectedFragment + RESET_BOLD + RESET_UNDERLINE + after + output.lines().toList.asScala.map(ol => s"${lineCounter.next()}: ${resetBoldAndUnderline(ol)}").mkString("\n") + } + + private def resetBoldAndUnderline(input: String): String = { + if(input.contains(BOLD + UNDERLINE) && !input.contains(RESET_BOLD + RESET_UNDERLINE)) { + input + RESET_BOLD + RESET_UNDERLINE + } else if(!input.contains(BOLD + UNDERLINE) && input.contains(RESET_BOLD + RESET_UNDERLINE)) { + BOLD + UNDERLINE + input + } else input + } +} + +case class RDFGenerationError(message: String, parserInfo: ParserInfo) extends ShExMLError +case class RMLGenerationError(message: String, parserInfo: ParserInfo) extends ShExMLError +case class ShapesGenerationError(message: String, parserInfo: ParserInfo) extends ShExMLError +case class JDBCDriverError(message: String, parserInfo: ParserInfo) extends ShExMLError +case class QueryError(message: String, parserInfo: ParserInfo) extends ShExMLError +case class VarError(message: String, parserInfo: ParserInfo) extends ShExMLError +case class FileError(message: String, parserInfo: ParserInfo) extends ShExMLError +case class FunctionExecutionError(message: String, parserInfo: ParserInfo = UnknownParserInfo) extends ShExMLError +case class SemanticCheckerError(message: String, parserInfo: ParserInfo) extends ShExMLError +case class LexerError(message: String, parserInfo: ParserInfo) extends ShExMLError +case class ParserError(message: String, parserInfo: ParserInfo) extends ShExMLError + +class LexerErrorListener extends BaseErrorListener { + override def syntaxError(recognizer: Recognizer[_, _], offendingSymbol: Any, line: Int, charPositionInLine: Int, msg: String, e: RecognitionException): Unit = { + throw LexerError(s"Lexer error: $msg", + new ParserInfo(Some(line), Some(charPositionInLine), Some(line), Some(charPositionInLine + 1))) + } +} + +class ParserErrorListener extends BaseErrorListener { + override def syntaxError(recognizer: Recognizer[_, _], offendingSymbol: Any, line: Int, charPositionInLine: Int, msg: String, e: RecognitionException): Unit = { + val endColumn = offendingSymbol match { + case ct: CommonToken => Some(charPositionInLine + (ct.getStopIndex - ct.getStartIndex) + 1) + case _ => Some(charPositionInLine + 1) + } + throw ParserError(s"Parser error: $msg", + new ParserInfo(Some(line), Some(charPositionInLine), Some(line), endColumn)) + } +} diff --git a/src/main/scala/com/herminiogarcia/shexml/parser/ASTCreatorVisitor.scala b/src/main/scala/com/herminiogarcia/shexml/parser/ASTCreatorVisitor.scala index 16aa147..63b3e1b 100644 --- a/src/main/scala/com/herminiogarcia/shexml/parser/ASTCreatorVisitor.scala +++ b/src/main/scala/com/herminiogarcia/shexml/parser/ASTCreatorVisitor.scala @@ -4,10 +4,10 @@ import com.herminiogarcia.shexml.antlr.ShExMLParser._ import com.herminiogarcia.shexml.antlr.ShExMLParserBaseVisitor import com.herminiogarcia.shexml.ast._ import com.herminiogarcia.shexml.ast.URL +import org.antlr.v4.runtime.ParserRuleContext import scala.collection.JavaConverters._ import scala.util.Try - /** * Created by herminio on 20/12/17. */ @@ -17,67 +17,67 @@ class ASTCreatorVisitor extends ShExMLParserBaseVisitor[AST] { val declarations = ctx.decl().asScala.map(visit(_).asInstanceOf[Declaration]).toList val graphs = ctx.graph().asScala.map(visit(_).asInstanceOf[Graph]).toList val shapes = ctx.shape().asScala.map(visit(_).asInstanceOf[Shape]).toList - ShExML(declarations, graphs, shapes) + ShExML(declarations, graphs, shapes, createParserInfo(ctx)) } override def visitDecl(ctx: DeclContext): AST = { val declarationStatement = super.visitDecl(ctx).asInstanceOf[DeclarationStatement] - Declaration(declarationStatement) + Declaration(declarationStatement, createParserInfo(ctx)) } override def visitPrefix(ctx: PrefixContext): AST = { - val url = URL(ctx.URL().getText) + val url = URL(ctx.URL().getText, createParserInfo(ctx)) val name = createVar(ctx.variable()) - Prefix(name, url) + Prefix(name, url, createParserInfo(ctx)) } override def visitSource(ctx: SourceContext): AST = { val url = - if(ctx.URL != null) URL(ctx.URL().getText) - else if(ctx.STDIN() != null) Stdin() - else if(ctx.QUERY_PART() != null) RelativePath(ctx.QUERY_PART().getText) - else JdbcURL(ctx.JDBC_URL().getText) + if(ctx.URL != null) URL(ctx.URL().getText, createParserInfo(ctx)) + else if(ctx.STDIN() != null) Stdin(createParserInfo(ctx)) + else if(ctx.QUERY_PART() != null) RelativePath(ctx.QUERY_PART().getText, createParserInfo(ctx)) + else JdbcURL(ctx.JDBC_URL().getText, createParserInfo(ctx)) val name = createVar(ctx.variable()) - Source(name, url) + Source(name, url, createParserInfo(ctx)) } override def visitFunctions(ctx: FunctionsContext): AST = { val url = - if(ctx.URL != null) URL(ctx.URL().getText) - else RelativePath(ctx.QUERY_PART().getText) + if(ctx.URL != null) URL(ctx.URL().getText, createParserInfo(ctx)) + else RelativePath(ctx.QUERY_PART().getText, createParserInfo(ctx)) val name = createVar(ctx.variable()) - Functions(name, url) + Functions(name, url, createParserInfo(ctx)) } override def visitQuery(ctx: QueryContext): AST = { val queryOrURL: QueryOrURL = if (ctx.URL() != null) { - URL(ctx.URL().getText) + URL(ctx.URL().getText, createParserInfo(ctx)) } else { visitQueryClause(ctx.queryClause()).asInstanceOf[QueryClause] } val name = createVar(ctx.variable()) - Query(name, queryOrURL) + Query(name, queryOrURL, createParserInfo(ctx)) } override def visitQueryClause(ctx: QueryClauseContext): AST = { - if(ctx.JSONPATH() != null) JsonPath(ctx.QUERY_PART().asScala.map(_.getText).mkString(" ")) - else if(ctx.XMLPATH() != null) XmlPath(ctx.QUERY_PART().asScala.map(_.getText).mkString(" ")) - else if(ctx.SQL() != null) SqlQuery(ctx.QUERY_PART().asScala.map(_.getText).mkString(" ")) + if(ctx.JSONPATH() != null) JsonPath(ctx.QUERY_PART().asScala.map(_.getText).mkString(" "), createParserInfo(ctx)) + else if(ctx.XMLPATH() != null) XmlPath(ctx.QUERY_PART().asScala.map(_.getText).mkString(" "), createParserInfo(ctx)) + else if(ctx.SQL() != null) SqlQuery(ctx.QUERY_PART().asScala.map(_.getText).mkString(" "), createParserInfo(ctx)) else if(ctx.SPARQL() != null) SparqlQuery(ctx.QUERY_PART().asScala.map(_.getText) - .mkString(" ").replaceAll("\\\\<", "<").replaceAll("\\\\>", ">")) - else CSVPerRow("") + .mkString(" ").replaceAll("\\\\<", "<").replaceAll("\\\\>", ">"), createParserInfo(ctx)) + else CSVPerRow("", createParserInfo(ctx)) } override def visitExpression(ctx: ExpressionContext): AST = { val exp = visit(ctx.exp()).asInstanceOf[Exp] val name = createVar(ctx.variable()) - Expression(name, exp) + Expression(name, exp, createParserInfo(ctx)) } override def visitMatcher(ctx: MatcherContext): AST = { val matcherVar = createVar(ctx.variable()) val matchers = visit(ctx.matchers()).asInstanceOf[MatcherList] - Matchers(matcherVar, matchers) + Matchers(matcherVar, matchers, createParserInfo(ctx)) } override def visitMatchers(ctx: MatchersContext): AST = { @@ -90,7 +90,7 @@ class ASTCreatorVisitor extends ShExMLParserBaseVisitor[AST] { val replacementString = if(ctx.STRING_OR_VAR() != null ) ctx.STRING_OR_VAR().getText else ctx.STRINGOPERATOR().getText.replaceAll("\"", "") - MatcherList(otherMatchers.::(Matcher(replacedStrings, replacementString))) + MatcherList(otherMatchers.::(Matcher(replacedStrings, replacementString, createParserInfo(ctx))), createParserInfo(ctx)) } override def visitReplacedStrings(ctx: ReplacedStringsContext): AST = { @@ -102,7 +102,7 @@ class ASTCreatorVisitor extends ShExMLParserBaseVisitor[AST] { val string = if(ctx.STRING_OR_VAR() != null ) ctx.STRING_OR_VAR().getText else ctx.STRINGOPERATOR().getText.replaceAll("\"", "") - ReplacedStrings(replacedStrings.::(string)) + ReplacedStrings(replacedStrings.::(string), createParserInfo(ctx)) } override def visitAutoincrement(ctx: AutoincrementContext): AST = { @@ -124,27 +124,27 @@ class ASTCreatorVisitor extends ShExMLParserBaseVisitor[AST] { else if(ctx.STRINGOPERATOR_AUTOINCREMENT(0) != null && ctx.STRINGOPERATOR_AUTOINCREMENT(1) != null) Some(ctx.STRINGOPERATOR_AUTOINCREMENT(1).getText.replaceAll("\"", "")) else None - AutoIncrement(name, from, to, by, precedentString, closingString) + AutoIncrement(name, from, to, by, precedentString, closingString, createParserInfo(ctx)) } override def visitUnion(ctx: UnionContext): AST = { val left = visit(ctx.leftUnionOption()).asInstanceOf[LeftUnion] val right = visit(ctx.rightUnionOption()).asInstanceOf[RightUnion] - Union(left, right) + Union(left, right, createParserInfo(ctx)) } override def visitStringOperation(ctx: StringOperationContext): AST = { val left = visit(ctx.iteratorQuery(0)).asInstanceOf[IteratorQuery] val right = visit(ctx.iteratorQuery(1)).asInstanceOf[IteratorQuery] val stringOperator = ctx.STRINGOPERATOR().getText.replace("\"", "") - StringOperation(left, right, stringOperator) + StringOperation(left, right, stringOperator, createParserInfo(ctx)) } override def visitSubstitution(ctx: SubstitutionContext): AST = { val leftUnion = visit(ctx.iteratorQuery(0)).asInstanceOf[IteratorQuery] val rightUnion = visit(ctx.iteratorQuery(1)).asInstanceOf[IteratorQuery] val joinClause = visit(ctx.iteratorQuery(2)).asInstanceOf[IteratorQuery] - Substitution(leftUnion, rightUnion, joinClause) + Substitution(leftUnion, rightUnion, joinClause, createParserInfo(ctx)) } override def visitJoin(ctx: JoinContext): AST = { @@ -152,35 +152,35 @@ class ASTCreatorVisitor extends ShExMLParserBaseVisitor[AST] { val rightUnion = visit(ctx.iteratorQuery(1)).asInstanceOf[IteratorQuery] val leftJoinClause = visit(ctx.iteratorQuery(2)).asInstanceOf[IteratorQuery] val rightJoinClause = visit(ctx.iteratorQuery(3)).asInstanceOf[IteratorQuery] - Join(leftUnion, rightUnion, leftJoinClause, rightJoinClause) + Join(leftUnion, rightUnion, leftJoinClause, rightJoinClause, createParserInfo(ctx)) } override def visitIterator(ctx: IteratorContext): AST = { val query: QueryOrVar = if(ctx.queryClause() != null) { visit(ctx.queryClause()).asInstanceOf[QueryClause] } else { - Var(ctx.QUERY_PART().asScala.map(_.getText).mkString(" ")) + Var(ctx.QUERY_PART().asScala.map(_.getText).mkString(" "), createParserInfo(ctx)) } val variable = createVar(ctx.variable()) val fields = ctx.field().listIterator().asScala.map(visit(_).asInstanceOf[Field]) val iterators = ctx.nestedIterator().listIterator().asScala.map(visit(_).asInstanceOf[NestedIterator]) - Iterator(variable, query, fields.toList, iterators.toList) + Iterator(variable, query, fields.toList, iterators.toList, createParserInfo(ctx)) } override def visitNestedIterator(ctx: NestedIteratorContext): AST = { - val query = FieldQuery(ctx.QUERY_PART().asScala.map(_.getText).mkString(" ")) + val query = FieldQuery(ctx.QUERY_PART().asScala.map(_.getText).mkString(" "), parserInfo = createParserInfo(ctx)) val variable = createVar(ctx.variable()) val fields = ctx.field().listIterator().asScala.map(visit(_).asInstanceOf[Field]) val iterators = ctx.nestedIterator().listIterator().asScala.map(visit(_).asInstanceOf[NestedIterator]) - NestedIterator(variable, query, fields.toList, iterators.toList) + NestedIterator(variable, query, fields.toList, iterators.toList, createParserInfo(ctx)) } override def visitField(ctx: FieldContext): AST = { val pushed = ctx.PUSHED_FIELD() != null val popped = ctx.POPPED_FIELD() != null - val fieldQuery = FieldQuery(ctx.QUERY_PART().asScala.map(_.getText).mkString(" "), pushed, popped) + val fieldQuery = FieldQuery(ctx.QUERY_PART().asScala.map(_.getText).mkString(" "), pushed, popped, createParserInfo(ctx)) val variable = createVar(ctx.variable()) - Field(variable, fieldQuery, pushed, popped) + Field(variable, fieldQuery, pushed, popped, createParserInfo(ctx)) } override def visitIteratorQuery(ctx: IteratorQueryContext): AST = { @@ -189,7 +189,7 @@ class ASTCreatorVisitor extends ShExMLParserBaseVisitor[AST] { val builtinFunction = if(ctx.builtinFunction() != null) Some(visit(ctx.builtinFunction()).asInstanceOf[BuiltinFunction]) else None - IteratorQuery(firstVar, varOrIteratorQuery, builtinFunction) + IteratorQuery(firstVar, varOrIteratorQuery, builtinFunction, createParserInfo(ctx)) } override def visitComposedVariable(ctx: ComposedVariableContext): AST = { @@ -198,44 +198,44 @@ class ASTCreatorVisitor extends ShExMLParserBaseVisitor[AST] { val otherVariables = if(ctx.composedVariable() != null) visit(ctx.composedVariable()).asInstanceOf[VarOrIteratorQuery] else null - if(otherVariables != null) IteratorQuery(variable, otherVariables) else variable + if(otherVariables != null) IteratorQuery(variable, otherVariables, parserInfo = createParserInfo(ctx, considerFullContext = true)) else variable } override def visitBuiltinFunction(ctx: BuiltinFunctionContext): AST = { if(ctx.INDEX() != null) { - Index() + Index(createParserInfo(ctx)) } else null } override def visitGraph(ctx: GraphContext): AST = { val graphName = createGraphVar(ctx.literalValue) val shapes = ctx.shape().asScala.map(visit(_).asInstanceOf[Shape]).toList - val dummyGraph = Graph(graphName, Nil) + val dummyGraph = Graph(graphName, Nil, UnknownParserInfo) val graphShapes = shapes.map(s => - Shape(s.shapeName, s.action, s.predicateObjects, Some(dummyGraph))) - Graph(graphName, graphShapes) + Shape(s.shapeName, s.action, s.predicateObjects, Some(dummyGraph), s.parserInfo)) + Graph(graphName, graphShapes, createParserInfo(ctx)) } override def visitShape(ctx: ShapeContext): AST = { val shapeName = createShapeVar(ctx.tripleElement) val action = visit(ctx.actionOrLiteral()).asInstanceOf[ActionOrLiteral] val predicateObjects = ctx.predicateObject().asScala.map(visit(_).asInstanceOf[PredicateObject]).toList - Shape(shapeName, action, predicateObjects, None) + Shape(shapeName, action, predicateObjects, None, createParserInfo(ctx)) } override def visitAction(ctx: ActionContext): AST = { - val shapePrefix = ctx.prefixVar().getText + val shapePrefix = createVar(ctx.prefixVar().variable()) val action = visit(ctx.expOrVarOrFunctionCallign(0)).asInstanceOf[ExpOrVar] val condition = if(ctx.expOrVarOrFunctionCallign(1) != null) Some(visit(ctx.expOrVarOrFunctionCallign(1)).asInstanceOf[ExpOrVar]) else None - Action(shapePrefix, action, condition) + Action(shapePrefix, action, condition, createParserInfo(ctx)) } override def visitLiteralSubject(ctx: LiteralSubjectContext): AST = { - val prefix = Var(ctx.prefixVar().getText) + val prefix = Var(ctx.prefixVar().getText, createParserInfo(ctx)) val value = ctx.variable().getText - LiteralSubject(prefix, value) + LiteralSubject(prefix, value, createParserInfo(ctx)) } override def visitPredicateObject(ctx: PredicateObjectContext): AST = { @@ -245,32 +245,35 @@ class ASTCreatorVisitor extends ShExMLParserBaseVisitor[AST] { else if(ctx.shapeLink() == null && ctx.literalValue() == null) visit(ctx.objectElement()).asInstanceOf[ObjectElement] else visit(ctx.literalValue()).asInstanceOf[LiteralObject] - PredicateObject(predicate, objectElementOrShapeLink) + PredicateObject(predicate, objectElementOrShapeLink, createParserInfo(ctx)) } override def visitPredicate(ctx: PredicateContext): AST = { if(ctx.A() != null) { - Predicate("rdf:", "type") + Predicate("rdf:", "type", createParserInfo(ctx)) } else { val prefix = ctx.literalValue().prefixVar().getText val name = ctx.literalValue().variable().getText.replaceAll("\\\\.|%2E", ".") - Predicate(prefix, name) + Predicate(prefix, name, createParserInfo(ctx)) } } override def visitLiteralValue(ctx: LiteralValueContext): AST = { - val prefix = Var(ctx.prefixVar().getText) + val prefix = Var(ctx.prefixVar().getText, createParserInfo(ctx)) val value = ctx.variable().getText - LiteralObject(prefix, value) + LiteralObject(prefix, value, createParserInfo(ctx)) } override def visitObjectElement(ctx: ObjectElementContext): AST = { - val prefix = if(ctx.firstPartObjectElement().prefixVar() != null) ctx.firstPartObjectElement().prefixVar().getText else "" + val prefix = + if(ctx.firstPartObjectElement().prefixVar() != null) + Some(Var(ctx.firstPartObjectElement().prefixVar().getText, createParserInfo(ctx.firstPartObjectElement().prefixVar(), considerFullContext = true))) + else None val mainExpOrVar = if(ctx.firstPartObjectElement().valueRetriever().expOrVarOrFunctionCallign(0) != null) Some(visit(ctx.firstPartObjectElement().valueRetriever().expOrVarOrFunctionCallign(0)).asInstanceOf[ExpOrVar]) else None val literalValue = if(ctx.firstPartObjectElement().valueRetriever().STRINGOPERATOR() != null) - Some(LiteralObjectValue(ctx.firstPartObjectElement().valueRetriever().STRINGOPERATOR().getText.replaceAll("\"", ""))) + Some(LiteralObjectValue(ctx.firstPartObjectElement().valueRetriever().STRINGOPERATOR().getText.replaceAll("\"", ""), createParserInfo(ctx))) else None val matcherVar = if(ctx.firstPartObjectElement().valueRetriever().variable() != null) Option(ctx.firstPartObjectElement().valueRetriever().variable()).map(createVar) @@ -283,13 +286,13 @@ class ASTCreatorVisitor extends ShExMLParserBaseVisitor[AST] { val rdfCollection = { if(ctx.firstPartObjectElement() == null || ctx.firstPartObjectElement().valueRetriever() == null || ctx.firstPartObjectElement().valueRetriever().rdfCollection() == null) None - else if(ctx.firstPartObjectElement().valueRetriever().rdfCollection().RDFLIST() != null) Some(RDFList()) - else if(ctx.firstPartObjectElement().valueRetriever().rdfCollection().RDFSEQ() != null) Some(RDFSeq()) - else if(ctx.firstPartObjectElement().valueRetriever().rdfCollection().RDFALT() != null) Some(RDFAlt()) - else if(ctx.firstPartObjectElement().valueRetriever().rdfCollection().RDFBAG() != null) Some(RDFBag()) + else if(ctx.firstPartObjectElement().valueRetriever().rdfCollection().RDFLIST() != null) Some(RDFList(createParserInfo(ctx))) + else if(ctx.firstPartObjectElement().valueRetriever().rdfCollection().RDFSEQ() != null) Some(RDFSeq(createParserInfo(ctx))) + else if(ctx.firstPartObjectElement().valueRetriever().rdfCollection().RDFALT() != null) Some(RDFAlt(createParserInfo(ctx))) + else if(ctx.firstPartObjectElement().valueRetriever().rdfCollection().RDFBAG() != null) Some(RDFBag(createParserInfo(ctx))) else None } - ObjectElement(prefix, mainExpOrVar, literalValue, matcherVar, condition, dataType, langTag, rdfCollection) + ObjectElement(prefix, mainExpOrVar, literalValue, matcherVar, condition, dataType, langTag, rdfCollection, createParserInfo(ctx)) } override def visitExpOrVarOrFunctionCallign(ctx: ExpOrVarOrFunctionCallignContext): AST = { @@ -300,26 +303,26 @@ class ASTCreatorVisitor extends ShExMLParserBaseVisitor[AST] { override def visitXmlschemadatatype(ctx: XmlschemadatatypeContext): AST = { if(ctx.firstPartObjectElement() == null) { - DataTypeLiteral(ctx.XMLSCHEMADATATYPE().getText) + DataTypeLiteral(ctx.XMLSCHEMADATATYPE().getText, createParserInfo(ctx)) } else { - val prefix = if(ctx.firstPartObjectElement().prefixVar() != null) ctx.firstPartObjectElement().prefixVar().getText else "" + val prefix = if(ctx.firstPartObjectElement().prefixVar() != null) Some(createVar(ctx.firstPartObjectElement().prefixVar().variable())) else None val expOrVar = visit(ctx.firstPartObjectElement().valueRetriever().expOrVarOrFunctionCallign(0)).asInstanceOf[ExpOrVar] val matcherVar = if(ctx.firstPartObjectElement().valueRetriever().variable() != null) Option(ctx.firstPartObjectElement().valueRetriever().variable()).map(createVar) else None - DataTypeGeneration(prefix, expOrVar, matcherVar) + DataTypeGeneration(prefix, expOrVar, matcherVar, createParserInfo(ctx)) } } override def visitLangtag(ctx: LangtagContext): AST = { if(ctx.valueRetriever() == null) { - LangTagLiteral(ctx.LANGTAG().getText.replace("@", "")) + LangTagLiteral(ctx.LANGTAG().getText.replace("@", ""), createParserInfo(ctx)) } else { val expOrVar = visit(ctx.valueRetriever().expOrVarOrFunctionCallign(0)).asInstanceOf[ExpOrVar] val matcherVar = if(ctx.valueRetriever().variable() != null) Option(ctx.valueRetriever().variable()).map(createVar) else None - LangTagGeneration(expOrVar, matcherVar) + LangTagGeneration(expOrVar, matcherVar, createParserInfo(ctx)) } } @@ -327,7 +330,7 @@ class ASTCreatorVisitor extends ShExMLParserBaseVisitor[AST] { val functionHub = createVar(ctx.variable(0)) val functionName = createVar(ctx.variable(1)) val functionArguments = visit(ctx.functionArguments()).asInstanceOf[Arguments] - FunctionCalling(functionHub, functionName, functionArguments) + FunctionCalling(functionHub, functionName, functionArguments, createParserInfo(ctx)) } override def visitFunctionArguments(ctx: FunctionArgumentsContext): AST = { @@ -337,33 +340,41 @@ class ASTCreatorVisitor extends ShExMLParserBaseVisitor[AST] { val arguments = if(ctx.functionArguments() != null) argument +: visit(ctx.functionArguments()).asInstanceOf[Arguments].arguments else List(argument) - Arguments(arguments) + Arguments(arguments, createParserInfo(ctx)) } override def visitShapeLink(ctx: ShapeLinkContext): AST = { - val shapeName = ShapeVar(ctx.getText.replace("@", "").replaceAll("\\\\.|%2E", ".")) - ShapeLink(shapeName) + val shapeName = ShapeVar(ctx.getText.replace("@", "").replaceAll("\\\\.|%2E", "."), createParserInfo(ctx)) + ShapeLink(shapeName, createParserInfo(ctx, considerFullContext = true)) } def createVar(variable: VariableContext): Var = { - Var(Try(variable.getText).getOrElse("").replaceAll("\\\\.|%2E", ".")) + Var(Try(variable.getText).getOrElse("").replaceAll("\\\\.|%2E", "."), createParserInfo(variable, considerFullContext = true)) } def createShapeVar(tripleElementContext: TripleElementContext): ShapeVar = { - ShapeVar(tripleElementContext.getText.replaceAll("\\\\.|%2E", ".")) + ShapeVar(tripleElementContext.getText.replaceAll("\\\\.|%2E", "."), createParserInfo(tripleElementContext)) } def createGraphVar(literalValueContext: LiteralValueContext): GraphVar = { GraphVar(literalValueContext.prefixVar().getText, - literalValueContext.variable().getText.replaceAll("\\\\.|%2E", ".")) + literalValueContext.variable().getText.replaceAll("\\\\.|%2E", "."), + createParserInfo(literalValueContext)) } def getDeclarationContent(content: String): String = content.replaceAll("<>", "") def createQuery(query: String): QueryClause = { - if(query.startsWith("$")) JsonPath(query) - else if(query.startsWith("/")) XmlPath(query) + if(query.startsWith("$")) JsonPath(query, UnknownParserInfo) + else if(query.startsWith("/")) XmlPath(query, UnknownParserInfo) else throw new Exception("Impossible to parse query: " + query) } + def createParserInfo(context: ParserRuleContext, considerFullContext: Boolean = false): ParserInfo = { + val start = context.getStart + val end = context.getStop + val endPosition = if(considerFullContext) Some(end.getCharPositionInLine + end.getText.length) else Some(end.getCharPositionInLine) + new ParserInfo(Some(start.getLine), Some(start.getCharPositionInLine), Some(end.getLine), endPosition) + } + } diff --git a/src/main/scala/com/herminiogarcia/shexml/shex/ShExGeneratorVisitor.scala b/src/main/scala/com/herminiogarcia/shexml/shex/ShExGeneratorVisitor.scala index 5fde7bc..7caeaf2 100644 --- a/src/main/scala/com/herminiogarcia/shexml/shex/ShExGeneratorVisitor.scala +++ b/src/main/scala/com/herminiogarcia/shexml/shex/ShExGeneratorVisitor.scala @@ -1,6 +1,7 @@ package com.herminiogarcia.shexml.shex import com.herminiogarcia.shexml.ast._ +import com.herminiogarcia.shexml.helper.ShapesGenerationError import com.herminiogarcia.shexml.visitor.DefaultVisitor import com.typesafe.scalalogging.Logger @@ -15,8 +16,9 @@ class ShExGeneratorVisitor(inferences: List[ShExMLInferredCardinalitiesAndDataty val prefixTable = mutable.HashMap[String, String]() override def doVisit(ast: com.herminiogarcia.shexml.ast.AST, optionalArgument: HashMap[String, String]): ShExSubsetAST = ast match { - case ShExML(declarations, graphs, shapes) => { - val prefixes = (declarations :+ Declaration(com.herminiogarcia.shexml.ast.Prefix(Var("rdf:"), URL("http://www.w3.org/1999/02/22-rdf-syntax-ns#")))) + case ShExML(declarations, graphs, shapes, _) => { + val prefixes = (declarations :+ Declaration(com.herminiogarcia.shexml.ast.Prefix(Var("rdf:"), + URL("http://www.w3.org/1999/02/22-rdf-syntax-ns#", UnknownParserInfo), UnknownParserInfo), UnknownParserInfo)) .filter(_.declarationStatement.isInstanceOf[com.herminiogarcia.shexml.ast.Prefix]) .map(doVisit(_, optionalArgument).asInstanceOf[com.herminiogarcia.shexml.shex.Prefix]) val shexGraphs = graphs.map(doVisit(_, optionalArgument).asInstanceOf[com.herminiogarcia.shexml.shex.Graph]) @@ -27,22 +29,22 @@ class ShExGeneratorVisitor(inferences: List[ShExMLInferredCardinalitiesAndDataty result } - case Declaration(declarationStatement) => { + case Declaration(declarationStatement, _) => { doVisit(declarationStatement, optionalArgument) } - case com.herminiogarcia.shexml.ast.Prefix(name, url) => { + case com.herminiogarcia.shexml.ast.Prefix(name, url, _) => { prefixTable += (name.name -> url.value) com.herminiogarcia.shexml.shex.Prefix(name.name, url) } - case com.herminiogarcia.shexml.ast.Graph(graphName, shapes) => { + case com.herminiogarcia.shexml.ast.Graph(graphName, shapes, _) => { logger.info(s"Extracting ShEx for ${shapes.size} shapes within the graph ${graphName.prefix + graphName.name}") val shexShapes = shapes.map(doVisit(_, optionalArgument).asInstanceOf[com.herminiogarcia.shexml.shex.Shape]) com.herminiogarcia.shexml.shex.Graph(graphName.prefix + graphName.name, shexShapes) } - case com.herminiogarcia.shexml.ast.Shape(shapeName, action, predicateObjects, _) => { + case com.herminiogarcia.shexml.ast.Shape(shapeName, action, predicateObjects, _, _) => { logger.info(s"Extracting shape ${shapeName.name} with ${predicateObjects.size} predicate-object statements") val shapePrefix = getShapePrefix(action) val arguments = HashMap("shapeName" -> shapeName.name) @@ -52,7 +54,7 @@ class ShExGeneratorVisitor(inferences: List[ShExMLInferredCardinalitiesAndDataty com.herminiogarcia.shexml.shex.Shape(name, iriStart, shexPredicateObjects) } - case com.herminiogarcia.shexml.ast.PredicateObject(predicate, objectOrShapeLink) => { + case com.herminiogarcia.shexml.ast.PredicateObject(predicate, objectOrShapeLink, _) => { val fullPrefix = prefixTable.getOrElse(predicate.prefix, "") val arguments = optionalArgument + ("predicateIRI" -> (fullPrefix + predicate.`extension`)) val shexPredicate = doVisit(predicate, optionalArgument).asInstanceOf[com.herminiogarcia.shexml.shex.Predicate] @@ -60,11 +62,11 @@ class ShExGeneratorVisitor(inferences: List[ShExMLInferredCardinalitiesAndDataty com.herminiogarcia.shexml.shex.PredicateObject(shexPredicate, objectElement) } - case com.herminiogarcia.shexml.ast.Predicate(prefix, localname) => { + case com.herminiogarcia.shexml.ast.Predicate(prefix, localname, _) => { com.herminiogarcia.shexml.shex.Predicate(prefix, localname) } - case com.herminiogarcia.shexml.ast.ObjectElement(prefix, _, literalValue, _, _, dataType, langTag, _) => { + case com.herminiogarcia.shexml.ast.ObjectElement(prefix, _, literalValue, _, _, dataType, langTag, _, _) => { val shapeName = optionalArgument("shapeName") val predicateIRI = optionalArgument("predicateIRI") val cardinality = getInferredCardinality(shapeName, predicateIRI) @@ -84,25 +86,25 @@ class ShExGeneratorVisitor(inferences: List[ShExMLInferredCardinalitiesAndDataty case None => if(prefix.isEmpty) { if(langTag.isDefined) langTag.get match { - case LangTagLiteral(value) => FixedValue("@" + value) - case LangTagGeneration(action, matcher) => throw new Exception("ShEx generation with dynamic langtag is not yet supported!") + case LangTagLiteral(value, _) => FixedValue("@" + value) + case LangTagGeneration(action, matcher, parserInfo) => throw ShapesGenerationError("ShEx generation with dynamic langtag is not yet supported!", parserInfo) } else ObjectDefinition(shexDatatype, cardinality) } - else PartialFixedValue(prefix, cardinality) + else PartialFixedValue(prefix.get.name, cardinality) } } - case com.herminiogarcia.shexml.ast.ShapeLink(shapeVar) => { + case com.herminiogarcia.shexml.ast.ShapeLink(shapeVar, _) => { com.herminiogarcia.shexml.shex.ShapeLink(shapeVar.name) } - case com.herminiogarcia.shexml.ast.LiteralObject(prefix, value) => { + case com.herminiogarcia.shexml.ast.LiteralObject(prefix, value, _) => { FixedValue(prefix.name + value) } - case com.herminiogarcia.shexml.ast.LiteralObjectValue(value) => { + case com.herminiogarcia.shexml.ast.LiteralObjectValue(value, _) => { FixedValue('"' + value + '"') } @@ -124,8 +126,8 @@ class ShExGeneratorVisitor(inferences: List[ShExMLInferredCardinalitiesAndDataty } protected def getShapePrefix(action: ActionOrLiteral): String = action match { - case Action(shapePrefix, _, _) => shapePrefix - case LiteralSubject(prefix, _) => prefix.name + case Action(shapePrefix, _, _, _) => shapePrefix.name + case LiteralSubject(prefix, _, _) => prefix.name } override def doVisitDefault(): ShExSubsetAST = ??? diff --git a/src/main/scala/com/herminiogarcia/shexml/visitor/DefaultUnitVisitor.scala b/src/main/scala/com/herminiogarcia/shexml/visitor/DefaultUnitVisitor.scala index 9ba9015..d7984f9 100644 --- a/src/main/scala/com/herminiogarcia/shexml/visitor/DefaultUnitVisitor.scala +++ b/src/main/scala/com/herminiogarcia/shexml/visitor/DefaultUnitVisitor.scala @@ -9,55 +9,55 @@ abstract class DefaultUnitVisitor { def visit(ast: AST): Unit = ast match { - case ShExML(declarations, graphs, shapes) => { + case ShExML(declarations, graphs, shapes, _) => { declarations.foreach(doVisit) graphs.foreach(doVisit) shapes.foreach(doVisit) } - case Declaration(declarationStatement) => doVisit(declarationStatement) + case Declaration(declarationStatement, _) => doVisit(declarationStatement) - case Graph(_, shapes) => shapes.foreach(doVisit) + case Graph(_, shapes, _) => shapes.foreach(doVisit) - case Shape(shapeName, action, predicateObjects, _) => { + case Shape(shapeName, action, predicateObjects, _, _) => { doVisit(shapeName) doVisit(action) predicateObjects.foreach(doVisit) } - case Query(_, queryClause) => doVisit(queryClause) + case Query(_, queryClause, _) => doVisit(queryClause) - case Expression(_, exp) => doVisit(exp) + case Expression(_, exp, _) => doVisit(exp) - case Union(left, right) => { + case Union(left, right, _) => { doVisit(left) doVisit(right) } - case Substitution(leftUnion, rightUnion, joinClause) => { + case Substitution(leftUnion, rightUnion, joinClause, _) => { doVisit(leftUnion) doVisit(rightUnion) doVisit(joinClause) } - case Join(leftUnion, rightUnion, leftJoinClause, rightJoinClause) => { + case Join(leftUnion, rightUnion, leftJoinClause, rightJoinClause, _) => { doVisit(leftUnion) doVisit(rightUnion) doVisit(leftJoinClause) doVisit(rightJoinClause) } - case StringOperation(left, right, _) => { + case StringOperation(left, right, _, _) => { doVisit(left) doVisit(right) } - case PredicateObject(objectOrShapeLink, predicate) => { + case PredicateObject(objectOrShapeLink, predicate, _) => { doVisit(objectOrShapeLink) doVisit(predicate) } - case ObjectElement(_, action, _, _, _, _, _, _) => action match { case Some(value) => doVisit(value) } + case ObjectElement(_, action, _, _, _, _, _, _, _) => action match { case Some(value) => doVisit(value) } case _ => // do nothing } diff --git a/src/main/scala/com/herminiogarcia/shexml/visitor/DefaultVisitor.scala b/src/main/scala/com/herminiogarcia/shexml/visitor/DefaultVisitor.scala index 0541c3d..31ed172 100644 --- a/src/main/scala/com/herminiogarcia/shexml/visitor/DefaultVisitor.scala +++ b/src/main/scala/com/herminiogarcia/shexml/visitor/DefaultVisitor.scala @@ -9,54 +9,54 @@ abstract class DefaultVisitor[A, B] { def visit(ast: AST, optionalArgument: A): B = ast match { - case ShExML(declarations, graphs, shapes) => { + case ShExML(declarations, graphs, shapes, _) => { declarations.foreach(doVisit(_, optionalArgument)) val firstGraph = graphs.map(doVisit(_, optionalArgument)).headOption val firstShape = shapes.map(doVisit(_, optionalArgument)).headOption if(firstGraph.isEmpty) firstShape.get else firstGraph.get } - case Declaration(declarationStatement) => doVisit(declarationStatement, optionalArgument) + case Declaration(declarationStatement, _) => doVisit(declarationStatement, optionalArgument) - case Shape(shapeName, action, predicateObjects, _) => { + case Shape(shapeName, action, predicateObjects, _, _) => { doVisit(shapeName, optionalArgument) predicateObjects.foreach(doVisit(_, optionalArgument)) doVisit(action, optionalArgument) } - case Query(_, queryClause) => doVisit(queryClause, optionalArgument) + case Query(_, queryClause, _) => doVisit(queryClause, optionalArgument) - case Expression(_, exp) => doVisit(exp, optionalArgument) + case Expression(_, exp, _) => doVisit(exp, optionalArgument) - case Union(left, right) => { + case Union(left, right, _) => { doVisit(left, optionalArgument) doVisit(right, optionalArgument) } - case Substitution(leftUnion, rightUnion, joinClause) => { + case Substitution(leftUnion, rightUnion, joinClause, _) => { doVisit(leftUnion, optionalArgument) doVisit(rightUnion, optionalArgument) doVisit(joinClause, optionalArgument) } - case Join(leftUnion, rightUnion, leftJoinClause, rightJoinClause) => { + case Join(leftUnion, rightUnion, leftJoinClause, rightJoinClause, _) => { doVisit(leftUnion, optionalArgument) doVisit(rightUnion, optionalArgument) doVisit(leftJoinClause, optionalArgument) doVisit(rightJoinClause, optionalArgument) } - case StringOperation(left, right, _) => { + case StringOperation(left, right, _, _) => { doVisit(left, optionalArgument) doVisit(right, optionalArgument) } - case PredicateObject(objectOrShapeLink, predicate) => { + case PredicateObject(objectOrShapeLink, predicate, _) => { doVisit(objectOrShapeLink, optionalArgument) doVisit(predicate, optionalArgument) } - case ObjectElement(_, action, _, _, _, _, _, _) => action match { case Some(value) => doVisit(value, optionalArgument) } + case ObjectElement(_, action, _, _, _, _, _, _, _) => action match { case Some(value) => doVisit(value, optionalArgument) } case _ => doVisitDefault() diff --git a/src/main/scala/com/herminiogarcia/shexml/visitor/JdbcDriverRegistry.scala b/src/main/scala/com/herminiogarcia/shexml/visitor/JdbcDriverRegistry.scala index f50e9d9..247e3ba 100644 --- a/src/main/scala/com/herminiogarcia/shexml/visitor/JdbcDriverRegistry.scala +++ b/src/main/scala/com/herminiogarcia/shexml/visitor/JdbcDriverRegistry.scala @@ -1,5 +1,8 @@ package com.herminiogarcia.shexml.visitor +import com.herminiogarcia.shexml.ast.ParserInfo +import com.herminiogarcia.shexml.helper.JDBCDriverError + trait JdbcDriverRegistry { val jdbcDrivers = Map( @@ -14,10 +17,10 @@ trait JdbcDriverRegistry { "jdbc:h2" -> "org.h2.Driver" ) - def lookForJdbcDriver(jdbcURL: String, moreDrivers: Map[String, String] = Map()): String = { + def lookForJdbcDriver(jdbcURL: String, moreDrivers: Map[String, String] = Map(), parserInfo: ParserInfo): String = { (jdbcDrivers ++ moreDrivers).find(t => jdbcURL.startsWith(t._1)) match { case Some(driver) => driver._2 - case None => throw new Exception("No driver found for URL: " + jdbcURL) + case None => throw JDBCDriverError("No driver found for URL: " + jdbcURL, parserInfo) } } diff --git a/src/main/scala/com/herminiogarcia/shexml/visitor/PushedOrPoppedValueSearchVisitor.scala b/src/main/scala/com/herminiogarcia/shexml/visitor/PushedOrPoppedValueSearchVisitor.scala index 02808e2..0303e7a 100644 --- a/src/main/scala/com/herminiogarcia/shexml/visitor/PushedOrPoppedValueSearchVisitor.scala +++ b/src/main/scala/com/herminiogarcia/shexml/visitor/PushedOrPoppedValueSearchVisitor.scala @@ -6,23 +6,23 @@ class PushedOrPoppedValueSearchVisitor extends DefaultVisitor[Any, Boolean] { override def doVisit(ast: AST, optionalArgument: Any): Boolean = ast match { - case ShExML(declarations, graphs, shapes) => { + case ShExML(declarations, graphs, shapes, _) => { declarations.map(doVisit(_, optionalArgument)).find(_.self).getOrElse(false) } - case Iterator(_, _, fields, iterators) => { + case Iterator(_, _, fields, iterators, _) => { val resultInFields = fields.map(doVisit(_, optionalArgument)).find(_.self).getOrElse(false) val resultsInIterators = iterators.map(doVisit(_, optionalArgument)).find(_.self).getOrElse(false) resultInFields || resultsInIterators } - case NestedIterator(_, _, fields, iterators) => { + case NestedIterator(_, _, fields, iterators, _) => { val resultInFields = fields.map(doVisit(_, optionalArgument)).find(_.self).getOrElse(false) val resultsInIterators = iterators.map(doVisit(_, optionalArgument)).find(_.self).getOrElse(false) resultInFields || resultsInIterators } - case Field(_, _, pushed, popped) => pushed || popped + case Field(_, _, pushed, popped, _) => pushed || popped case default => visit(default, optionalArgument) } diff --git a/src/main/scala/com/herminiogarcia/shexml/visitor/QuerySearcher.scala b/src/main/scala/com/herminiogarcia/shexml/visitor/QuerySearcher.scala index d05ef76..8e82f89 100644 --- a/src/main/scala/com/herminiogarcia/shexml/visitor/QuerySearcher.scala +++ b/src/main/scala/com/herminiogarcia/shexml/visitor/QuerySearcher.scala @@ -1,7 +1,7 @@ package com.herminiogarcia.shexml.visitor import com.herminiogarcia.shexml.ast._ -import com.herminiogarcia.shexml.helper.SourceHelper +import com.herminiogarcia.shexml.helper.{FileError, QueryError, SourceHelper, VarError} class QuerySearcher(val varTable: Map[Variable, VarResult]) { @@ -15,13 +15,13 @@ class QuerySearcher(val varTable: Map[Variable, VarResult]) { case Some(result) => result match { case qv: QueryClause => qv case u: URL => getURLContents(u) - case _ => throw new Exception("Result of variable " + v.name + " not suitable as a query") + case _ => throw QueryError("Result of variable " + v.name + " not suitable as a query", variable.parserInfo) } - case None => throw new Exception("Variable " + v.name + " not defined") + case None => throw VarError("Variable " + v.name + " not defined", variable.parserInfo) } } - case None => throw new Exception("Variable " + variable.name + " not found") + case None => throw VarError("Variable " + variable.name + " not found", variable.parserInfo) } } @@ -30,14 +30,14 @@ class QuerySearcher(val varTable: Map[Variable, VarResult]) { val extension = parts(parts.length - 1) val file = new SourceHelper().getURLContent(u.value) if(extension == "xpath") { - XmlPath(file.fileContent) + XmlPath(file.fileContent, u.parserInfo) } else if(extension == "jsonpath") { - JsonPath(file.fileContent) + JsonPath(file.fileContent, u.parserInfo) } else if(extension == "sql") { - SqlQuery(file.fileContent) + SqlQuery(file.fileContent, u.parserInfo) } else if(extension == "sparql") { - SparqlQuery(file.fileContent) - } else throw new Exception("File extension " + extension + " is not supported for queries") + SparqlQuery(file.fileContent, u.parserInfo) + } else throw FileError("File extension " + extension + " is not supported for queries", u.parserInfo) } } diff --git a/src/main/scala/com/herminiogarcia/shexml/visitor/RDFGeneratorVisitor.scala b/src/main/scala/com/herminiogarcia/shexml/visitor/RDFGeneratorVisitor.scala index 72a0233..dfd80c3 100644 --- a/src/main/scala/com/herminiogarcia/shexml/visitor/RDFGeneratorVisitor.scala +++ b/src/main/scala/com/herminiogarcia/shexml/visitor/RDFGeneratorVisitor.scala @@ -1,8 +1,8 @@ package com.herminiogarcia.shexml.visitor import com.github.tototoshi.csv.{CSVReader, DefaultCSVFormat} -import com.herminiogarcia.shexml.ast.{AST, Action, ActionOrLiteral, AutoIncrement, BuiltinFunction, CSVPerRow, DataTypeGeneration, DataTypeLiteral, Declaration, Exp, FieldQuery, FilePath, FilePathOrStdin, FunctionCalling, Graph, Index, IteratorQuery, JdbcURL, Join, JsonPath, LangTagGeneration, LangTagLiteral, LiteralObject, LiteralObjectValue, LiteralSubject, Matcher, Matchers, ObjectElement, Predicate, PredicateObject, Prefix, QueryClause, RDFAlt, RDFBag, RDFCollection, RDFList, RDFSeq, RelativePath, ShExML, Shape, ShapeLink, ShapeVar, Sparql, SparqlColumn, Sql, SqlColumn, Stdin, StringOperation, Substitution, URL, Union, Var, VarResult, Variable, XmlPath} -import com.herminiogarcia.shexml.helper.{FunctionHubExecutor, LoadedSource, ParallelExecutionConfigurator, SourceHelper} +import com.herminiogarcia.shexml.ast.{AST, Action, ActionOrLiteral, AutoIncrement, BuiltinFunction, CSVPerRow, DataTypeGeneration, DataTypeLiteral, Declaration, Exp, FieldQuery, FilePath, FilePathOrStdin, FunctionCalling, Graph, Index, IteratorQuery, JdbcURL, Join, JsonPath, LangTagGeneration, LangTagLiteral, LiteralObject, LiteralObjectValue, LiteralSubject, Matcher, Matchers, ObjectElement, ParserInfo, Predicate, PredicateObject, Prefix, QueryClause, RDFAlt, RDFBag, RDFCollection, RDFList, RDFSeq, RelativePath, ShExML, Shape, ShapeLink, ShapeVar, Sparql, SparqlColumn, Sql, SqlColumn, Stdin, StringOperation, Substitution, URL, Union, Var, VarResult, Variable, XmlPath} +import com.herminiogarcia.shexml.helper.{FunctionHubExecutor, LoadedSource, ParallelExecutionConfigurator, RDFGenerationError, SourceHelper} import com.herminiogarcia.shexml.shex.{Node, ShExMLInferredCardinalitiesAndDatatypes, ShapeMapInference, ShapeMapShape} import com.herminiogarcia.shexml.visitor import com.jayway.jsonpath.{Configuration, DocumentContext} @@ -62,11 +62,11 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], override def doVisit(ast: AST, optionalArgument: Any): Any = ast match { - case ShExML(declarations, graphs, shapes) => { + case ShExML(declarations, graphs, shapes, _) => { declarations.foreach(doVisit(_, optionalArgument)) val firstGraph = graphs.map(doVisit(_, optionalArgument)).headOption val linkedShapes = shapes.flatMap(_.predicateObjects.flatMap(_.objectOrShapeLink match { - case ShapeLink(shapeVar) => List(shapeVar) + case ShapeLink(shapeVar, _) => List(shapeVar) case _ => List() })) val filteredShapes = shapes.filterNot(s => linkedShapes.contains(s.shapeName)) @@ -86,17 +86,17 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], if(firstShape.isEmpty) firstGraph.get } - case Declaration(declarationStatement) => { + case Declaration(declarationStatement, _) => { if(declarationStatement.isInstanceOf[Prefix]) doVisit(declarationStatement, optionalArgument) } - case Prefix(variable, url) => { + case Prefix(variable, url, _) => { prefixTable += ((variable.name, url.value)) defaultModel.setNsPrefix(variable.name.replace(":", ""), url.value) } - case Graph(graphName, shapes) => { + case Graph(graphName, shapes, _) => { logger.info(s"Generating ${shapes.size} shapes within $graphName graph") val shapesHead = shapes.headOption val shapesTail = shapes.tail @@ -109,7 +109,7 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], ) } - case Shape(shapeName, action, predicateObjects, holdingGraph) => { + case Shape(shapeName, action, predicateObjects, holdingGraph, _) => { logger.info(s"Generating shape ${shapeName.name} results with ${predicateObjects.size} predicate-object statements") val shapePrefix = getShapePrefix(action) val graphName = holdingGraph.map(g => prefixTable.getOrElse(g.graphName.prefix, "") + g.graphName.name).getOrElse("") @@ -181,7 +181,7 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], }), r.dataType, r.langTag, r.rdfCollection)) } - case PredicateObject(predicate, objectOrShapeLink) => { + case PredicateObject(predicate, objectOrShapeLink, _) => { val predicateResult = doVisit(predicate, optionalArgument) val objectResult = doVisit(objectOrShapeLink, optionalArgument) if(predicateResult != null && objectResult != null) @@ -196,16 +196,16 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], else Nil } - case Predicate(prefix, extension) => { + case Predicate(prefix, extension, _) => { prefixTable(prefix) + extension } - case ObjectElement(prefix, action, literalValue, matcher, condition, dataType, langTag, rdfCollection) => { + case ObjectElement(prefix, action, literalValue, matcher, condition, dataType, langTag, rdfCollection, parserInfo) => { val result = action match { case Some(value) => doVisit(value, optionalArgument) case None => literalValue match { case Some(literal) => doVisit(literal, optionalArgument) - case None => throw new Exception("No generation clause given.") + case None => throw RDFGenerationError("No generation clause given.", parserInfo) } } val matchedResultList = matcher match { @@ -218,7 +218,7 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], result match { case _: List[Result] => matchedResultList.asInstanceOf[List[Result]].map(result => { - val newResults = result.results.map(prefixTable.getOrElse(prefix, "") + _) + val newResults = result.results.map(prefix.flatMap(p => prefixTable.get(p.name)).getOrElse("") + _) val dataTypeValue = dataTypeResult.map({ case dataTypeResults: List[Result] => dataTypeResults.filter(_.id == result.id).head.results.head case value: String => value @@ -237,19 +237,19 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], }).filter(_ != Nil) case ResultAutoIncrement(iterator, predicate, _, _, _) => val dataTypeValue = dataTypeResult.map({ - case _: List[Result] => throw new Exception("Autoincrement values cannot have a generated dataType") + case _: List[Result] => throw RDFGenerationError("Autoincrement values cannot have a generated dataType", parserInfo) case value: String => value }) val langTagValue = langTagResult.map({ - case _: List[Result] => throw new Exception("Autoincrement values cannot have a generated langTag") + case _: List[Result] => throw RDFGenerationError("Autoincrement values cannot have a generated langTag", parserInfo) case value: String => value }) - visitor.ResultAutoIncrement(iterator, predicate, prefixTable.getOrElse(prefix, ""), normaliseDataType(dataTypeValue), langTagValue) + visitor.ResultAutoIncrement(iterator, predicate, prefix.flatMap(p => prefixTable.get(p.name)).getOrElse(""), normaliseDataType(dataTypeValue), langTagValue) case _ => result } } - case Union(left, right) => { + case Union(left, right, parserInfo) => { val expName = optionalArgument.asInstanceOf[Map[String, Any]].getOrElse("varName", "") val leftList = doVisit(left, optionalArgument) val rightList = doVisit(right, optionalArgument) @@ -262,13 +262,13 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], iteratorsCombinations += value k -> leftResult.union(rightResult) }).toMap - case _ => throw new Exception("Impossible to combine an iterator with a non iterator expression") + case _ => throw RDFGenerationError("Impossible to combine an iterator with a non iterator expression", parserInfo) } case l: List[Result] => l.union(rightList.asInstanceOf[List[Result]]) } } - case Substitution(left, right, join) => { + case Substitution(left, right, join, parserInfo) => { val expName = optionalArgument.asInstanceOf[Map[String, Any]].getOrElse("varName", "") val leftList = doVisit(left, optionalArgument) val rightList = doVisit(right, optionalArgument) @@ -283,15 +283,15 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], val value = s"$expName$k" -> getSubstitutionResults(leftResult, rightResult, joinResult) iteratorsCombinations += value }) - case _ => throw new Exception("Cannot join iterator with non iterator expression. Join clause is not an iterator expression") + case _ => throw RDFGenerationError("Cannot join iterator with non iterator expression. Join clause is not an iterator expression", parserInfo) } - case _ => throw new Exception("Cannot join iterator with non iterator expression. Right clause is not an iterator expression") + case _ => throw RDFGenerationError("Cannot join iterator with non iterator expression. Right clause is not an iterator expression", parserInfo) } case left: List[Result] => getSubstitutionResults(left, rightList.asInstanceOf[List[Result]], joinList.asInstanceOf[List[Result]]) } } - case Join(left, right, leftClause, rightClause) => { + case Join(left, right, leftClause, rightClause, parserInfo) => { val expName = optionalArgument.asInstanceOf[Map[String, Any]].getOrElse("varName", "") val leftList = doVisit(left, optionalArgument) val rightList = doVisit(right, optionalArgument) @@ -307,17 +307,17 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], val value = s"$expName$k" -> getJoinResults(leftResult, rightResult, l, r) iteratorsCombinations += value }) - case _ => throw new Exception("Cannot use an iterator expression for the join condition clause. Right condition is not a plain expression") + case _ => throw RDFGenerationError("Cannot use an iterator expression for the join condition clause. Right condition is not a plain expression", parserInfo) } - case _ => throw new Exception("Cannot use an iterator expression for the join condition clause. Left condition is not a plain expression") + case _ => throw RDFGenerationError("Cannot use an iterator expression for the join condition clause. Left condition is not a plain expression", parserInfo) } - case _ => throw new Exception("Cannot join iterator with non iterator expression. Right clause is not an iterator expression") + case _ => throw RDFGenerationError("Cannot join iterator with non iterator expression. Right clause is not an iterator expression", parserInfo) } - case _ => throw new Exception("Cannot join iterator with non iterator expression. Left clause is not an iterator expression") + case _ => throw RDFGenerationError("Cannot join iterator with non iterator expression. Left clause is not an iterator expression", parserInfo) } } - case StringOperation(left, right, unionString) => { + case StringOperation(left, right, unionString, parserInfo) => { val expName = optionalArgument.asInstanceOf[Map[String, Any]].getOrElse("varName", "") val leftList = doVisit(left, optionalArgument) val rightList = doVisit(right, optionalArgument) @@ -329,7 +329,7 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], val value = s"$expName$k" -> getStringOperationResults(leftResult, rightResult, unionString) iteratorsCombinations += value }) - case _ => throw new Exception("Cannot make string operation with left clause being an iterator expression and right clause a non iterator expression") + case _ => throw RDFGenerationError("Cannot make string operation with left clause being an iterator expression and right clause a non iterator expression", parserInfo) } case left: List[Result] => getStringOperationResults(left, rightList.asInstanceOf[List[Result]], unionString) } @@ -360,13 +360,13 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], case None => { val iteratorName = varList.tail.head.name val values = varTable.keys.filter { - case Var(name) => name.contains(iteratorName) + case Var(name, _) => name.contains(iteratorName) case _ => false }.toList.sortWith { case (a: Var, b: Var) => a.name.size < b.name.size }.map { case v: Var => v.name.replaceFirst(iteratorName, "") -> { - val vars = v.name.split("[.]").map(Var.apply).toList + val vars = v.name.split("[.]").map(Var(_, v.parserInfo)).toList if (vars.size > 1) doIteratorQuery(vars, middleArguments, file.asInstanceOf[LoadedSource]) else Nil @@ -383,11 +383,13 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], } } } else if (varTable(varList.head).isInstanceOf[Exp] && varList.size > 1) { - iteratorsCombinations(varList.map(_.name).mkString(".")) + val composedVar = varList.map(_.name).mkString(".") + iteratorsCombinations.getOrElse(composedVar, + throw RDFGenerationError(s"Variable $composedVar does not resolve to any reference, check the used variable or the iterator definition", i.composedVar.parserInfo)) } else if (varList.size >= 3) { doIteratorQuery(varList.slice(1, varList.size), middleArguments, file.asInstanceOf[LoadedSource]) } else { - throw new Exception("Bad number of vars") + throw RDFGenerationError("Bad number of vars", i.parserInfo) } }) val finalResult = results.headOption match { @@ -409,13 +411,13 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], case Some(executor) => executor case None => val loadedSource = functionsIRI match { - case JdbcURL(jdbcURL) => throw new Exception(s"The JDBC URL $jdbcURL cannot be used as the source of functions.") + case JdbcURL(jdbcURL, _) => throw RDFGenerationError(s"The JDBC URL $jdbcURL cannot be used as the source of functions.", f.parserInfo) case fp: FilePath => doVisit(fp, optionalArgument).asInstanceOf[List[LoadedSource]].headOption match { case Some(ls) => ls - case None => throw new Exception(s"There is no functions code in the provided path: ${fp.value}") + case None => throw RDFGenerationError(s"There is no functions code in the provided path: ${fp.value}", f.parserInfo) } } - val executor = new FunctionHubExecutor(loadedSource) + val executor = new FunctionHubExecutor(loadedSource, f.parserInfo) functionHubExecuterCache.save(functionsIRI.value, executor) executor } @@ -427,7 +429,7 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], val sizeArguments = argumentsResults.filter(_.forall(_.isInstanceOf[Result])).map(_.size) val maxSize = if(sizeArguments.isEmpty) 1 else argumentsResults.filter(_.forall(_.isInstanceOf[Result])).map(_.size).max val sameSizeArguments = argumentsResults.exists(rl => rl.forall(r => r.isInstanceOf[Result]) && rl.size != maxSize) - if(sameSizeArguments) throw new Exception(s"The results of the function arguments are not equal in size. This prevents a correct execution of the function.") + if(sameSizeArguments) throw RDFGenerationError(s"The results of the function arguments are not equal in size. This prevents a correct execution of the function.", f.parserInfo) val argumentsHead = argumentsResults.find(_.forall(_.isInstanceOf[Result])) val argumentsResultsFinal = argumentsResults.map(r => { if(r.nonEmpty && r.head.isInstanceOf[ResultAutoIncrement]) { @@ -471,7 +473,7 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], doVisit(varTable(sv), optionalArgument) } - case JsonPath(query) => { + case JsonPath(query, _) => { logger.debug(s"Doing JSONPath query: $query") val arguments = optionalArgument.asInstanceOf[Map[String, Any]] val iteratorQuery = arguments.getOrElse("iteratorQuery", "").toString @@ -509,7 +511,7 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], } } - case XmlPath(query) => { + case XmlPath(query, _) => { logger.debug(s"Doing XPath query: $query") val arguments = optionalArgument.asInstanceOf[Map[String, Any]] val iteratorQuery = arguments.getOrElse("iteratorQuery", "").toString @@ -544,31 +546,31 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], ResultAutoIncrement(a, "", "", None, None) } - case Matchers(_, matchers) => { + case Matchers(_, matchers, _) => { val listToMatch = optionalArgument.asInstanceOf[List[Result]] listToMatch.map(r => visitor.Result(r.id, r.rootIds, r.results.map(s => { matchers.matchers.foldLeft(s)((value, matcher) => doVisit(matcher, value).asInstanceOf[String]) }), r.dataType, r.langTag, r.rdfCollection)) } - case Matcher(replacedStrings, replacement) => { + case Matcher(replacedStrings, replacement, _) => { if(replacedStrings.strings.contains(optionalArgument.toString)) replacement else optionalArgument.toString } - case LiteralSubject(prefix, value) => { + case LiteralSubject(prefix, value, _) => { List(Result(Option.empty, HashSet(), List(value), None, None, None)) } - case LiteralObject(prefix, value) => { + case LiteralObject(prefix, value, _) => { val prefixValue = prefixTable(prefix.name) List(Result(Option.empty, HashSet(), List(prefixValue + value), None, None, None)) } - case LiteralObjectValue(value) => { + case LiteralObjectValue(value, _) => { List(Result(Option.empty, HashSet(), List(value), None, None, None)) } - case DataTypeGeneration(prefix, action, matcher) => { + case DataTypeGeneration(prefix, action, matcher, _) => { val result = doVisit(action, optionalArgument) val matchedResultList = matcher match { case Some(matcherVar) => doVisit(matcherVar, result) @@ -577,14 +579,14 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], result match { case _: List[Result] => matchedResultList.asInstanceOf[List[Result]].map(result => { - val newResults = result.results.map(prefixTable.getOrElse(prefix, "") + _) + val newResults = result.results.map(prefix.flatMap(p => prefixTable.get(p.name)).getOrElse("") + _) Result(result.id, result.rootIds, newResults, None, None, None) }) case _ => result } } - case LangTagGeneration(action, matcher) => { + case LangTagGeneration(action, matcher, _) => { val result = doVisit(action, optionalArgument) val matchedResultList = matcher match { case Some(matcherVar) => doVisit(matcherVar, result) @@ -593,30 +595,30 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], matchedResultList } - case DataTypeLiteral(value) => { + case DataTypeLiteral(value, _) => { value } - case LangTagLiteral(value) => { + case LangTagLiteral(value, _) => { value } - case ShapeLink(shapeVar) => { + case ShapeLink(shapeVar, _) => { doVisit(shapeVar, optionalArgument) } - case URL(url) => + case URL(url, parserInfo) => logger.debug(s"Selecting file $url") if(isRDFSource(url)) List(LoadedSource("", url)) else if(url.contains('*') && url.startsWith("file://")) getAllFilesContents(url) else if(url.contains('*')) - throw new Exception("* wildcard not allowed over remote files") + throw RDFGenerationError("* wildcard not allowed over remote files", parserInfo) else List(new SourceHelper().getURLContent(url)) - case RelativePath(path) => + case RelativePath(path, _) => if(isRDFSource(path)) { val fileAbsolutePath = new File(path).getAbsolutePath val fileProtocol = @@ -627,9 +629,9 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], else if(path.contains('*')) getAllFilesContents(path) else List(new SourceHelper().getContentFromRelativePath(path)) - case JdbcURL(url) => List(LoadedSource("", url)) + case JdbcURL(url, _) => List(LoadedSource("", url)) - case Stdin() => List(new SourceHelper().getStdinContents()) + case Stdin(_) => List(new SourceHelper().getStdinContents()) case default => visit(default, optionalArgument) @@ -719,8 +721,8 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], } protected def doVisitIteratorQuery(nestedIterator: QueryClause, currentIterator: QueryClause, optionalArgument: Any): Result = nestedIterator match { - case JsonPath(query) => doVisit(JsonPath(query + currentIterator.query), optionalArgument).asInstanceOf[Result] - case XmlPath(query) => doVisit(XmlPath(query + currentIterator.query), optionalArgument).asInstanceOf[Result] + case JsonPath(query, parserInfo) => doVisit(JsonPath(query + currentIterator.query, parserInfo), optionalArgument).asInstanceOf[Result] + case XmlPath(query, parserInfo) => doVisit(XmlPath(query + currentIterator.query, parserInfo), optionalArgument).asInstanceOf[Result] } protected def iteratorQueryToList(i: IteratorQuery): List[Var] = i.composedVar match { @@ -730,29 +732,29 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], protected def iteratorResultsToQueries(iteratorQueries: List[Resultable], query: QueryClause, rootIds: HashSet[Int], file: LoadedSource): List[QueryWithIndex] = iteratorQueries.flatMap({ case r: ResultWithIteratorQuery => r.results.indices.map(i => query match { - case XmlPath(xpathQuery) => { - val composedQuery = XmlPath(xpathQuery.replaceFirst("[*]", (i + 1).toString)) + case XmlPath(xpathQuery, parserInfo) => { + val composedQuery = XmlPath(xpathQuery.replaceFirst("[*]", (i + 1).toString), parserInfo) val rootId = (r.iteratorQuery + file.filepath + i.toString).hashCode QueryWithIndex(i.toString, rootIds.+(rootId), composedQuery, r.iteratorQuery) } - case JsonPath(jsonpathQuery) => { - val composedQuery = JsonPath(jsonpathQuery.replaceFirst("[*]", i.toString)) + case JsonPath(jsonpathQuery, parserInfo) => { + val composedQuery = JsonPath(jsonpathQuery.replaceFirst("[*]", i.toString), parserInfo) val rootId = (r.iteratorQuery + file.filepath + i.toString).hashCode QueryWithIndex(i.toString, rootIds.+(rootId), composedQuery, r.iteratorQuery) } }).toList case r: ResultWithNested => r.results.indices.flatMap(i => query match { - case XmlPath(xpathQuery) => { - val indicedQuery = XmlPath(xpathQuery.replaceFirst("[*]", (i + 1).toString)) + case XmlPath(xpathQuery, parserInfo) => { + val indicedQuery = XmlPath(xpathQuery.replaceFirst("[*]", (i + 1).toString), parserInfo) val rootId = (r.iteratorQuery + file.filepath + i.toString).hashCode iteratorResultsToQueries(List(r.nestedResults(i)), indicedQuery, rootIds.+(rootId), file) } - case JsonPath(jsonpathQuery) => { + case JsonPath(jsonpathQuery, parserInfo) => { val indicedQuery = if(r.iteratorQuery.contains("[*]")) - JsonPath(jsonpathQuery.replaceFirst("[*]", i.toString)) + JsonPath(jsonpathQuery.replaceFirst("[*]", i.toString), parserInfo) else - JsonPath(jsonpathQuery) + JsonPath(jsonpathQuery, parserInfo) val rootId = (r.iteratorQuery + file.filepath + i.toString).hashCode iteratorResultsToQueries(List(r.nestedResults(i)), indicedQuery, rootIds.+(rootId), file) } @@ -766,7 +768,7 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], val querySeparator = if(xs.isDefinedAt(0) && getQueryFromVarTable(Var(context + x.name + "." + xs.head.name)).query.startsWith("[")) "" else "." - val constructedQuery = JsonPath(j.query + querySeparator + generateFinalQuery(xs, context + x.name + ".", j).query) + val constructedQuery = JsonPath(j.query + querySeparator + generateFinalQuery(xs, context + x.name + ".", j).query, j.parserInfo) getQueryFromVarTable(Var(context + varList.map(_.name).mkString("."))) match { case f: FieldQuery => { if(f.pushed) { @@ -781,21 +783,21 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], case _ => constructedQuery } } - case xp: XmlPath => XmlPath(xp.query + "[*]/" + generateFinalQuery(xs, context + x.name + ".", xp).query) - case csv: CSVPerRow => CSVPerRow(generateFinalQuery(xs, context + x.name + ".", csv).query) - case sql: Sql => SqlColumn(sql.query, generateFinalQuery(xs, context + x.name + ".", sql).query) - case sp: Sparql => SparqlColumn(sp.query, generateFinalQuery(xs, context + x.name + ".", sp).query) - case FieldQuery(query, pushed, popped) => rootQuery match { + case xp: XmlPath => XmlPath(xp.query + "[*]/" + generateFinalQuery(xs, context + x.name + ".", xp).query, xp.parserInfo) + case csv: CSVPerRow => CSVPerRow(generateFinalQuery(xs, context + x.name + ".", csv).query, csv.parserInfo) + case sql: Sql => SqlColumn(sql.query, generateFinalQuery(xs, context + x.name + ".", sql).query, sql.parserInfo) + case sp: Sparql => SparqlColumn(sp.query, generateFinalQuery(xs, context + x.name + ".", sp).query, sp.parserInfo) + case FieldQuery(query, pushed, popped, _) => rootQuery match { case j: JsonPath => { val querySeparator = if(xs.isDefinedAt(0) && getQueryFromVarTable(Var(context + x.name + "." + xs.head.name)).query.startsWith("[")) "" else "." - JsonPath(query + querySeparator + generateFinalQuery(xs, context + x.name + ".", j).query) + JsonPath(query + querySeparator + generateFinalQuery(xs, context + x.name + ".", j).query, j.parserInfo) } - case xp: XmlPath => XmlPath(query + "[*]/" + generateFinalQuery(xs, context + x.name + ".", xp).query) - case csv: CSVPerRow => CSVPerRow(query) - case sql: Sql => SqlColumn(rootQuery.query, query) - case sp: Sparql => SparqlColumn(rootQuery.query, query) + case xp: XmlPath => XmlPath(query + "[*]/" + generateFinalQuery(xs, context + x.name + ".", xp).query, xp.parserInfo) + case csv: CSVPerRow => CSVPerRow(query, csv.parserInfo) + case sql: Sql => SqlColumn(rootQuery.query, query, sql.parserInfo) + case sp: Sparql => SparqlColumn(rootQuery.query, query, sp.parserInfo) } } @@ -806,11 +808,11 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], precedentQueries.map(q => { val xpathIteratorQueryEnd = "[*]" getQueryFromVarTable(Var(varContext + x.name)) match { - case JsonPath(query) => (doVisit(JsonPath(q + query), arguments).asInstanceOf[Result], q + query) - case XmlPath(query) => (doVisit(XmlPath(q + query), arguments).asInstanceOf[Result], q + query + xpathIteratorQueryEnd) - case FieldQuery(query, _, _) => rootQuery match { - case JsonPath(_) => (doVisit(JsonPath(q + query), arguments).asInstanceOf[Result], q + query) - case XmlPath(_) => (doVisit(XmlPath(q + query), arguments).asInstanceOf[Result], q + query + xpathIteratorQueryEnd) + case JsonPath(query, parserInfo) => (doVisit(JsonPath(q + query, parserInfo), arguments).asInstanceOf[Result], q + query) + case XmlPath(query, parserInfo) => (doVisit(XmlPath(q + query, parserInfo), arguments).asInstanceOf[Result], q + query + xpathIteratorQueryEnd) + case FieldQuery(query, _, _, parserInfo) => rootQuery match { + case JsonPath(_, parserInfo) => (doVisit(JsonPath(q + query, parserInfo), arguments).asInstanceOf[Result], q + query) + case XmlPath(_, parserInfo) => (doVisit(XmlPath(q + query, parserInfo), arguments).asInstanceOf[Result], q + query + xpathIteratorQueryEnd) } } }) @@ -818,14 +820,14 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], results.map(r => ResultWithIteratorQuery(r._1.id, r._1.rootIds, r._1.results, r._2)) case x :: xs => { val queries = precedentQueries.map(q => getQueryFromVarTable(Var(varContext + x.name)) match { - case JsonPath(query) => JsonPath(q + query) - case XmlPath(query) => XmlPath(q + query + "[*]") + case JsonPath(query, parserInfo) => JsonPath(q + query, parserInfo) + case XmlPath(query, parserInfo) => XmlPath(q + query + "[*]", parserInfo) }) val results = queries.map(doVisit(_, arguments).asInstanceOf[Result]) val newQueries = queries.indices.map(iq => { results(iq).results.indices.map(ir => queries(iq) match { - case JsonPath(query) => query.replaceFirst("[*]", ir.toString) + "." - case XmlPath(query) => query.replaceFirst("[*]", (ir + 1).toString) + "/" + case JsonPath(query, _) => query.replaceFirst("[*]", ir.toString) + "." + case XmlPath(query, _) => query.replaceFirst("[*]", (ir + 1).toString) + "/" }) }).toList results.indices.map(r => ResultWithNested(results(r).id, results(r).rootIds, results(r).results, @@ -837,7 +839,7 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], val query = generateFinalQuery(iteratorVars, "", null) query match { case c: CSVPerRow => doPerRowResults(c, fileContentOrURL) - case s: SqlColumn => doSqlResults(s, fileContentOrURL) + case s: SqlColumn => doSqlResults(s, fileContentOrURL, s.parserInfo) case sp: SparqlColumn => doSparqlResults(sp, fileContentOrURL) case _ => { val iteratorQueries = doIteratorQueries(iteratorVars.slice(0, iteratorVars.size - 1), "", List(""), middleArguments, null) @@ -886,12 +888,12 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], } - private def doSqlResults(query: SqlColumn, source: LoadedSource): List[Result] = { + private def doSqlResults(query: SqlColumn, source: LoadedSource, parserInfo: ParserInfo): List[Result] = { val dbURLConnection = source.filepath val resultMap = queryResultCache.search(query.query, source) match { case Some(value) => value.asInstanceOf[Map[String, List[String]]] case None => { - val connection = connectToDB(dbURLConnection) + val connection = connectToDB(dbURLConnection, parserInfo) val statement = connection.prepareStatement(query.query) val resultSet = statement.executeQuery() queryResultCache.save(query.query, source, resultSet) @@ -907,9 +909,9 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], composedResults.toList } - private def connectToDB(dbURL: String) = { + private def connectToDB(dbURL: String, parserInfo: ParserInfo) = { logger.info(s"Connecting to JDBC database $dbURL") - Class.forName(lookForJdbcDriver(dbURL, driversMap)) + Class.forName(lookForJdbcDriver(dbURL, driversMap, parserInfo)) DriverManager.getConnection(dbURL, username, password) } @@ -927,7 +929,7 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], val id = (file.filepath + i).hashCode Result(Option(id), HashSet(id), List(result), None, None, None) } - case None => throw new Exception("Field not present") + case None => throw RDFGenerationError("Field not present", query.parserInfo) } } results.toList @@ -1152,20 +1154,20 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], private def collectionConstructor(output: Model, nodes: Iterator[RDFNode], rdfCollection: RDFCollection): Resource = rdfCollection match { - case RDFList() => output.createList(nodes.asJava) - case RDFBag() => + case RDFList(_) => output.createList(nodes.asJava) + case RDFBag(_) => val bag = output.createBag() nodes.foreach(n => bag.add(n)) bag - case RDFAlt() => + case RDFAlt(_) => val alt = output.createAlt() nodes.foreach(n => alt.add(n)) alt - case RDFSeq() => + case RDFSeq(_) => val seq = output.createSeq() nodes.foreach(n => seq.add(n)) seq - case _ => throw new Exception("Collection not supported") + case _ => throw RDFGenerationError(s"Collection ${rdfCollection.getClass.getName} not supported", rdfCollection.parserInfo) } private def normaliseDataType(datatype: Option[String]): Option[String] = datatype.map(dt => { @@ -1190,12 +1192,12 @@ class RDFGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], } protected def getShapePrefix(action: ActionOrLiteral): String = action match { - case Action(shapePrefix, _, _) => shapePrefix - case LiteralSubject(prefix, _) => prefix.name + case Action(shapePrefix, _, _, _) => shapePrefix.name + case LiteralSubject(prefix, _, _) => prefix.name } private def executeBuiltinFunction(result: Result, builtinFunction: BuiltinFunction): Result = builtinFunction match { - case Index() => result.resultContext match { + case Index(_) => result.resultContext match { case Some(rc) => Result(result.id, result.rootIds, result.results.map(_ => rc.index), result.dataType, result.langTag, result.rdfCollection) case None => result } @@ -1364,4 +1366,4 @@ case class ResultAutoIncrement(iterator: AutoIncrement, predicate: String, names List(predicateWithSpace + namespace + precedentString + iterator.iterator.next() + closingString) } } -case class ResultContext(index: String, query: QueryClause, iteratorQuery: String) \ No newline at end of file +case class ResultContext(index: String, query: QueryClause, iteratorQuery: String) diff --git a/src/main/scala/com/herminiogarcia/shexml/visitor/RMLGeneratorVisitor.scala b/src/main/scala/com/herminiogarcia/shexml/visitor/RMLGeneratorVisitor.scala index 4566e41..1438a4a 100644 --- a/src/main/scala/com/herminiogarcia/shexml/visitor/RMLGeneratorVisitor.scala +++ b/src/main/scala/com/herminiogarcia/shexml/visitor/RMLGeneratorVisitor.scala @@ -3,6 +3,7 @@ package com.herminiogarcia.shexml.visitor import com.herminiogarcia.shexml.ast._ import com.typesafe.scalalogging.Logger import org.apache.jena.query.Dataset +import com.herminiogarcia.shexml.helper.RMLGenerationError import org.apache.jena.rdf.model.{Resource, Statement} class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], prettify: Boolean ,username: String, password: String) @@ -33,7 +34,7 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], override def doVisit(ast: AST, optionalArgument: Any): Any = ast match { - case Shape(shapeName, action, predicateObjects, holdingGraph) => { + case Shape(shapeName, action, predicateObjects, holdingGraph, _) => { logger.info(s"Converting shape ${shapeName.name} with ${predicateObjects.size} predicate-object statements under the graph " + s"${holdingGraph.map(g => g.graphName.prefix + g.graphName.name).getOrElse("default")}") val shapePrefix = getShapePrefix(action) @@ -83,11 +84,11 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], }) } - case Action(shapePrefix: String, action: ExpOrVar, _) => { + case Action(_, action: ExpOrVar, _, _) => { doVisit(action, optionalArgument).asInstanceOf[List[RMLMap]].filter(_.logicalSource.nonEmpty) } - case IteratorQuery(firstVar, composedVar, _) => { + case IteratorQuery(firstVar, composedVar, _, _) => { val receivedArguments = if(optionalArgument != null) optionalArgument.asInstanceOf[Map[String, Any]] else Map[String, Any]() val arguments = if(receivedArguments.isDefinedAt("rmlType")) receivedArguments else receivedArguments.+("rmlType" -> "object") varTable(firstVar) match { @@ -95,7 +96,7 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], case i: IteratorQuery => List(doVisit(i, arguments + ("composedVar" -> composedVar))) case source: FilePath => { val iterator = composedVar match { - case IteratorQuery(iteratorVar, _, _) => + case IteratorQuery(iteratorVar, _, _, _) => if(varTable.get(iteratorVar).isDefined && iteratorVar.name.contains(".")) { val query = transformNestedIterator(getQueryFromVarTable(iteratorVar), iteratorVar) val rootQuery = getQueryFromVarTable(Var(iteratorVar.name.splitAt(iteratorVar.name.lastIndexOf("."))._1)) @@ -105,13 +106,13 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], else return RMLMap(Nil, Nil, Nil, Nil) case v: Var => transformNestedIterator(getQueryFromVarTable(v), v) } - val logicalSourceName = mapPrefixOrBNode + "l_" + (source.toString + iterator.query).hashCode.abs + val logicalSourceName = mapPrefixOrBNode + "l_" + (source.value + iterator.query).hashCode.abs val logicalSource = iterator match { - case SqlQuery(query) => { + case SqlQuery(query, parserInfo) => { val dbSubjectID = mapPrefixOrBNode + "db_" + dbIndex.next() val datasource = List( createStatementWithLiteral(dbSubjectID, rdfPrefix + "type", d2rqPrefix + "Database"), - createStatementWithLiteral(dbSubjectID, d2rqPrefix + "jdbcDriver", lookForJdbcDriver(source.asInstanceOf[JdbcURL].value)), + createStatementWithLiteral(dbSubjectID, d2rqPrefix + "jdbcDriver", lookForJdbcDriver(source.asInstanceOf[JdbcURL].value, parserInfo = parserInfo)), createStatementWithLiteral(dbSubjectID, d2rqPrefix + "jdbcDSN", source.asInstanceOf[JdbcURL].value), createStatementWithLiteral(dbSubjectID, d2rqPrefix + "username", username), createStatementWithLiteral(dbSubjectID, d2rqPrefix + "password", password) @@ -128,9 +129,9 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], } case _ => { val referenceFormulation = iterator match { - case JsonPath(_) => "JSONPath" - case XmlPath(_) => "XPath" - case CSVPerRow(_) => "CSV" + case JsonPath(_, _) => "JSONPath" + case XmlPath(_, _) => "XPath" + case CSVPerRow(_, _) => "CSV" } val iteratorStatement = if(iterator.isInstanceOf[JsonPath] | iterator.isInstanceOf[XmlPath]) List(createStatementWithLiteral(logicalSourceName, rmlPrefix + "iterator", iterator.query)) @@ -149,7 +150,11 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], case _ => arguments("composedVar") match { case cv: Var => varTable(Var(v.name + "." + cv.name)).asInstanceOf[FieldQuery] case i: IteratorQuery => - return doVisit(IteratorQuery(firstVar, IteratorQuery(Var(composedVar.asInstanceOf[Var].name + "." + i.firstVar.name), i.composedVar)), optionalArgument) + return doVisit(IteratorQuery( + firstVar, + IteratorQuery(Var(composedVar.asInstanceOf[Var].name + "." + i.firstVar.name), i.composedVar, parserInfo = i.parserInfo), parserInfo = i.parserInfo), + optionalArgument + ) } } @@ -172,11 +177,11 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], } else { val datatypePrefix = arguments.get("dataType").map({ case dt: DataTypeLiteral => prefixTable(dt.value.split(":")(0) + ":") - case _: DataTypeGeneration => throw new Exception("DataType generation from data not supported in RML") + case dtg: DataTypeGeneration => throw RMLGenerationError("DataType generation from data not supported in RML", dtg.parserInfo) }) val datatype = arguments.get("dataType").map({ case dt: DataTypeLiteral => dt.value.split(":")(1) - case _: DataTypeGeneration => throw new Exception("DataType generation from data not supported in RML") + case dtg: DataTypeGeneration => throw RMLGenerationError("DataType generation from data not supported in RML", dtg.parserInfo) }) val datatypeURI = datatypePrefix.map(_ + datatype.get) val langTagStatement = arguments.get("langTag").map({ @@ -234,7 +239,7 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], } } - case Union(left, right) => { + case Union(left, right, _) => { val leftMap = doVisit(left, optionalArgument) match { case l: List[RMLMap] => l case r: RMLMap => List(r) @@ -246,7 +251,7 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], leftMap.union(rightMap) } - case StringOperation(left, right, unionString) => { + case StringOperation(left, right, unionString, _) => { val leftMap = doVisit(left, optionalArgument).asInstanceOf[RMLMap] val rightMap = doVisit(right, optionalArgument).asInstanceOf[RMLMap] val leftTemplate = leftMap.objectMap.filter(s => s.getPredicate.getLocalName == "template").head.getObject.asLiteral().getString @@ -268,7 +273,7 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], ) } - case Substitution(left, right, join) => { + case Substitution(left, right, join, _) => { val leftMap = doVisit(left, optionalArgument).asInstanceOf[RMLMap] val rightMap = doVisit(right, optionalArgument).asInstanceOf[RMLMap] val joinMap = doVisit(join, optionalArgument).asInstanceOf[RMLMap] @@ -314,7 +319,7 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], ) } - case PredicateObject(predicate, objectOrShapeLink) => { + case PredicateObject(predicate, objectOrShapeLink, _) => { val objectMap = doVisit(objectOrShapeLink, optionalArgument).asInstanceOf[List[RMLMap]] objectMap.map(om => { val predicateStatements = doVisit(predicate, optionalArgument).asInstanceOf[List[Statement]] @@ -335,7 +340,7 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], }).filter(_.isDefined).map {case Some(v) => v} } - case Predicate(prefix, extension) => { + case Predicate(prefix, extension, _) => { val predicateID = mapPrefixOrBNode + "p_" + predicateIndex.next List( createStatement(predicateID, rdfPrefix + "type", rrPrefix + "predicateMap"), @@ -343,21 +348,21 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], ) } - case ObjectElement(prefix, action, literalValue, matcher, condition, dataType, langTag, rdfCollection) => { + case ObjectElement(prefix, action, literalValue, matcher, condition, dataType, langTag, rdfCollection, parserInfo) => { val arguments = if(optionalArgument != null) optionalArgument.asInstanceOf[Map[String, Any]] else Map[String, Any]() - val prefixArguments = if(prefix.nonEmpty) arguments.+("prefix" -> prefix) else arguments + val prefixArguments = if(prefix.nonEmpty) arguments.+("prefix" -> prefix.get.name) else arguments val dataTypeArguments = if(dataType.isDefined) prefixArguments.+("dataType" -> dataType.getOrElse(None)) else prefixArguments val finalArguments = if(langTag.isDefined) dataTypeArguments.+("langTag" -> langTag.getOrElse(None)) else dataTypeArguments action match { case Some(value) => doVisit(value, finalArguments) case None => literalValue match { case Some(literal) => doVisit(literal, finalArguments) - case None => throw new Exception("No generation clause given.") + case None => throw RMLGenerationError("No generation clause given.", parserInfo) } } } - case LiteralObject(prefix, value) => { + case LiteralObject(prefix, value, _) => { val fullPrefix = prefixTable(prefix.name) val objectMapID = mapPrefixOrBNode + "o_" + objectIndex.next val objectMap = List( @@ -368,7 +373,7 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], List(RMLMap(Nil, objectMap, Nil, Nil)) } - case LiteralSubject(prefix, value) => { + case LiteralSubject(prefix, value, _) => { val fullPrefix = prefixTable(prefix.name) val subjectMapID = mapPrefixOrBNode + "s_" + objectIndex.next val objectMap = List( @@ -379,7 +384,7 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], List(RMLMap(Nil, objectMap, Nil, Nil)) } - case LiteralObjectValue(value) => { + case LiteralObjectValue(value, _) => { val arguments = optionalArgument.asInstanceOf[Map[String, Any]] val datatypePrefix = arguments.get("dataType").map(d => prefixTable(d.toString.split(":")(0) + ":")) val datatype = arguments.get("dataType").map(d => d.toString.split(":")(1)) @@ -432,11 +437,11 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], } private def transformNestedIterator(queryClause: QueryClause, v: Var): QueryClause = queryClause match { - case FieldQuery(query, _, _) => getQueryFromVarTable(Var(v.name.split('.').head)) match { - case JsonPath(jsonQuery) => JsonPath(jsonQuery + "." + query) - case XmlPath(xpathQuery) => XmlPath(xpathQuery + "/" + query) - case CSVPerRow(_) => CSVPerRow(query) - case SqlQuery(_) => SqlQuery(query) + case FieldQuery(query, _, _, _) => getQueryFromVarTable(Var(v.name.split('.').head)) match { + case JsonPath(jsonQuery, parserInfo) => JsonPath(jsonQuery + "." + query, parserInfo) + case XmlPath(xpathQuery, parserInfo) => XmlPath(xpathQuery + "/" + query, parserInfo) + case CSVPerRow(_, parserInfo) => CSVPerRow(query, parserInfo) + case SqlQuery(_, parserInfo) => SqlQuery(query, parserInfo) } case j: JsonPath => j case x: XmlPath => x @@ -445,24 +450,24 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], } private def mergeQueries(leftQuery: QueryClause, rightQuery: QueryClause): QueryClause = rightQuery match { - case XmlPath(query) => XmlPath(leftQuery.query + "/" + query) - case JsonPath(query) => JsonPath(leftQuery.query + "." + query) + case XmlPath(query, parserInfo) => XmlPath(leftQuery.query + "/" + query, parserInfo) + case JsonPath(query, parserInfo) => JsonPath(leftQuery.query + "." + query, parserInfo) } private def getNestedIteratorFieldQuery(iteratorQuery: IteratorQuery, precedentVar: Var, iteratorQueryClause: QueryClause): Option[FieldQuery] = { iteratorQuery.composedVar match { case i: IteratorQuery => { val accIteratorQueryClause = iteratorQueryClause match { - case JsonPath(_) => JsonPath(getQueryFromVarTable(Var(precedentVar.name + "." + i.firstVar.name)).query + ".") - case XmlPath(_) => XmlPath(getQueryFromVarTable(Var(precedentVar.name + "." + i.firstVar.name)).query + "/") + case JsonPath(_, parserInfo) => JsonPath(getQueryFromVarTable(Var(precedentVar.name + "." + i.firstVar.name)).query + ".", parserInfo) + case XmlPath(_, parserInfo) => XmlPath(getQueryFromVarTable(Var(precedentVar.name + "." + i.firstVar.name)).query + "/", parserInfo) } getNestedIteratorFieldQuery(i, Var(precedentVar.name + "." + i.firstVar.name), accIteratorQueryClause) } case v: Var => varTable.get(Var(precedentVar.name + "." + v.name)) match { case Some(value) => value match { case fq: FieldQuery => iteratorQueryClause match { - case JsonPath(_) => Some(FieldQuery(getQueryFromVarTable(precedentVar).query + "." + fq.query)) - case XmlPath(_) => Some(FieldQuery(getQueryFromVarTable(precedentVar).query + "/" + fq.query)) + case JsonPath(_, parserInfo) => Some(FieldQuery(getQueryFromVarTable(precedentVar).query + "." + fq.query, parserInfo = parserInfo)) + case XmlPath(_, parserInfo) => Some(FieldQuery(getQueryFromVarTable(precedentVar).query + "/" + fq.query, parserInfo = parserInfo)) } case _ => None } @@ -493,4 +498,4 @@ class RMLGeneratorVisitor(dataset: Dataset, varTable: Map[Variable, VarResult], } case class RMLMap(logicalSource: List[Statement], objectMap: List[Statement], - predicateMap: List[Statement], predicateObjectMap: List[Statement]) \ No newline at end of file + predicateMap: List[Statement], predicateObjectMap: List[Statement]) diff --git a/src/main/scala/com/herminiogarcia/shexml/visitor/SemanticCheckerVisitor.scala b/src/main/scala/com/herminiogarcia/shexml/visitor/SemanticCheckerVisitor.scala new file mode 100644 index 0000000..cfd77f5 --- /dev/null +++ b/src/main/scala/com/herminiogarcia/shexml/visitor/SemanticCheckerVisitor.scala @@ -0,0 +1,93 @@ +package com.herminiogarcia.shexml.visitor + +import com.herminiogarcia.shexml.ast._ +import com.herminiogarcia.shexml.helper.SemanticCheckerError + +class SemanticCheckerVisitor(varTable: Map[Variable, VarResult]) extends DefaultVisitor[Any, Unit] { + + override def doVisit(ast: AST, optionalArgument: Any): Unit = ast match { + + case i: IteratorQuery => { + checkVar(i.firstVar, i.firstVar.parserInfo) //file var or existing expression + varTable(i.firstVar) match { + case e: Exp => doVisit(e, optionalArgument) + case _ => + val fullName = iteratorQueryToList(i.composedVar).map(_.name).mkString(".") + varTable.getOrElse(Var(fullName), throw SemanticCheckerError(s"Variable $fullName is not defined", i.composedVar.parserInfo)) + } + } + + case LiteralObject(prefix, _, parserInfo) => checkVar(prefix, parserInfo) + + case PredicateObject(predicate, objectOrShapeLink, _) => { + doVisit(predicate, optionalArgument) + doVisit(objectOrShapeLink, optionalArgument) + } + + case Predicate(prefix, _, parserInfo) => checkPrefix(prefix, parserInfo) + + case FunctionCalling(functionHub, _, arguments, parserInfo) => { + checkVar(functionHub, functionHub.parserInfo) + doVisit(arguments, optionalArgument) + } + + case Arguments(arguments, _) => arguments.foreach(checkExpOrVar(_, optionalArgument)) + + case Action(shapePrefix, action, condition, parserInfo) => { + if(shapePrefix.name != "_:") checkPrefix(shapePrefix.name, shapePrefix.parserInfo) + doVisit(action, optionalArgument) + condition.foreach(doVisit(_, optionalArgument)) + } + + case ObjectElement(prefix, action, _, matcher, filter, dataType, langTag, _, parserInfo) => { + prefix.foreach(p => checkPrefix(p.name, p.parserInfo)) + action.foreach(checkExpOrVar(_, optionalArgument)) + matcher.foreach(m => checkVar(m, m.parserInfo)) + filter.foreach(checkExpOrVar(_, optionalArgument)) + dataType.foreach(doVisit(_, optionalArgument)) + langTag.foreach(doVisit(_, optionalArgument)) + } + + case ShapeLink(shape, parserInfo) => checkVar(shape, parserInfo) + + case DataTypeGeneration(prefix, action, matcher, parserInfo) => { + prefix.foreach(p => checkPrefix(p.name, p.parserInfo)) + checkExpOrVar(action, optionalArgument) + matcher.foreach(checkVar(_, parserInfo)) + } + + case LangTagGeneration(action, matcher, parserInfo) => { + checkExpOrVar(action, optionalArgument) + matcher.foreach(checkVar(_, parserInfo)) + } + + case default => super.visit(default, optionalArgument) + } + + private def checkVar(variable: Variable, parserInfo: ParserInfo): Unit = { + varTable.getOrElse(variable, throw SemanticCheckerError(s"Variable ${nameOf(variable)} is not defined", parserInfo)) + } + + private def checkPrefix(prefix: String, parserInfo: ParserInfo): Unit = { + varTable.getOrElse(Var(prefix), throw SemanticCheckerError(s"Prefix $prefix is not defined", parserInfo)) + } + + private def checkExpOrVar(exp: ExpOrVar, optionalArgument: Any): Unit = exp match { + case v: Var => checkVar(v, v.parserInfo) + case default => doVisit(default, optionalArgument) + } + + private def nameOf(variable: Variable): String = variable match { + case v: Var => v.name + case sv: ShapeVar => sv.name + case gv: GraphVar => gv.prefix + gv.name + } + + private def iteratorQueryToList(varOrIteratorQuery: VarOrIteratorQuery): List[Var] = varOrIteratorQuery match { + case v: Var => List(v) + case c: IteratorQuery => List(c.firstVar) ::: iteratorQueryToList(c.composedVar) + } + + override def doVisitDefault(): Unit = () + +} diff --git a/src/main/scala/com/herminiogarcia/shexml/visitor/VarTableBuilderVisitor.scala b/src/main/scala/com/herminiogarcia/shexml/visitor/VarTableBuilderVisitor.scala index 65ad6cb..cfd4724 100644 --- a/src/main/scala/com/herminiogarcia/shexml/visitor/VarTableBuilderVisitor.scala +++ b/src/main/scala/com/herminiogarcia/shexml/visitor/VarTableBuilderVisitor.scala @@ -10,14 +10,14 @@ import scala.collection.mutable class VarTableBuilderVisitor(val variableMap: mutable.HashMap[Variable, VarResult]) extends DefaultVisitor[Map[String, Any], Unit] { override def doVisit(ast: AST, optionalArgument: Map[String, Any]): Unit = ast match { - case Prefix(name, value) => variableMap += ((name, value)) - case Source(name, value) => variableMap += ((name, value)) - case Query(name, value) => variableMap += ((name, value)) - case Functions(name, value) => variableMap += ((name, value)) - case Expression(name, value) => variableMap += ((name, value)) - case Iterator(name, value, fields, iterators) => registerIterator(name, value, fields, iterators, optionalArgument) - case NestedIterator(name, value, fields, iterators) => registerIterator(name, value, fields, iterators, optionalArgument) - case Field(name, value, _, _) => { + case Prefix(name, value, _) => variableMap += ((name, value)) + case Source(name, value, _) => variableMap += ((name, value)) + case Query(name, value, _) => variableMap += ((name, value)) + case Functions(name, value, _) => variableMap += ((name, value)) + case Expression(name, value, _) => variableMap += ((name, value)) + case Iterator(name, value, fields, iterators, _) => registerIterator(name, value, fields, iterators, optionalArgument) + case NestedIterator(name, value, fields, iterators, _) => registerIterator(name, value, fields, iterators, optionalArgument) + case Field(name, value, _, _, _) => { val fieldName = if(optionalArgument("variable") == "") name.name else optionalArgument("variable").toString + "." + name.name variableMap += ((Var(fieldName), value)) } @@ -34,15 +34,15 @@ class VarTableBuilderVisitor(val variableMap: mutable.HashMap[Variable, VarResul def registerIterator(name: Var, value: QueryOrVar, fields: List[Field], iterators: List[NestedIterator], optionalArgument: Map[String, Any]) = { val iteratorName = if(optionalArgument("variable") == "") name.name else optionalArgument("variable").toString + "." + name.name val finalValue = value match { - case FieldQuery(query, pushed, popped) => optionalArgument("type") match { - case XmlPath(_) => XmlPath(query) - case JsonPath(_) => JsonPath(query) + case FieldQuery(query, pushed, popped, _) => optionalArgument("type") match { + case XmlPath(_, parserInfo) => XmlPath(query, parserInfo) + case JsonPath(_, parserInfo) => JsonPath(query, parserInfo) } case _ => value } variableMap += ((Var(iteratorName), finalValue)) val topIteratorType = value match { - case FieldQuery(_, _, _) => optionalArgument("type").asInstanceOf[QueryClause] + case FieldQuery(_, _, _, _) => optionalArgument("type").asInstanceOf[QueryClause] case x: XmlPath => x case j: JsonPath => j case c: CSVPerRow => c diff --git a/src/test/scala/com/herminiogarcia/shexml/FilmsCLITest.scala b/src/test/scala/com/herminiogarcia/shexml/FilmsCLITest.scala index c481380..07947df 100644 --- a/src/test/scala/com/herminiogarcia/shexml/FilmsCLITest.scala +++ b/src/test/scala/com/herminiogarcia/shexml/FilmsCLITest.scala @@ -200,7 +200,7 @@ class FilmsCLITest extends AnyFunSuite { | rr:template "{name}" ; | rr:termType rr:Literal . | - |map:l_353498812 a rml:LogicalSource ; + |map:l_1694368582 a rml:LogicalSource ; | rml:iterator "$.films[*]" ; | rml:referenceFormulation ql:JSONPath ; | rml:source "https://shexml.herminiogarcia.com/files/films.json" . @@ -209,7 +209,7 @@ class FilmsCLITest extends AnyFunSuite { | rr:template "http://example.com/{@id}" . | |map:m_2 a rr:TriplesMap ; - | rml:logicalSource map:l_353498812 ; + | rml:logicalSource map:l_1694368582 ; | rr:predicateObjectMap map:po_5 , map:po_3 , map:po_13 , map:po_1 , map:po_11 , map:po_9 , map:po_7 ; | rr:subjectMap map:s_2 . | @@ -231,7 +231,7 @@ class FilmsCLITest extends AnyFunSuite { |map:p_1 a rr:predicateMap ; | rr:constant . | - |map:l_1572684380 a rml:LogicalSource ; + |map:l_1409459066 a rml:LogicalSource ; | rml:iterator "//film" ; | rml:referenceFormulation ql:XPath ; | rml:source "https://shexml.herminiogarcia.com/files/films.xml" . @@ -241,7 +241,7 @@ class FilmsCLITest extends AnyFunSuite { | rr:termType rr:IRI . | |map:m_1 a rr:TriplesMap ; - | rml:logicalSource map:l_1572684380 ; + | rml:logicalSource map:l_1409459066 ; | rr:predicateObjectMap map:po_12 , map:po_10 , map:po_4 , map:po_8 , map:po_1 , map:po_6 , map:po_2 ; | rr:subjectMap map:s_1 . | diff --git a/src/test/scala/com/herminiogarcia/shexml/runtimeErrors/UndefinedConditionalGenerationTest.scala b/src/test/scala/com/herminiogarcia/shexml/runtimeErrors/UndefinedConditionalGenerationTest.scala new file mode 100644 index 0000000..647b016 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/runtimeErrors/UndefinedConditionalGenerationTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.runtimeErrors + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.RDFGenerationError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedConditionalGenerationTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name IF films.shouldGenerate] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined variable in a conditional generation expression") { + val error = intercept[RDFGenerationError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable films.shouldGenerate does not resolve to any reference, check the used variable or the iterator definition") + assert(error.getEnrichedErrorMessage(example).contains("44: :name [films.name IF films.shouldGenerate] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/runtimeErrors/UndefinedFunctionCallTest.scala b/src/test/scala/com/herminiogarcia/shexml/runtimeErrors/UndefinedFunctionCallTest.scala new file mode 100644 index 0000000..781172a --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/runtimeErrors/UndefinedFunctionCallTest.scala @@ -0,0 +1,83 @@ +package com.herminiogarcia.shexml.runtimeErrors + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.RDFGenerationError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedFunctionCallTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |FUNCTIONS helper + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [helper.toLowerCase(films.name)] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined function in function call is detected") { + val error = intercept[RDFGenerationError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Function toLowerCase was not found in source code src/test/resources/functions.scala") + assert(error.getEnrichedErrorMessage(example).contains("45: :name [helper.toLowerCase(films.name)] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/runtimeErrors/UndefinedVariableInActionRightExpressionTest.scala b/src/test/scala/com/herminiogarcia/shexml/runtimeErrors/UndefinedVariableInActionRightExpressionTest.scala new file mode 100644 index 0000000..c18d720 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/runtimeErrors/UndefinedVariableInActionRightExpressionTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.runtimeErrors + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.RDFGenerationError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedVariableInActionRightExpressionTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + |PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.nonExistent] { + | :name [films.name] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined variable in an action expression is detected") { + val error = intercept[RDFGenerationError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable films.nonExistent does not resolve to any reference, check the used variable or the iterator definition") + assert(error.getEnrichedErrorMessage(example).contains("43: :Films :[films.nonExistent] {")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedExpressionInShapeTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedExpressionInShapeTest.scala new file mode 100644 index 0000000..cb36bab --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedExpressionInShapeTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedExpressionInShapeTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [notfilms.name] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined expression in a shape is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable notfilms is not defined") + assert(error.getEnrichedErrorMessage(example).contains("44: :name [notfilms.name] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedFieldExpressionTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedFieldExpressionTest.scala new file mode 100644 index 0000000..a8111bc --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedFieldExpressionTest.scala @@ -0,0 +1,84 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedFieldExpressionTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + |PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + |EXPRESSION goodExp + |EXPRESSION erroredExp + | + |:Films :[films.id] { + | :name [films.name] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined field within expression is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable film_xml.country.name is not defined") + assert(error.getEnrichedErrorMessage(example).contains("43: EXPRESSION erroredExp ")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedFieldInShapeTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedFieldInShapeTest.scala new file mode 100644 index 0000000..22ad555 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedFieldInShapeTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedFieldInShapeTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films_xml_file.film_xml.nonexistent] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined field in a shape is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable film_xml.nonexistent is not defined") + assert(error.getEnrichedErrorMessage(example).contains("44: :name [films_xml_file.film_xml.nonexistent] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedFileInExpressionTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedFileInExpressionTest.scala new file mode 100644 index 0000000..eec06fe --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedFileInExpressionTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedFileInExpressionTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined file in expression is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable films_csv_file is not defined") + assert(error.getEnrichedErrorMessage(example).contains("41: EXPRESSION films <films_csv_file.film_xml UNION films_json_file.film_json>")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedFunctionHubTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedFunctionHubTest.scala new file mode 100644 index 0000000..4ab2d4d --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedFunctionHubTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedFunctionHubTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [helper.toUpperCase(films.name)] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined variable for a function hub before function call") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable helper is not defined") + assert(error.getEnrichedErrorMessage(example).contains("44: :name [helper.toUpperCase(films.name)] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedMatcherTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedMatcherTest.scala new file mode 100644 index 0000000..41d7f9d --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedMatcherTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedMatcherTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name] ; + | :year [films.year] ; + | :country [films.country MATCHING country_matcher] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined matcher in an object is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable country_matcher is not defined") + assert(error.getEnrichedErrorMessage(example).contains("46: :country [films.country MATCHING country_matcher] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedNestedIteratorFieldInShapeTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedNestedIteratorFieldInShapeTest.scala new file mode 100644 index 0000000..20330dd --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedNestedIteratorFieldInShapeTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedNestedIteratorFieldInShapeTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films_xml_file.film_xml.actors.nonexistent] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined nested iterator field in a shape is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable film_xml.actors.nonexistent is not defined") + assert(error.getEnrichedErrorMessage(example).contains("54: :appear_on :[films_xml_file.film_xml.actors.nonexistent] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInDataTypeTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInDataTypeTest.scala new file mode 100644 index 0000000..28ce503 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInDataTypeTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedPrefixInDataTypeTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name] ; + | :year [films.year] wiki:[films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined prefix in a datatype is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Prefix wiki: is not defined") + assert(error.getEnrichedErrorMessage(example).contains("45: :year [films.year] wiki:[films.year] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInLiteralObjectTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInLiteralObjectTest.scala new file mode 100644 index 0000000..24a324d --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInLiteralObjectTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedPrefixInLiteralObjectTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name] ; + | :year [films.year] ; + | :country wiki:USA ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined prefix in a literal object is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable wiki: is not defined") + assert(error.getEnrichedErrorMessage(example).contains("46: :country wiki:USA ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInObjectTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInObjectTest.scala new file mode 100644 index 0000000..5b6cccd --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInObjectTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedPrefixInObjectTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name wiki:[films.name] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined prefix in an object is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Prefix wiki: is not defined") + assert(error.getEnrichedErrorMessage(example).contains("44: :name wiki:[films.name] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInPredicateTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInPredicateTest.scala new file mode 100644 index 0000000..407e8e2 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInPredicateTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedPrefixInPredicateTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | wiki:name [films.name] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined prefix in a predicate is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Prefix wiki: is not defined") + assert(error.getEnrichedErrorMessage(example).contains("44: wiki:name [films.name] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInSubjectTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInSubjectTest.scala new file mode 100644 index 0000000..bcc1765 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedPrefixInSubjectTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedPrefixInSubjectTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films wiki:[films.id] { + | :name [films.name] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined prefix in a subject is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Prefix wiki: is not defined") + assert(error.getEnrichedErrorMessage(example).contains("43: :Films wiki:[films.id] {")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedShapeLinkTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedShapeLinkTest.scala new file mode 100644 index 0000000..f93ab91 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedShapeLinkTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedShapeLinkTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:NonExistentShape ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined shape in a shape link is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable :NonExistentShape is not defined") + assert(error.getEnrichedErrorMessage(example).contains("48: :actor @:NonExistentShape ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedSourceInExpressionTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedSourceInExpressionTest.scala new file mode 100644 index 0000000..fd7ba36 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedSourceInExpressionTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedSourceInExpressionTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined source in an expression is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable undeclared_file is not defined") + assert(error.getEnrichedErrorMessage(example).contains("41: EXPRESSION films <undeclared_file.film_xml UNION films_json_file.film_json>")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInActionLeftExpressionTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInActionLeftExpressionTest.scala new file mode 100644 index 0000000..af2d9c9 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInActionLeftExpressionTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedVariableInActionLeftExpressionTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + |PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[nonExistent.id] { + | :name [films.name] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined variable in action is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable nonExistent is not defined") + assert(error.getEnrichedErrorMessage(example).contains("43: :Films :[nonExistent.id] {")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInDataTypeLeftTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInDataTypeLeftTest.scala new file mode 100644 index 0000000..dc07fa5 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInDataTypeLeftTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedVariableInDataTypeLeftTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name] ; + | :year [films.year] dbr:[nonExistent.name] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined variable in a datatype is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable nonExistent is not defined") + assert(error.getEnrichedErrorMessage(example).contains("45: :year [films.year] dbr:[nonExistent.name] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInDataTypeTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInDataTypeTest.scala new file mode 100644 index 0000000..e674d53 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInDataTypeTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedVariableInDataTypeTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name] ; + | :year [films.year] dbr:[films_xml_file.film_xml.nonexistent] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined variable in a datatype is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable film_xml.nonexistent is not defined") + assert(error.getEnrichedErrorMessage(example).contains("45: :year [films.year] dbr:[films_xml_file.film_xml.nonexistent] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInLangTagFullTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInLangTagFullTest.scala new file mode 100644 index 0000000..1840763 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInLangTagFullTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedVariableInLangTagFullTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name] @[filmsIt.lang] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined variable in a language tag is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable filmsIt is not defined") + assert(error.getEnrichedErrorMessage(example).contains("44: :name [films.name] @[filmsIt.lang] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInLangTagTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInLangTagTest.scala new file mode 100644 index 0000000..115138c --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/UndefinedVariableInLangTagTest.scala @@ -0,0 +1,82 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import com.herminiogarcia.shexml.helper.SemanticCheckerError +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class UndefinedVariableInLangTagTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name] @[films_xml_file.film_xml.nonexistent] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + test("Undefined variable in a language tag is detected") { + val error = intercept[SemanticCheckerError] { + mappingLauncher.launchMapping(example) + } + assert(error.message == "Variable film_xml.nonexistent is not defined") + assert(error.getEnrichedErrorMessage(example).contains("44: :name [films.name] @[films_xml_file.film_xml.nonexistent] ;")) + } + +} diff --git a/src/test/scala/com/herminiogarcia/shexml/semanticChecker/ValidMappingTest.scala b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/ValidMappingTest.scala new file mode 100644 index 0000000..0824ad7 --- /dev/null +++ b/src/test/scala/com/herminiogarcia/shexml/semanticChecker/ValidMappingTest.scala @@ -0,0 +1,86 @@ +package com.herminiogarcia.shexml.semanticChecker + +import com.herminiogarcia.shexml.ParallelConfigInferenceDatatypesNormaliseURIsFixture +import org.apache.jena.rdf.model.Model +import org.scalatest.ConfigMap +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.must.Matchers + +class ValidMappingTest extends AnyFunSuite + with Matchers with ParallelConfigInferenceDatatypesNormaliseURIsFixture { + + private val example = + """ + PREFIX : + |PREFIX dbr: + |PREFIX schema: + |SOURCE films_xml_file + |SOURCE films_json_file + |ITERATOR film_xml { + | FIELD id <@id> + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + | ITERATOR actresses { + | FIELD name + | FIELD role + | FIELD film <../../@id> + | } + |} + |ITERATOR film_json { + | FIELD id + | FIELD name + | FIELD year + | FIELD country + | FIELD directors + | FIELD screenwritters + | FIELD music + | FIELD photography + | ITERATOR actors { + | FIELD name + | FIELD role + | } + |} + |EXPRESSION films + | + |:Films :[films.id] { + | :name [films.name] ; + | :year [films.year] ; + | :country [films.country] ; + | :director [films.directors] ; + | :actor @:Actor ; + | :actor @:Actress ; + |} + | + |:Actor :[films.actors.name] { + | :name [films.actors.name] ; + | :appear_on :[films.actors.film] ; + |} + | + |:Actress :[films.actresses.name] { + | :name [films.actresses.name] ; + | :appear_on :[films.actresses.film] ; + |} + """.stripMargin + + private var output: Model = _ + + override def beforeAll(configMap: ConfigMap): Unit = { + super.beforeAll(configMap) + output = mappingLauncher.launchMapping(example).getDefaultModel + } + + test("Valid mapping passes the semantic analysis and generates output") { + assert(output.size() > 0) + } + +}