Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions codemeta.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
4 changes: 2 additions & 2 deletions films.shexml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
PREFIX : <http://example.com/>
PREFIX dbr: <http://dbpedia.org/resource/>
PREFIX schema: <http://schema.org/>
SOURCE films_xml_file <http://shexml.herminiogarcia.com/files/films.xml>
SOURCE films_json_file <http://shexml.herminiogarcia.com/files/films.json>
SOURCE films_xml_file <https://shexml.herminiogarcia.com/files/films.xml>
SOURCE films_json_file <https://shexml.herminiogarcia.com/files/films.json>
ITERATOR film_xml <xpath: //film> {
FIELD id <@id>
FIELD name <name>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ILoggingEvent> {

@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
}

}
5 changes: 4 additions & 1 deletion src/main/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<configuration>
<conversionRule conversionWord="errorsInRed"
converterClass="com.herminiogarcia.shexml.helper.LogRedErrorConverter" />

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level[%thread] [%class] - %msg%n</pattern>
<pattern>%errorsInRed(%d{HH:mm:ss.SSS} %-5level[%thread] [%class] - %msg%n)</pattern>
</encoder>
</appender>
<root level="info">
Expand Down
18 changes: 15 additions & 3 deletions src/main/scala/com/herminiogarcia/shexml/Main.scala
Original file line number Diff line number Diff line change
@@ -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

/**
Expand All @@ -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 = ""

Expand All @@ -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,
Expand Down Expand Up @@ -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() }
}
}
Expand Down
30 changes: 25 additions & 5 deletions src/main/scala/com/herminiogarcia/shexml/MappingLauncher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
}
Expand All @@ -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)
Expand All @@ -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 =>
Expand All @@ -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 = {
Expand All @@ -142,14 +156,20 @@ 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:")
varTable.foreach {case (v, vr) => logger.debug(s"$v -> $vr")}
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()
Expand Down
Loading
Loading