diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index f9a5477a..00000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-name: Java CI with Maven
-
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
-
-jobs:
- build:
- name: Run Unit Tests
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
- - name: Set up JDK 21
- uses: actions/setup-java@v3
- with:
- java-version: '21'
- distribution: 'temurin'
- cache: 'maven'
- - name: Build and test with Maven
- run: mvn test
- - name: Publish Test Report
- uses: mikepenz/action-junit-report@v6.0.0
- if: success() || failure() # always run even if the previous step fails
- with:
- report_paths: '**/target/surefire-reports/TEST-*.xml'
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 00000000..1b19694b
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,55 @@
+name: Deploy
+
+on:
+ push:
+ tags:
+ - 'v*'
+
+jobs:
+ deploy:
+ name: Deploy Release
+ if: startsWith(github.ref, 'refs/tags/v')
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ issues: write
+ id-token: write
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: true
+ - name: Set up JDK 21
+ uses: actions/setup-java@v5
+ with:
+ java-version: '21'
+ distribution: 'temurin'
+ cache: 'maven'
+ server-id: central
+ server-username: MAVEN_USERNAME # env variable for username in deploy
+ server-password: MAVEN_CENTRAL_TOKEN # env variable for token in deploy
+ - name: Set project version from tag
+ run: |
+ TAG_VERSION="${{ github.ref_name }}"
+ # Remove leading 'v' if present (e.g. v0.1.4 -> 0.1.4)
+ TAG_VERSION="${TAG_VERSION#v}"
+
+ echo "Updating pom.xml version to: $TAG_VERSION"
+ mvn versions:set -DnewVersion="$TAG_VERSION" -DgenerateBackupPoms=false -B
+ - name: Deploy Maven package
+ # Locally run instead: mvn clean deploy -DskipTests -Pdeploy -Dgpg.passphrase=""
+ run: mvn deploy -B -Pdeploy -Dgpg.signer=bc -DskipTests
+ env:
+ MAVEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
+ MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
+ # ASCII-armored secret key (TSK), e.g. from `gpg --armor --export-secret-keys YOUR_ID`
+ MAVEN_GPG_KEY: ${{ secrets.GPG_ARMOR_KEY }}
+ MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
+ - name: JReleaser Release
+ run: mvn jreleaser:full-release -B -Pdeploy
+ env:
+ TAG_NAME: ${{ github.ref }}
+ JRELEASER_PROJECT_VERSION: ${{ github.ref }}
+ JRELEASER_JBANG_GITHUB_TOKEN: ${{ secrets.JBANG_CATALOG_TOKEN }}
+ JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 00000000..ebcf362e
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,32 @@
+name: Tests
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ name: Run Unit Tests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v6
+ with:
+ submodules: true
+ - name: Set up JDK 21
+ uses: actions/setup-java@v5
+ with:
+ java-version: '21'
+ distribution: 'temurin'
+ cache: 'maven'
+ - name: Build with Maven
+ run: mvn clean compile
+ - name: Test with Maven
+ run: mvn test
+ - name: Publish Test Report
+ uses: mikepenz/action-junit-report@v6
+ if: success() || failure() # always run even if the previous step fails
+ with:
+ report_paths: '**/target/surefire-reports/TEST-*.xml'
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..9c2b66f1
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,22 @@
+[submodule "src/test/resources/rdf-tests"]
+ path = src/test/resources/rdf-tests
+ url = https://github.com/w3c/rdf-tests.git
+ branch = main
+[submodule "rml-modules/rml-core"]
+ path = rml-modules/rml-core
+ url = https://github.com/kg-construct/rml-core.git
+[submodule "rml-modules/rml-cc"]
+ path = rml-modules/rml-cc
+ url = https://github.com/kg-construct/rml-cc.git
+[submodule "rml-modules/rml-io"]
+ path = rml-modules/rml-io
+ url = https://github.com/kg-construct/rml-io.git
+[submodule "rml-modules/rml-fnml"]
+ path = rml-modules/rml-fnml
+ url = https://github.com/kg-construct/rml-fnml.git
+[submodule "rml-modules/rml-lv"]
+ path = rml-modules/rml-lv
+ url = https://github.com/kg-construct/rml-lv.git
+[submodule "rml-modules/rml-io-registry"]
+ path = rml-modules/rml-io-registry
+ url = https://github.com/kg-construct/rml-io-registry.git
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index aa00ffab..051e40b8 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,6 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 9f12a696..7fb48768 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,3 +1,4 @@
+
@@ -7,5 +8,5 @@
-
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 35eb1ddf..fec1c04d 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,5 +2,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 56d03ca5..7cf7a97d 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,58 @@
-# BURP: A Basic and Unassuming RML Processor
+# BURP-Error: A Basic and Unassuming RML Processor with RML Execution Report
-[](https://doi.org/10.5281/zenodo.11037712)
+[](https://doi.org/10.5281/zenodo.19455346)
+BURP (Basic and Unassuming RML Processor) is a reference implementation for the
+new [RML specification](http://w3id.org/rml/portal) which has been written from scratch to have no influence from prior
+implementations of RML.
+BURP was created to serve as a reference RML implementation for the Knowledge Graph Construction community and to verify
+the RML specifications, their feasibility, and coverage of their test cases.
-BURP (Basic and Unassuming RML Processor) is a reference implementation for the new [RML specification](http://w3id.org/rml/portal) which has been written from scratch to have no influence from prior implementations of RML.
-BURP was created to serve as a reference RML implementation for the Knowledge Graph Construction community and to verify the RML specifications their feasibility and coverage of their test cases.
+BURP-Error is a fork of BURP that adds error handling to the RML processor, according to
+the [RML Execution Report](https://w3id.org/dre/rer). The project is a proof of concept, no maintenance guaranteed, but
+if you have any questions or issues, feel free to
+[open an issue on GitHub](https://github.com/jduchateau/BURP/issues/new).
+
+Warning: Joins are quadratic, in terms of iterations and number of multivalued join conditions values.
## Coverage matrix
-| [RML-Core](http://w3id.org/rml/core/spec) | [RML-IO](http://w3id.org/rml/io/spec) | [RML-CC](http://w3id.org/rml/cc/spec) | [RML-FNML](http://w3id.org/rml/fnml/spec) | [RML-Star](http://w3id.org/rml/star/spec) |
-| ----------------------------------------- | ------------------------------------- | ------------------------------------- | ----------------------------------------- | ----------------------------------------- |
-| ✔️ 100% coverage | 🚧 Source yes, Target WIP | ✔️ 100% coverage | ✔️ 100% coverage | 🚧 WIP |
+| RML module | Test Cases: Pass / Fail / Total |
+|------------------------------------------------------|---------------------------------|
+| [RML-Core](https://w3id.org/rml/core/spec) | ✅ 76 / 0 / 76 |
+| [RML-IO](https://w3id.org/rml/io/spec) | ✔️ 56 / 0 / 56* |
+| [RML-IO-Registry](https://w3id.org/rml/io-registry/) | 🚧️ 52 / 50 / 102 Details below |
+| [RML-CC](https://w3id.org/rml/cc/spec) | ✅ 35 / 0 / 35 |
+| [RML-FNML](https://w3id.org/rml/fnml/spec) | ✅️ 20 / 0 / 20 |
+| [RML-Star](https://w3id.org/rml/star/spec) | ❌ Not implemented |
+| [RML-LV](https://w3id.org/rml/lv/spec) | ✅️ 41 / 0 / 41 |
+| [RER](https://w3id.org/dre/rer) | 🪅 Demo implementation |
+
+\* We consider 17 test cases in RML-IO to have issues.
+
+### RML-IO-Registry coverage details
+
+BURP supports natively the following input sources:
+
+- rml:FilePath or rml:RelativePathSource — local files (supports rml:root and rml:path)
+- rml:CSV — CSV files (including CSVW tables and their dialects: encoding, delimiter, header, nulls)
+- rml:JSONPath — JSON sources ([RFC 9535](https://www.rfc-editor.org/rfc/rfc9535) JSONPath iterator)
+- rml:XPath — XML sources (XPath 1.0 iterator; supports namespace/prefix mappings for XPath reference formulations)
+- rml:SPARQL Results (CSV/TSV/XML/JSON) — SPARQL result files, SPARQL endpoints/services, and data dumps (VOID/SD)
+- rml:SQL2008Query and rml:SQL2008Table — relational database sources
+ (via D2RQ properties such as d2rq:jdbcDSN, d2rq:jdbcDriver, username, password)
+- DCAT Distribution / CSVW Table — remote files via DCAT downloadURL or CSVW url
+
+- Extensions possible see extending BURP
+
+## About the RML Execution Report
+
+The RML Execution Report (RER) is a taxonomy for describing the execution of an RML mapping, defined at [https://w3id.org/dre/rer](https://w3id.org/dre/rer).
+
+BURP-Error can output the execution report using the `rer` vocabulary.
+In addition, if your mapping is in Turtle format, BURP-Error will also output the execution report in a textual format with code snippets or your mappings instead of the rdf 1.2 statements pointers ([ptr](https://w3id.org/dre/ptr)).
+
+Notice; to locate prescicely the turtle token location, we use a custom turtle parser (in `turtleprov`) that admitedly is only 39/103 (37%) of RDF1.2 test cases and 217/313 (69%) of RDF1.1 test cases.
## Building BURP
@@ -27,7 +69,17 @@ Some libraries are available on GitHub's maven repository. Add a GitHub authenti
## Using BURP
-The run the R2RML processor, execute the following command:
+You can run BURP using [JBang](https://jbang.dev):
+
+```bash
+$ jbang burp@jduchateau [-h] [-b=] -m= [-o=]
+```
+
+*Note: JBang will automatically download the required JDK and resolve all project dependencies on the first run.*
+
+If you don't have JBang installed yet, see the [JBang Installation Guide](https://jbang.dev/download) to install it.
+
+Alternatively, if you prefer to run it using a local pre-built fat JAR and standard Java, execute the following command:
```bash
$ java -jar burp.jar [-h] [-b=] -m= [-o=]
@@ -48,6 +100,24 @@ If no outputFile is provided and the RML mapping does not rely on RML-IO for tar
## Citation
+If you use BURP-Error, please cite our paper:
+
+```
+@inproceedings{duchateau2026rml-execution-report,
+ author = {Jakub Duchateau and Dylan {Van Assche} and Christophe Debruyne},
+ editor = {},
+ title = {Beyond Exit Code 1: A Vocabulary for Execution Report of RML Processors (RER)},
+ booktitle = {Proceedings of the 7th International Workshop on Knowledge Graph Construction
+ co-located with 23rd Extended Semantic Web Conference ({ESWC} 2026),
+ Dubrovnik, Croatia, May 10, 2026},
+ series = {{CEUR} Workshop Proceedings},
+ volume = {X},
+ publisher = {CEUR-WS.org},
+ year = {2026},
+ url = {https://ceur-ws.org/Vol-X/paperX.pdf}
+}
+```
+
If you use BURP, please cite our paper:
```
diff --git a/fetch-test-cases.bat b/fetch-test-cases.bat
deleted file mode 100644
index c0586723..00000000
--- a/fetch-test-cases.bat
+++ /dev/null
@@ -1,61 +0,0 @@
-@echo off
-
-set "targetDir=target"
-
-echo Creating target directory...
-if not exist "%targetDir%" mkdir "%targetDir%"
-if errorlevel 1 goto error
-
-cd "%targetDir%"
-if errorlevel 1 goto error
-
-echo Cloning https://github.com/kg-construct/rml-core
-git clone https://github.com/kg-construct/rml-core
-if errorlevel 1 goto error
-
-echo Cloning rhttps://github.com/kg-construct/rml-cc
-git clone https://github.com/kg-construct/rml-cc
-if errorlevel 1 goto error
-
-echo Cloning https://github.com/kg-construct/rml-io
-git clone https://github.com/kg-construct/rml-io
-if errorlevel 1 goto error
-
-echo Cloning rhttps://github.com/kg-construct/rml-fnml
-git clone https://github.com/kg-construct/rml-fnml
-if errorlevel 1 goto error
-
-echo Cloning https://github.com/kg-construct/rml-lv
-git clone https://github.com/kg-construct/rml-lv
-if errorlevel 1 goto error
-
-echo Cloning rhttps://github.com/kg-construct/rml-star
-git clone https://github.com/kg-construct/rml-star
-if errorlevel 1 goto error
-
-echo All repositories cloned successfully into the target directory!
-
-echo Copying test-case directories to ..\src\test\resources
-xcopy .\rml-core\test-cases ..\src\test\resources\rml-core /E /I /Y
-xcopy .\rml-cc\test-cases ..\src\test\resources\rml-cc /E /I /Y
-xcopy .\target\rml-io\test-cases ..\src\test\resources\rml-io /E /I /Y
-xcopy .\rml-lv\test-cases ..\src\test\resources\rml-lv /E /I /Y
-xcopy .\rml-fnml\test-cases ..\src\test\resources\rml-fnml /E /I /Y
-xcopy .\rml-star\test-cases ..\src\test\resources\rml-star /E /I /Y
-
-rmdir /S /Q .\rml-core
-rmdir /S /Q .\rml-cc
-rmdir /S /Q .\rml-io
-rmdir /S /Q .\rml-lv
-rmdir /S /Q .\rml-fnml
-rmdir /S /Q .\rml-star
-
-goto end
-
-:error
-echo Error occurred.
-echo Check the repository URLs and your internet connection.
-echo or verify that you have permissions to create and write to the target directory.
-
-:end
-pause
\ No newline at end of file
diff --git a/fetch-test-cases.sh b/fetch-test-cases.sh
deleted file mode 100755
index fb070632..00000000
--- a/fetch-test-cases.sh
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env bash
-set -Eeuo pipefail
-
-# --- config ---
-target_dir="target"
-resources_dir="src/test/resources"
-shapes_resources_dir="src/main/resources/shapes"
-repos=(
- "rml-core"
- "rml-cc"
- "rml-io"
- "rml-fnml"
- "rml-lv"
- "rml-star"
-)
-
-trap 'echo; echo "Error occurred."; echo "• Check the repository URLs and your internet connection."; echo "• Verify you have permissions to create/write to \"$target_dir\"."; exit 1' ERR
-
-# Check prerequisites
-command -v git >/dev/null 2>&1 || { echo "git is required but not found in PATH."; exit 1; }
-
-echo "Creating $target_dir directory..."
-mkdir -p "$target_dir"
-
-echo "Entering $target_dir..."
-cd "$target_dir"
-
-# Clone all repositories
-for repo in "${repos[@]}"; do
- echo "Cloning https://github.com/kg-construct/$repo ..."
- git clone "https://github.com/kg-construct/$repo"
-done
-
-echo "All repositories cloned successfully into $target_dir"
-
-# Copy test-cases into ../src/test/resources/
-echo "Copying test-case directories to ../$resources_dir ..."
-for repo in "${repos[@]}"; do
- src="$repo/test-cases"
- dest="../$resources_dir/$repo"
- if [[ -d "$src" ]]; then
- mkdir -p "$dest"
- # -a to preserve attrs; -r for recursive; --no-target-directory not needed here
- cp -a "$src/." "$dest/"
- echo "• Copied $src -> $dest"
- else
- echo "• Skipped $repo (no test-cases directory found)"
- fi
-done
-
-# Copy shapes into ../src/main/resources/
-echo "Copying shapes directories to ../$shapes_resources_dir ..."
-for repo in "${repos[@]}"; do
- src="$repo/shapes"
- dest="../$shapes_resources_dir/$repo"
- if [[ -d "$src" ]]; then
- mkdir -p "$dest"
- # -a to preserve attrs; -r for recursive; --no-target-directory not needed here
- cp -a "$src/." "$dest/"
- echo "• Copied $src -> $dest"
- else
- echo "• Skipped $repo (no shapes directory found)"
- fi
-done
-
-# Remove cloned repositories
-echo "Cleaning up cloned repositories..."
-for repo in "${repos[@]}"; do
- rm -rf "./$repo"
-done
-
-echo "Done."
diff --git a/pom.xml b/pom.xml
index 6dc54d76..8633c449 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,15 +2,54 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
+
io.github.kg-construct
- BURP
- 0.1.2
- Basic and Unassuming RML Processor
+ burp
+ 0.1.3
+
+ BURP
+
+ A Basic and Unassuming RML Processor (BURP) with RML Execution Report (RER) error handling
+
+ https://github.com/kg-construct/BURP/
+ 2024
+
+
+
+ MIT
+ https://opensource.org/license/mit/
+ repo
+
+
+
+
+
+ Christophe Debruyne
+ https://ChristopheDebruyne.be
+ University of Liège
+ https://www.uliege.be
+
+
+ Jakub Duchateau
+ https://jduchateau.github.io
+ University of Liège
+ https://www.uliege.be
+
+
+
+
+ https://github.com/kg-construct/BURP/
+ scm:git:git://github.com/kg-construct/BURP.git
+ scm:git:ssh://git@github.com/kg-construct/BURP.git
+
- 17
- 17
+ 21
+ 21
UTF-8
+ 5.6.0
+ 4.13.1
+ 2.0.5
@@ -29,17 +68,247 @@
**/*.ttl
+
+
+
+ rml-modules/rml-core/shapes
+ shapes
+
+ core.ttl
+
+
+
+ rml-modules/rml-cc/shapes
+ shapes
+
+ cc.ttl
+
+
+
+ rml-modules/rml-lv/shapes
+ shapes
+
+ lv.ttl
+
+
+
+ rml-modules/rml-io/shapes
+ shapes
+
+ io.ttl
+
+
+
+
+
+ rml-modules/rml-core/ontology
+ vocabularies/rml
+
+ *.owl
+
+
+
+ rml-modules/rml-cc/ontology
+ vocabularies/rml
+
+ *.owl
+
+
+
+ rml-modules/rml-io/ontology
+ vocabularies/rml
+
+ *.owl
+
+
+
+ rml-modules/rml-fnml/ontology
+ vocabularies/rml
+
+ *.owl
+
+
+
+ rml-modules/rml-lv/ontology
+ vocabularies/rml
+
+ *.owl
+
+
+
+
+ src/test/resources
+
+
+ rml-modules/rml-core/test-cases
+ rml-core
+
+
+ rml-modules/rml-cc/test-cases
+ rml-cc
+
+
+ rml-modules/rml-lv/test-cases
+ rml-lv
+
+
+ rml-modules/rml-io/test-cases
+ rml-io
+
+
+ rml-modules/rml-io-registry/test-cases
+ rml-io-registry
+
+
+ rml-modules/rml-fnml/test-cases
+ rml-fnml
+
+
+
maven-compiler-plugin
- 3.8.1
+ 3.11.0
- 17
+ 21
+ 21
+ 21
+
+
+ com.google.auto.service
+ auto-service
+ 1.1.1
+
+
+
+
+ org.antlr
+ antlr4-maven-plugin
+ ${antlr.version}
+
+
+ generate-sources
+ antlr
+
+ antlr4
+
+
+
+
+ true
+ false
+
+ -package
+ burp.parse.turtleprov
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+
+ generate-ptr
+ generate-sources
+
+ java
+
+
+ jena.schemagen
+
+ -i
+ ${project.basedir}/src/main/resources/vocabularies/ptr.ttl
+ -e
+ TURTLE
+ -o
+ ${project.build.directory}/generated-sources/vocabularies
+ --package
+ burp.vocabularies
+ -n
+ PTR
+ --ontology
+ --rdfs
+ --inference
+ -a
+ https://w3id.org/dre/ptr#
+
+ true
+
+
+
+ generate-rer
+ generate-sources
+
+ java
+
+
+ jena.schemagen
+
+ -i
+ ${project.basedir}/src/main/resources/vocabularies/rer.ttl
+ -e
+ TURTLE
+ -o
+ ${project.build.directory}/generated-sources/vocabularies
+ --package
+ burp.vocabularies
+ -n
+ RER
+ --ontology
+ --inference
+ --import
+ ${project.basedir}/src/main/resources/vocabularies/ptr.ttl
+ -a
+ https://w3id.org/dre/rer#
+ --declarations
+
+ static{M_MODEL.read(RER.class.getClassLoader().getResourceAsStream("vocabularies/rer.ttl"),
+ RER.NS, "TURTLE");}
+
+
+ true
+
+
+
+
+
+ org.apache.jena
+ jena-cmds
+ ${jena.version}
+
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.4.0
+
+
+ add-source
+ generate-sources
+
+ add-source
+
+
+
+ ${project.build.directory}/generated-sources/vocabularies
+ ${project.build.directory}/generated-sources/antlr4
+
+
+
+
+
+
org.apache.maven.plugins
maven-surefire-plugin
@@ -80,6 +349,9 @@
org.apache.maven.plugins
maven-javadoc-plugin
3.7.0
+
+ -Xdoclint:none
+
attach-javadocs
@@ -97,6 +369,8 @@
deploy
+
+
org.apache.maven.plugins
maven-shade-plugin
@@ -108,18 +382,19 @@
shade
+ false
true
+ implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
burp.Main
+ implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
+ implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer"/>
+ implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
false
@@ -138,6 +413,152 @@
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 3.2.8
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+
+
+
+
+ dev.sigstore
+ sigstore-maven-plugin
+ 2.2.0
+
+
+ sign
+
+ sign
+
+
+
+
+
+
+
+
+ org.sonatype.central
+ central-publishing-maven-plugin
+ 0.9.0
+ true
+
+ central
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.3.0
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.6.3
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+
+ org.jreleaser
+ jreleaser-maven-plugin
+ 1.12.0
+
+
+
+
+ A Basic and Unassuming RML Processor (BURP) with RML Execution Report (RER) error handling
+
+ Jakub Duchateau, Christophe Debruyne
+ MIT
+
+ https://github.com/kg-construct/BURP
+
+ 2026 Jakub Duchateau, 2024 Christophe Debruyne
+
+
+
+ kg-construct
+ BURP
+ github.com
+ true
+ true
+ true
+
+ true
+ ALWAYS
+ conventional-commits
+
+
+ true
+
+
+
+
+
+ ALWAYS
+ SINGLE_JAR
+
+ 21
+ burp.Main
+
+
+ burp
+
+
+
+ ${project.build.directory}/burp.jar
+
+
+
+
+
+
+ ALWAYS
+
+ ALWAYS
+ kg-construct
+ jbang-catalog
+
+
+ jduchateau
+ jduchateau@users.noreply.github.com
+
+
+
+
+
+
+
@@ -150,40 +571,72 @@
picocli
4.7.5
+
org.apache.jena
jena-arq
- 5.6.0
+ ${jena.version}
org.apache.jena
jena-shacl
- 5.6.0
+ ${jena.version}
- com.google.guava
- guava
- 33.6.0-jre
+ org.apache.jena
+ jena-iri3986
+ ${jena.version}
+
+
+ org.apache.jena
+ jena-ontapi
+ ${jena.version}
+
com.opencsv
opencsv
- 5.9
+ 5.12.0
+
+
+ com.univocity
+ univocity-parsers
+ 2.9.1
com.jayway.jsonpath
json-path
- 2.9.0
+ 2.10.0
net.minidev
json-smart
- 2.5.0
+ 2.6.0
+
+
+ at.asitplus
+ jsonpath4k-jvm
+ 3.1.1
+
+
+ com.strumenta
+ antlr-kotlin-runtime-jvm
+ 1.0.8
- mysql
- mysql-connector-java
- 8.0.33
+ org.jetbrains.kotlinx
+ kotlinx-serialization-json-jvm
+ 1.6.3
+
+
+ org.mongodb
+ bson-kotlinx
+ 5.9.0
+
+
+ com.mysql
+ mysql-connector-j
+ 9.3.0
org.postgresql
@@ -193,7 +646,7 @@
com.microsoft.sqlserver
mssql-jdbc
- 12.6.1.jre11
+ 13.2.1.jre11
org.xerial
@@ -205,6 +658,41 @@
commons-text
1.11.0
+
+ net.sf.saxon
+ Saxon-HE
+ 12.9
+
+
+ com.google.guava
+ guava
+ 33.4.8-jre
+
+
+ org.tukaani
+ xz
+ 1.9
+
+
+ org.antlr
+ antlr4-runtime
+ ${antlr.version}
+
+
+
+
+ com.github.candil-data-fabric
+ JNC
+ 1.3.0
+
+
+
+
+ com.google.auto.service
+ auto-service-annotations
+ 1.1.1
+ compile
+
@@ -220,50 +708,40 @@
test
- org.junit.jupiter
- junit-jupiter-params
- 5.10.2
- test
-
-
- org.testcontainers
- junit-jupiter
- 1.19.5
+ org.junit.jupiter
+ junit-jupiter-params
+ 5.10.2
test
org.testcontainers
- postgresql
- 1.19.5
+ testcontainers-junit-jupiter
+ ${testcontainers.version}
test
org.testcontainers
- mysql
- 1.19.5
+ testcontainers-postgresql
+ ${testcontainers.version}
test
- org.apache.jena
- jena-fuseki-main
- 4.10.0
+ org.testcontainers
+ testcontainers-mysql
+ ${testcontainers.version}
test
-
-
- org.tukaani
- xz
- 1.9
-
- com.univocity
- univocity-parsers
- 2.9.1
+ org.testcontainers
+ testcontainers-mssqlserver
+ ${testcontainers.version}
+ test
- com.github.candil-data-fabric
- JNC
- xpath-namespace-SNAPSHOT
+ org.apache.jena
+ jena-fuseki-main
+ ${jena.version}
+ test
diff --git a/rml-modules/rml-cc b/rml-modules/rml-cc
new file mode 160000
index 00000000..d97fddf0
--- /dev/null
+++ b/rml-modules/rml-cc
@@ -0,0 +1 @@
+Subproject commit d97fddf0f769f352d256a96b93544f3dfd192ffc
diff --git a/rml-modules/rml-core b/rml-modules/rml-core
new file mode 160000
index 00000000..82ab28d4
--- /dev/null
+++ b/rml-modules/rml-core
@@ -0,0 +1 @@
+Subproject commit 82ab28d46803ba66a83c133f1db371a60116f84d
diff --git a/rml-modules/rml-fnml b/rml-modules/rml-fnml
new file mode 160000
index 00000000..df68527f
--- /dev/null
+++ b/rml-modules/rml-fnml
@@ -0,0 +1 @@
+Subproject commit df68527f945eea0ac4f31285dc2113048107cc2c
diff --git a/rml-modules/rml-io b/rml-modules/rml-io
new file mode 160000
index 00000000..980b9062
--- /dev/null
+++ b/rml-modules/rml-io
@@ -0,0 +1 @@
+Subproject commit 980b90626d86394af91ed606f8493927d59d5e67
diff --git a/rml-modules/rml-io-registry b/rml-modules/rml-io-registry
new file mode 160000
index 00000000..3bb0c3ce
--- /dev/null
+++ b/rml-modules/rml-io-registry
@@ -0,0 +1 @@
+Subproject commit 3bb0c3ce7ada75d053d584faa58361ab74b6fdbd
diff --git a/rml-modules/rml-lv b/rml-modules/rml-lv
new file mode 160000
index 00000000..a6a23ddc
--- /dev/null
+++ b/rml-modules/rml-lv
@@ -0,0 +1 @@
+Subproject commit a6a23ddcd040f313d1ec7fe38668fc09a9ca07c0
diff --git a/src/main/antlr4/burp/parse/turtleprov/Turtle.g4 b/src/main/antlr4/burp/parse/turtleprov/Turtle.g4
new file mode 100644
index 00000000..b2090bb4
--- /dev/null
+++ b/src/main/antlr4/burp/parse/turtleprov/Turtle.g4
@@ -0,0 +1,418 @@
+/*
+ [The "BSD licence"]
+ Copyright (c) 2024, Jakub Duchateau (@ Université de Liège, https://www.uliege.be/)
+ Copyright (c) 2014, Alejandro Medrano (@ Universidad Politecnica de Madrid, http://www.upm.es/)
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+*/
+
+// Derived from https://www.w3.org/TR/rdf12-turtle/#sec-grammar-grammar
+
+
+// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false
+// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging
+
+grammar Turtle;
+
+// [1] turtleDoc ::= statement*
+turtleDoc
+ : statement* EOF
+ ;
+
+// [2] statement ::= directive | (triples '.')
+statement
+ : directive
+ | triples '.'
+ ;
+
+// [3] directive ::= prefixID | base | sparqlPrefix | sparqlBase
+directive
+ : prefixID
+ | base
+ | sparqlPrefix
+ | sparqlBase
+ ;
+
+// [4] prefixID ::= '@prefix' PNAME_NS IRIREF '.'
+prefixID
+ : '@prefix' PNAME_NS IRIREF '.'
+ ;
+
+// [5] base ::= '@base' IRIREF '.'
+base
+ : '@base' IRIREF '.'
+ ;
+
+// [6] sparqlPrefix ::= 'PREFIX' PNAME_NS IRIREF
+sparqlPrefix
+ : 'PREFIX' PNAME_NS IRIREF
+ ;
+
+// [7] sparqlBase ::= 'BASE' IRIREF
+sparqlBase
+ : 'BASE' IRIREF
+ ;
+
+// [8] triples ::= (subject predicateObjectList) | (blankNodePropertyList predicateObjectList?) | (reifiedTriple predicateObjectList?)
+triples
+ : subject predicateObjectList
+ | blankNodePropertyList predicateObjectList?
+ | reifiedTriple predicateObjectList?
+ ;
+
+// [9] predicateObjectList ::= verb objectList (';' (verb objectList)?)*
+predicateObjectList
+ : verb objectList (';' (verb objectList)?)*
+ ;
+
+// [10] objectList ::= object annotation (',' object annotation)*
+objectList
+ : object_ annotation (',' object_ annotation)*
+ ;
+
+// [11] verb ::= predicate | 'a'
+verb
+ : iri // from [13] predicate ::= iri
+ | 'a'
+ ;
+
+// [12] subject ::= iri | BlankNode | collection
+subject
+ : iri
+ | BlankNode
+ | collection
+ ;
+
+// [14] object ::= iri | BlankNode | collection | blankNodePropertyList | literal | tripleTerm | reifiedTriple
+object_
+ : iri
+ | BlankNode
+ | collection
+ | blankNodePropertyList
+ | literal
+ | tripleTerm
+ | reifiedTriple
+ ;
+
+// [15] literal ::= RDFLiteral | NumericLiteral | BooleanLiteral
+literal
+ : rdfLiteral
+ | NumericLiteral
+ | BooleanLiteral
+ ;
+
+// [16] blankNodePropertyList ::= '[' predicateObjectList ']'
+blankNodePropertyList
+ : '[' predicateObjectList ']'
+ ;
+
+// [17] collection ::= '(' object* ')'
+collection
+ : '(' object_* ')'
+ ;
+
+// [18] NumericLiteral ::= INTEGER | DECIMAL | DOUBLE
+NumericLiteral
+ : INTEGER
+ | DECIMAL
+ | DOUBLE
+ ;
+
+// [19] RDFLiteral ::= String (LANGTAG | '^^' iri)?
+rdfLiteral
+ : string (LANG_DIR | '^^' iri)?
+ ;
+
+// [20] BooleanLiteral ::= 'true' | 'false'
+BooleanLiteral
+ : 'true'
+ | 'false'
+ ;
+
+// [21] String ::= STRING_LITERAL_QUOTE | STRING_LITERAL_SINGLE_QUOTE | STRING_LITERAL_LONG_SINGLE_QUOTE | STRING_LITERAL_LONG_QUOTE
+string
+ : STRING_LITERAL_QUOTE
+ | STRING_LITERAL_SINGLE_QUOTE
+ | STRING_LITERAL_LONG_SINGLE_QUOTE
+ | STRING_LITERAL_LONG_QUOTE
+ ;
+
+// [22] iri ::= IRIREF | PrefixedName
+iri
+ : IRIREF
+ | PrefixedName
+ ;
+
+// [24] BlankNode ::= BLANK_NODE_LABEL | ANON
+BlankNode
+ : BLANK_NODE_LABEL
+ | ANON
+ ;
+
+// Turtle-Star
+
+// [25] reifier ::= '~' (iri | BlankNode)?
+reifier
+ : '~' (iri | BlankNode)?
+ ;
+
+// [26] reifiedTriple ::= '<<' rtSubject verb rtObject reifier? '>>'
+reifiedTriple
+ : '<<' rtSubject verb rtObject reifier? '>>'
+ ;
+
+// [27] rtSubject ::= iri | BlankNode | reifiedTriple
+rtSubject
+ : iri
+ | BlankNode
+ | reifiedTriple
+ ;
+
+// [28] rtObject ::= iri | BlankNode | literal | tripleTerm | reifiedTriple
+rtObject
+ : iri
+ | BlankNode
+ | literal
+ | tripleTerm
+ | reifiedTriple
+ ;
+
+// [29] tripleTerm ::= '<<(' ttSubject verb ttObject ')>>'
+tripleTerm
+ : '<<(' ttSubject verb ttObject ')>>'
+ ;
+
+// [30] ttSubject ::= iri | BlankNode
+ttSubject
+ : iri
+ | BlankNode
+ ;
+
+// [31] ttObject ::= iri | BlankNode | literal | tripleTerm
+ttObject
+ : iri
+ | BlankNode
+ | literal
+ | tripleTerm
+ ;
+
+// [32] annotation ::= (reifier | annotationBlock)*
+annotation
+ : (reifier | annotationBlock)*
+ ;
+
+// [33] annotationBlock ::= '{|' predicateObjectList '|}'
+annotationBlock
+ : '{|' predicateObjectList '|}'
+ ;
+
+//
+// Productions for terminals
+//
+
+// [35] IRIREF ::= '<' ([^#x00-#x20<>"{}|^`\] | UCHAR)* '>'
+IRIREF
+ : '<' (~[\u0000-\u0020<>"{}|^`\\] | UCHAR)* '>'
+ ;
+
+// [36] PNAME_NS ::= PN_PREFIX? ':'
+PNAME_NS
+ : PN_PREFIX? ':'
+ ;
+
+// [23] PrefixedName ::= PNAME_LN | PNAME_NS
+PrefixedName
+ : PNAME_LN
+ | PNAME_NS
+ ;
+
+// [37] PNAME_LN ::= PNAME_NS PN_LOCAL
+PNAME_LN
+ : PNAME_NS PN_LOCAL
+ ;
+
+// [50] WS ::= #x20 | #x9 | #xD | #xA
+WS
+ : ([\t\u000C] | ' ')+ -> skip
+ ;
+NL
+ : ('\r'?'\n') -> channel(HIDDEN)
+ ;
+
+// [55] PN_PREFIX ::= PN_CHARS_BASE ((PN_CHARS | '.')* PN_CHARS)?
+PN_PREFIX
+ : PN_CHARS_BASE ((PN_CHARS | '.')* PN_CHARS)?
+ ;
+
+// [38] BLANK_NODE_LABEL ::= '_:' (PN_CHARS_U | [0-9]) ((PN_CHARS | '.')* PN_CHARS)?
+BLANK_NODE_LABEL
+ : '_:' (PN_CHARS_U | [0-9]) ((PN_CHARS | '.')* PN_CHARS)?
+ ;
+
+// [39] LANG_DIR ::= '@' [a-zA-Z]+ ('-' [a-zA-Z0-9]+)* ('--' [a-zA-Z]+)?
+LANG_DIR
+ : '@' [a-zA-Z]+ ('-' [a-zA-Z0-9]+)* ('--' [a-zA-Z]+)?
+ ;
+
+// [40] INTEGER ::= [+-]? [0-9]+
+INTEGER
+ : [+-]? [0-9]+
+ ;
+
+// [41] DECIMAL ::= [+-]? [0-9]* '.' [0-9]+
+DECIMAL
+ : [+-]? [0-9]* '.' [0-9]+
+ ;
+
+// [42] DOUBLE ::= [+-]? ([0-9]+ '.' [0-9]* EXPONENT | '.' [0-9]+ EXPONENT | [0-9]+ EXPONENT)
+DOUBLE
+ : [+-]? ([0-9]+ '.' [0-9]* EXPONENT | '.' [0-9]+ EXPONENT | [0-9]+ EXPONENT)
+ ;
+
+// [43] EXPONENT ::= [eE] [+-]? [0-9]+
+EXPONENT
+ : [eE] [+-]? [0-9]+
+ ;
+
+// [46] STRING_LITERAL_LONG_SINGLE_QUOTE ::= "'''" (("'" | "''")? ([^'\] | ECHAR | UCHAR))* "'''"
+STRING_LITERAL_LONG_SINGLE_QUOTE
+ : '\'\'\'' (('\'' | '\'\'')? ((~ ['\\]) | ECHAR | UCHAR))* '\'\'\''
+ ;
+
+// [47] STRING_LITERAL_LONG_QUOTE ::= '"""' (('"' | '""')? ([^"\] | ECHAR | UCHAR))* '"""'
+STRING_LITERAL_LONG_QUOTE
+ : '"""' (('"' | '""')? ((~ ["\\]) | ECHAR | UCHAR))* '"""'
+ ;
+
+// [44] STRING_LITERAL_QUOTE ::= '"' ([^#x22#x5C#x0A#x0D] | ECHAR | UCHAR)* '"'
+STRING_LITERAL_QUOTE
+ : '"' ((~ [\u0022\u005C\u000A\u000D]) | ECHAR | UCHAR)* '"'
+ ;
+
+// [45] STRING_LITERAL_SINGLE_QUOTE ::= "'" ([^#x27#x5C#x0A#x0D] | ECHAR | UCHAR)* "'"
+STRING_LITERAL_SINGLE_QUOTE
+ : '\'' ((~ [\u0027\u005C\u000A\u000D]) | ECHAR | UCHAR)* '\''
+ ;
+
+// [48] UCHAR ::= '\u' HEX HEX HEX HEX | '\U' HEX HEX HEX HEX HEX HEX HEX HEX
+UCHAR
+ : '\\u' HEX HEX HEX HEX
+ | '\\U' HEX HEX HEX HEX HEX HEX HEX HEX
+ ;
+
+// [49] ECHAR ::= '\' [tbnrf\"']
+ECHAR
+ : '\\' [tbnrf"'\\]
+ ;
+
+// [51] ANON ::= '[' WS* ']'
+ANON_WS
+ : ' '
+ | '\t'
+ | '\r'
+ | '\n'
+ ;
+
+ANON
+ : '[' ANON_WS* ']'
+ ;
+
+// [52] PN_CHARS_BASE ::= ...
+PN_CHARS_BASE
+ : 'A' .. 'Z'
+ | 'a' .. 'z'
+ | '\u00C0' .. '\u00D6'
+ | '\u00D8' .. '\u00F6'
+ | '\u00F8' .. '\u02FF'
+ | '\u0370' .. '\u037D'
+ | '\u037F' .. '\u1FFF'
+ | '\u200C' .. '\u200D'
+ | '\u2070' .. '\u218F'
+ | '\u2C00' .. '\u2FEF'
+ | '\u3001' .. '\uD7FF'
+ | '\uF900' .. '\uFDCF'
+ | '\uFDF0' .. '\uFFFD'
+ | '\u{10000}' .. '\u{EFFFF}'
+ ;
+
+// [53] PN_CHARS_U ::= PN_CHARS_BASE | '_'
+PN_CHARS_U
+ : PN_CHARS_BASE
+ | '_'
+ ;
+
+// [54] PN_CHARS ::= PN_CHARS_U | '-' | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
+PN_CHARS
+ : PN_CHARS_U
+ | '-'
+ | [0-9]
+ | '\u00B7'
+ | [\u0300-\u036F]
+ | [\u203F-\u2040]
+ ;
+
+// [56] PN_LOCAL ::= (PN_CHARS_U | ':' | [0-9] | PLX) ((PN_CHARS | '.' | ':' | PLX)* (PN_CHARS | ':' | PLX))?
+PN_LOCAL
+ : (PN_CHARS_U | ':' | [0-9] | PLX) ((PN_CHARS | '.' | ':' | PLX)* (PN_CHARS | ':' | PLX))?
+ ;
+
+// [57] PLX ::= PERCENT | PN_LOCAL_ESC
+PLX
+ : PERCENT
+ | PN_LOCAL_ESC
+ ;
+
+// [58] PERCENT ::= '%' HEX HEX
+PERCENT
+ : '%' HEX HEX
+ ;
+
+// [59] HEX ::= [0-9] | [A-F] | [a-f]
+HEX
+ : [0-9]
+ | [A-F]
+ | [a-f]
+ ;
+
+// [60] PN_LOCAL_ESC ::= '\' ('_' | '~' | '.' | '-' | "!" | '$' | '&' | "'" | '(' | ')' | '*' | '+' | ',' | ';' | '=' | '/' | '?' | '#' | '@' | '%')
+PN_LOCAL_ESC
+ : '\\' (
+ '_'
+ | '~'
+ | '.'
+ | '-'
+ | '!'
+ | '$'
+ | '&'
+ | '\''
+ | '('
+ | ')'
+ | '*'
+ | '+'
+ | ','
+ | ';'
+ | '='
+ | '/'
+ | '?'
+ | '#'
+ | '@'
+ | '%'
+ )
+ ;
+
+LC
+ : '#' ~[\r\n]* -> channel(HIDDEN)
+ ;
\ No newline at end of file
diff --git a/src/main/java/burp/Main.java b/src/main/java/burp/Main.java
index d7ac6597..da96ddcf 100644
--- a/src/main/java/burp/Main.java
+++ b/src/main/java/burp/Main.java
@@ -1,303 +1,288 @@
package burp;
-import java.io.FileOutputStream;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
+import burp.model.*;
+import burp.model.rdf.*;
+import burp.parse.Parse;
+import burp.parse.PlanWiring;
+import burp.reporting.*;
+import burp.util.BURPConfiguration;
+import burp.util.Util;
+import burp.vocabularies.RER;
+import burp.vocabularies.RML;
+import org.apache.jena.datatypes.BaseDatatype;
import org.apache.jena.query.Dataset;
import org.apache.jena.query.DatasetFactory;
-import org.apache.jena.rdf.model.Container;
-import org.apache.jena.rdf.model.Model;
-import org.apache.jena.rdf.model.Property;
-import org.apache.jena.rdf.model.RDFList;
-import org.apache.jena.rdf.model.RDFNode;
-import org.apache.jena.rdf.model.Resource;
-import org.apache.jena.rdf.model.ResourceFactory;
-import org.apache.jena.rdf.model.Statement;
-import org.apache.jena.rdf.model.StmtIterator;
+import org.apache.jena.rdf.model.*;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFDataMgr;
-import org.apache.jena.util.ResourceUtils;
-import org.apache.jena.vocabulary.RDF;
-
-import burp.model.GraphMap;
-import burp.model.Iteration;
-import burp.model.ObjectMap;
-import burp.model.PredicateMap;
-import burp.model.PredicateObjectMap;
-import burp.model.ReferencingObjectMap;
-import burp.model.SubjectMap;
-import burp.model.TriplesMap;
-import burp.model.gathermaputil.SubGraph;
-import burp.parse.Parse;
-import burp.util.BURPConfiguration;
-import burp.vocabularies.RML;
+import org.apache.jena.riot.RDFLanguages;
+import org.jspecify.annotations.NonNull;
+import picocli.CommandLine;
-public class Main {
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
- private static List def = List.of(RML.defaultGraph);
-
- public static void main(String[] args) {
- int exit = doMain(args);
- System.out.println("System exiting with code: " + exit);
- System.exit(exit);
- }
-
- public static int doMain(String[] args) {
- try {
- // Process the configuration file
- BURPConfiguration conf = new BURPConfiguration(args);
-
- // Parse the mapping file
- List triplesmaps = Parse.parseMappingFile(conf.mappingFile);
-
- Dataset ds = generate(triplesmaps, conf.baseIRI);
-
- if (conf.outputFile != null)
- RDFDataMgr.write(new FileOutputStream(conf.outputFile), ds, Lang.NQ);
- else
- RDFDataMgr.write(System.out, ds, Lang.NQ);
-
- // It all went well, thus return 0
- return 0;
- } catch (Exception e) {
- e.printStackTrace();
- System.err.println(e.getMessage());
- return 1;
- }
- }
-
- private static Dataset generate(List triplesmaps, String givenBaseIRI) {
- Dataset ds = DatasetFactory.create();
-
- // Execute the triples maps
- for (TriplesMap tm : triplesmaps) {
- String baseIRI = tm.baseIRI == null ? givenBaseIRI : tm.baseIRI;
-
- // Let sm be the subject map of the triples map
- SubjectMap sm = tm.subjectMap;
-
- // Let sgm be the set of graph maps of subject maps
- List sgm = sm.graphMaps;
-
- // For each iteration i in iterations, apply the following steps:
- Iterator iter = tm.logicalSource.iterator();
- while (iter.hasNext()) {
- Iteration i = iter.next();
-
- // Let sgs be the set of the generated RDF terms
- // that result from applying each term map in sgm to i
- List sgs = sgm.isEmpty() ? def : new ArrayList();
- for (GraphMap gm : sgm) {
- sgs.addAll(gm.generateTerms(i, baseIRI));
- }
-
- // Let subjects be the generated RDF terms that result from applying sm to i
- List subjects = new ArrayList();
-
- if (!sm.isGatherMap()) {
- subjects.addAll(sm.generateTerms(i, baseIRI));
- } else {
- for (SubGraph subgraph : sm.generateGatherMapGraphs(i, baseIRI)) {
- subjects.add(subgraph.node);
- addToGraphs(ds, sgs, subgraph);
- }
- }
-
- // For each subject in subjects and each class in classes,
- // add triples to the output dataset as follows:
- // subject: subject
- // predicate: rdf:type
- // object: class
- // target graphs: If sgm is empty: rr:defaultgraph; otherwise: subject_graphs
-
- storeTriplesOfSubjectMaps(ds, sm.classes, subjects, sgs);
-
- // For each predicate-object map of the triples map, apply the following steps:
- // Let predicates be the set of generated RDF terms that result
- // from applying each of the predicate-object map's predicate maps to i
- // Let objects be the set of generated RDF terms that result from applying each
- // of the predicate-object map's object maps (but not referencing object maps)
- // to i
- // Let pogm be the set of graph maps of the predicate-object map
- // Let pogs be the set of generated RDF terms that result from applying each
- // graph map in pogm to i
- // For each possible combination in subjects X predicates X objects,
- // add triples to the output dataset as follows:
- // s: subject
- // p: predicate
- // o: object
- // Target graphs: If sgm and pogm are empty: rr:defaultGraph; otherwise:
- // union of subject_graphs and predicate-object_graphs
-
- for (PredicateObjectMap pom : tm.predicateObjectMaps) {
- List pogs = new ArrayList();
- for (GraphMap gm : pom.graphMaps) {
- pogs.addAll(gm.generateTerms(i, baseIRI));
- }
-
- List graphs = def;
- // If sgm and pogm are empty: rr:defaultGraph (see line above)
- if (!sgm.isEmpty() || !pogs.isEmpty()) {
- // otherwise: union of subject_graphs and predicate-object_graphs
- // we do an additional test as sgs contains rml:defaultGraph if sgm is empty
- // we do not want to include that
- pogs.addAll(!sgm.isEmpty() ? sgs : new ArrayList<>());
- graphs = pogs;
- }
-
- List predicates = new ArrayList();
- for (PredicateMap pm : pom.predicateMaps) {
- predicates.addAll(pm.generateTerms(i, baseIRI));
- }
-
- List objects = new ArrayList();
-
- for (ObjectMap om : pom.objectMaps) {
- if (!om.isGatherMap()) {
- objects.addAll(om.generateTerms(i, baseIRI));
- } else {
- for (SubGraph subgraph : om.generateGatherMapGraphs(i, baseIRI)) {
- objects.add(subgraph.node);
- addToGraphs(ds, graphs, subgraph);
- }
- }
- }
-
- for (ReferencingObjectMap rom : pom.refObjectMaps) {
- objects.addAll(rom.generateTerms(i, baseIRI));
- }
-
- storetriples(ds, subjects, predicates, objects, graphs);
- }
-
- }
-
- }
-
- removeJunk(ds);
-
- return ds;
- }
-
- private static void removeJunk(Dataset ds) {
- removeJunk(ds.getDefaultModel());
- Iterator iter = ds.listModelNames();
- while (iter.hasNext())
- removeJunk(ds.getNamedModel(iter.next()));
- }
-
- private static void removeJunk(Model model) {
- StmtIterator s = model.listStatements(null, RDF.type, RML.list);
- while (s.hasNext()) {
- Statement statement = s.next();
- s.remove();
-
- Resource l = statement.getSubject();
- if (!l.hasProperty(RDF.first)) {
- ResourceUtils.renameResource(l, RDF.nil.toString());
- }
- }
- }
-
- private static void storetriples(Dataset ds, List subjects, List predicates,
- List objects, List graphs) {
-
- for (RDFNode s : subjects) {
- Resource sr = s.asResource();
- for (RDFNode p : predicates) {
- Property pp = ResourceFactory.createProperty(p.asResource().getURI());
- for (RDFNode o : objects)
- for (RDFNode g : graphs)
- getModel(ds, g).add(sr, pp, o);
- }
- }
- }
-
- private static void storeTriplesOfSubjectMaps(Dataset ds, List classes, List subjects,
- List graphs) {
-
- for (RDFNode s : subjects) {
- Resource sr = s.asResource();
-
- for (RDFNode c : classes)
- for (RDFNode g : graphs)
- getModel(ds, g).add(sr, RDF.type, c);
- }
- }
-
- private static void addToGraphs(Dataset ds, List graphs, SubGraph subgraph) {
- for (RDFNode graph : graphs) {
- Model g = getModel(ds, graph);
- Resource r = subgraph.node.asResource();
-
- if(subgraph.isList()) {
- g.add(r, RDF.type, RML.list);
-
- try {
- RDFList l = g.getList(r);
- RDFList sub = subgraph.model.getList(r);
-
- List elements = sub.iterator().toList();
- for(RDFNode e : elements) {
- l.add(e);
- }
-
- while(true) {
- if(sub.isEmpty())
- break;
- sub = sub.removeHead();
- }
-
- g.add(subgraph.model);
- } catch (Exception e) {
- // List did not exist, so we can just add it
- g.add(subgraph.model);
- }
-
- } else {
- Container c = null;
- Container sub = null;
- if(subgraph.isAlt()) {
- g.add(r, RDF.type, RDF.Alt);
- c = g.getAlt(r);
- sub = subgraph.model.getAlt(r);
- } else if(subgraph.isBag()) {
- g.add(r, RDF.type, RDF.Bag);
- c = g.getAlt(r);
- sub = subgraph.model.getBag (r);
- } else if(subgraph.isSeq()) {
- g.add(r, RDF.type, RDF.Seq);
- c = g.getAlt(r);
- sub = subgraph.model.getSeq(r);
- }
-
- // Now amend everything so that
- // we append the containers
- List elements = sub.iterator().toList();
- for(RDFNode e : elements)
- c.add(e);
-
- StmtIterator iter = sub.listProperties();
- while(iter.hasNext()) {
- Statement s = iter.next();
- if(s.getSubject().equals(r))
- if(s.getPredicate().getURI().startsWith("http://www.w3.org/1999/02/22-rdf-syntax-ns#_"))
- iter.remove();
- }
-
- // We all all the remaining triples
- g.add(subgraph.model);
-
- }
- }
- }
-
- private static Model getModel(Dataset ds, RDFNode g) {
- if (g.equals(RML.defaultGraph))
- return ds.getDefaultModel();
- return ds.getNamedModel(g.asResource());
- }
+public class Main {
+ public static void main(String[] args) {
+ Path cwd = Paths.get("").toAbsolutePath();
+ int exit = doMain(args, cwd);
+ System.out.println("System exiting with code: " + exit);
+ System.exit(exit);
+ }
+
+ public static int doMain(String[] args, Path currentWorkingDirectory) {
+ BURPConfiguration conf = new BURPConfiguration();
+ CommandLine cmd = new CommandLine(conf);
+ try {
+ cmd.parseArgs(args);
+ if (cmd.isUsageHelpRequested()) {
+ cmd.usage(System.out);
+ return 0;
+ } else if (cmd.isVersionHelpRequested()) {
+ cmd.printVersionHelp(System.out);
+ return 0;
+ }
+ } catch (CommandLine.ParameterException ex) {
+ System.err.println(ex.getMessage());
+ ex.getCommandLine().usage(System.err);
+ return 1;
+ }
+
+ return doMain(
+ conf.mappingFile,
+ conf.outputFile,
+ conf.getOutputFormat(),
+ conf.reportFile,
+ conf.baseIRI,
+ currentWorkingDirectory
+ );
+ }
+
+ public static RmlExecutionReport report;
+ public static String baseIRI;
+ public static Path mappingFile;
+
+ public static int doMain(
+ String mappingFilePath,
+ String outputFilePath,
+ Lang outputFormat,
+ String reportFilePath,
+ String baseIRIValue,
+ Path currentWorkingDirectory
+ ) {
+ report = new RmlExecutionReport();
+ baseIRI = baseIRIValue;
+ mappingFile = Path.of(mappingFilePath);
+
+ try {
+ Parse parser = new Parse();
+ List triplesMaps;
+ try {
+ triplesMaps = parser.parseMappingFile(mappingFile, currentWorkingDirectory);
+ } catch (BurpException burpException) {
+ throw burpException;
+ } catch (Exception e) {
+ throw new BurpException(new RmlError(
+ "Unknown Error while Parsing " + mappingFilePath,
+ new Origin(),
+ RER.RDFMappingSyntaxError,
+ e
+ ));
+ }
+ if (triplesMaps.isEmpty()) {
+ report.getErrors().add(Errors.NoTriplesMap());
+ }
+
+ // Wire AST tree
+ MappingDocument document = new MappingDocument(triplesMaps);
+ PlanWiring.wire(document);
+ report.setExecutionPlan(document);
+
+ Map statementsPerMap = new HashMap<>();
+ for (TriplesMap map : triplesMaps) {
+ statementsPerMap.put(map, map.countGeneratedStatements);
+ }
+ report.getStatistics().setGeneratedStatementPerTriplesMap(statementsPerMap);
+
+ List statements = document.generate();
+
+ statementsPerMap = new HashMap<>();
+ for (TriplesMap map : triplesMaps) {
+ statementsPerMap.put(map, map.countGeneratedStatements);
+ }
+ report.getStatistics().setGeneratedStatementPerTriplesMap(statementsPerMap);
+
+ List defaultStatements = new ArrayList<>();
+ Map> statementsByTarget = new HashMap<>();
+
+ for (RdfStatement stmtLike : statements) {
+ if (stmtLike == null) continue;
+ if (stmtLike.targets() == null || stmtLike.targets().isEmpty()) {
+ defaultStatements.add(stmtLike);
+ } else {
+ for (LogicalTarget target : stmtLike.targets()) {
+ statementsByTarget.computeIfAbsent(target, k -> new ArrayList<>()).add(stmtLike);
+ }
+ }
+ }
+
+ // Write default statements if there are any, or if we have an explicit output file (to create an empty file if needed)
+ if (!defaultStatements.isEmpty() || outputFilePath != null) {
+ Lang lang = outputFormat != null ? outputFormat :
+ (outputFilePath != null ? RDFLanguages.pathnameToLang(outputFilePath) : null);
+ if (lang == null) lang = Lang.NQ;
+
+ if (outputFilePath != null) {
+ try (FileOutputStream output = new FileOutputStream(outputFilePath)) {
+ writeStatements(output, defaultStatements, lang, StandardCharsets.UTF_8);
+ }
+ } else {
+ writeStatements(System.out, defaultStatements, lang, StandardCharsets.UTF_8);
+ }
+ }
+
+ // Write target statements
+ for (var entry : statementsByTarget.entrySet()) {
+ LogicalTarget target = entry.getKey();
+ List stmts = entry.getValue();
+ RMLTarget t = target.getTarget();
+ if (t instanceof FilePathTarget fpt) {
+ File resolvedPath;
+ if (RML.MappingDirectory.equals(fpt.getRoot())) {
+ resolvedPath = mappingFile.getParent().resolve(fpt.getPath()).toFile();
+ } else {
+ resolvedPath = currentWorkingDirectory.resolve(fpt.getPath()).toFile();
+ }
+
+ Lang targetLang = null;
+ if (target.getSerialization() != null)
+ targetLang = getTargetLang(target.getSerialization(), resolvedPath);
+ if (targetLang == null) targetLang = RDFLanguages.pathnameToLang(resolvedPath.getName());
+ if (targetLang == null) targetLang = Lang.NQ;
+
+ Charset targetEncoding = StandardCharsets.UTF_8;
+ if (target.getEncoding() != null)
+ targetEncoding = getStandardCharsets(target.getEncoding());
+
+ if (resolvedPath.getParentFile() != null)
+ resolvedPath.getParentFile().mkdirs();
+
+ final Lang finalLang = targetLang;
+ final Charset finalEncoding = targetEncoding;
+ Util.writeCompressedFile(resolvedPath, target.getCompression(), output -> writeStatements(output, stmts, finalLang, finalEncoding));
+ }
+ }
+ } catch (BurpException e) {
+ report.getErrors().add(e.getError());
+ } catch (Exception e) {
+ report.getErrors().add(Errors.UnexpectedError(e));
+ } finally {
+ System.out.print(PlainTextReportGenerator.generateTextReport(report));
+ if (reportFilePath != null && !reportFilePath.isBlank()) {
+ RdfReportGenerator.generateRdfReport(report, reportFilePath);
+ }
+ }
+
+ return report.getErrors().isEmpty() ? 0 : 1;
+ }
+
+ private static Lang getTargetLang(Resource targetSerialization, File resolvedPath) {
+ return switch (targetSerialization.getURI()) {
+ case "http://www.w3.org/ns/formats/N-Quads" -> Lang.NQ;
+ case "http://www.w3.org/ns/formats/N-Triples" -> Lang.NT;
+ case "http://www.w3.org/ns/formats/Turtle" -> Lang.TURTLE;
+ case "http://www.w3.org/ns/formats/JSON-LD" -> Lang.JSONLD;
+ case "http://www.w3.org/ns/formats/RDF_XML" -> Lang.RDFXML;
+ case "http://www.w3.org/ns/formats/RDF_JSON" -> Lang.RDFJSON;
+ case "http://www.w3.org/ns/formats/TriG" -> Lang.TRIG;
+ default -> RDFLanguages.pathnameToLang(resolvedPath.getName());
+ };
+ }
+
+ private static @NonNull Charset getStandardCharsets(Resource encoding) {
+ if (encoding.equals(RML.UTF8)) return StandardCharsets.UTF_8;
+ else if (encoding.equals(RML.UTF16)) return StandardCharsets.UTF_16;
+ return StandardCharsets.UTF_8;
+ }
+
+ private static void writeStatements(OutputStream output, List statements, Lang lang, Charset encoding) {
+ if (lang == Lang.NQ || lang == Lang.NT) {
+ NQuadsWriter.write(output, statements, encoding);
+ report.getStatistics().setGeneratedStatements((long) statements.size());
+ return;
+ }
+
+ Dataset ds = generateDataset(statements);
+ if (RDFLanguages.isQuads(lang)) {
+ RDFDataMgr.write(output, ds, lang);
+ } else {
+ RDFDataMgr.write(output, ds.getDefaultModel(), lang);
+ }
+ }
+
+ private static Dataset generateDataset(List statements) {
+ Dataset ds = DatasetFactory.create();
+
+ Map bnodeMap = new HashMap<>();
+
+ for (RdfStatement stmt : statements) {
+ IRITerm graph = stmt.getGraph();
+ Model model;
+ if (graph == null || RML.defaultGraph.getURI().equals(graph.uri())) {
+ model = ds.getDefaultModel();
+ } else {
+ model = ds.getNamedModel(graph.uri());
+ }
+
+ BlankNodeOrIRI sub = stmt.getSubject();
+ Resource s;
+ if (sub instanceof IRITerm) {
+ s = ResourceFactory.createResource(((IRITerm) sub).uri());
+ } else if (sub instanceof BlankNodeTerm) {
+ String id = ((BlankNodeTerm) sub).id();
+ s = bnodeMap.computeIfAbsent(id, k -> ResourceFactory.createResource());
+ } else {
+ throw new RuntimeException("Subject must be URI or BlankNode");
+ }
+
+ Property p = ResourceFactory.createProperty(stmt.getPredicate().uri());
+
+ Term obj = stmt.getObject();
+ RDFNode o;
+ switch (obj) {
+ case IRITerm iriTerm -> o = ResourceFactory.createResource(iriTerm.uri());
+ case BlankNodeTerm blankNodeTerm -> {
+ String id = blankNodeTerm.id();
+ o = bnodeMap.computeIfAbsent(id, k -> ResourceFactory.createResource());
+ }
+ case LiteralTerm lit -> {
+ if (lit.language() != null) {
+ o = ResourceFactory.createLangLiteral(lit.value(), lit.language());
+ } else if (lit.datatype() != null) {
+ o = ResourceFactory.createTypedLiteral(lit.value(), new BaseDatatype(lit.datatype().uri()));
+ } else {
+ o = ResourceFactory.createTypedLiteral(lit.value());
+ }
+ }
+ case null, default -> throw new RuntimeException("Unsupported object term " + obj);
+ }
+ model.add(s, p, o);
+ }
+
+ long count = ds.getDefaultModel().size();
+ Iterator names = ds.listNames();
+ while (names.hasNext()) {
+ count += ds.getNamedModel(names.next()).size();
+ }
+ report.getStatistics().setGeneratedStatements(count);
+
+ return ds;
+ }
}
diff --git a/src/main/java/burp/NQuadsWriter.java b/src/main/java/burp/NQuadsWriter.java
new file mode 100644
index 00000000..edf173a0
--- /dev/null
+++ b/src/main/java/burp/NQuadsWriter.java
@@ -0,0 +1,177 @@
+package burp;
+
+import burp.model.RdfStatement;
+import burp.model.rdf.*;
+import burp.vocabularies.RML;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+public class NQuadsWriter {
+
+ public static void write(OutputStream output, List statements) {
+ write(output, statements, StandardCharsets.UTF_8);
+ }
+
+ public static void write(OutputStream output, List statements, Charset charset) {
+ BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, charset));
+ try {
+ for (RdfStatement statement : statements) {
+ writer.append(serializeStatement(statement)).append('\n');
+ }
+ writer.flush();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static String serializeStatement(RdfStatement statement) {
+ String subject = serializeSubject(statement.getSubject());
+ String predicate = serializeIRI(statement.getPredicate());
+ String obj = serializeObject(statement.getObject());
+ String graph = serializeGraph(statement.getGraph());
+ if (graph == null) {
+ return subject + " " + predicate + " " + obj + " .";
+ } else {
+ return subject + " " + predicate + " " + obj + " " + graph + " .";
+ }
+ }
+
+ private static String serializeSubject(BlankNodeOrIRI subject) {
+ if (subject instanceof IRITerm) {
+ return serializeIRI((IRITerm) subject);
+ } else if (subject instanceof BlankNodeTerm) {
+ return serializeBlankNode((BlankNodeTerm) subject);
+ } else {
+ throw new IllegalArgumentException("Unsupported subject term " + subject);
+ }
+ }
+
+ private static String serializeObject(Term obj) {
+ return switch (obj) {
+ case IRITerm iriTerm -> serializeIRI(iriTerm);
+ case BlankNodeTerm blankNodeTerm -> serializeBlankNode(blankNodeTerm);
+ case LiteralTerm literalTerm -> serializeLiteral(literalTerm);
+ case null, default -> throw new IllegalArgumentException("Unsupported object term " + obj);
+ };
+ }
+
+ private static String serializeGraph(IRITerm graph) {
+ if (graph == null || RML.defaultGraph.getURI().equals(graph.uri())) return null;
+ return serializeIRI(graph);
+ }
+
+ private static String serializeIRI(IRITerm iri) {
+ return "<" + iri.uri() + ">";
+ }
+
+ private static String serializeBlankNode(BlankNodeTerm blankNode) {
+ String label = blankNode.id().startsWith("_:") ? blankNode.id() : "_:" + blankNode.id();
+ return normalizeBlankNodeLabel(label);
+ }
+
+ private static boolean isValidBlankNodeLabel(String label) {
+ if (!label.startsWith("_:") || label.length() < 3) return false;
+
+ String id = label.substring(2);
+
+ char firstChar = id.charAt(0);
+ if (!isValidFirstBlankNodeChar(firstChar)) return false;
+
+ if (id.length() == 1) return true;
+
+ for (int i = 1; i < id.length(); i++) {
+ if (!isValidEncodableBlankNodeChar(id.charAt(i))) return false;
+ }
+
+ return true;
+ }
+
+ private static boolean isValidFirstBlankNodeChar(char c) {
+ return Character.isLetterOrDigit(c) || c == '_';
+ }
+
+ private static boolean isValidEncodableBlankNodeChar(char c) {
+ return Character.isLetterOrDigit(c) || c == '_' || c == '-';
+ }
+
+ private static String normalizeBlankNodeLabel(String label) {
+ if (isValidBlankNodeLabel(label)) {
+ return label;
+ } else {
+ String id = label.substring(2);
+ StringBuilder encoded = new StringBuilder("_:");
+
+ for (int i = 0; i < id.length(); i++) {
+ char ch = id.charAt(i);
+ if (isValidEncodableBlankNodeChar(ch)) {
+ encoded.append(ch);
+ } else {
+ encoded.append('_');
+ String hex = Integer.toHexString((int) ch);
+ if (hex.length() < 2) encoded.append('0');
+ encoded.append(hex);
+ }
+ }
+ return encoded.toString();
+ }
+ }
+
+ private static String serializeLiteral(LiteralTerm literal) {
+ String lexicalForm = escapeLiteral(literal.value());
+ String suffix = "";
+ if (literal.language() != null) {
+ suffix = "@" + literal.language();
+ } else if (literal.datatype() != null && !literal.datatype().uri().equals("http://www.w3.org/2001/XMLSchema#string")) {
+ suffix = "^^" + serializeIRI(literal.datatype());
+ }
+ return "\"" + lexicalForm + "\"" + suffix;
+ }
+
+ static String escapeLiteral(String value) {
+ StringBuilder escaped = new StringBuilder(value.length());
+ for (int i = 0; i < value.length(); i++) {
+ char ch = value.charAt(i);
+ int chCode = value.codePointAt(i);
+ switch (ch) {
+ case '\\':
+ escaped.append("\\\\");
+ break;
+ case '"':
+ escaped.append("\\\"");
+ break;
+ case '\n':
+ escaped.append("\\n");
+ break;
+ case '\r':
+ escaped.append("\\r");
+ break;
+ case '\t':
+ escaped.append("\\t");
+ break;
+ case '\b':
+ escaped.append("\\b");
+ break;
+ case '\u000C':
+ escaped.append("\\f");
+ break;
+ default:
+ if (chCode < 0x20) {
+ escaped.append("\\u");
+ String hex = Integer.toHexString(ch);
+ escaped.repeat("0", 4 - hex.length());
+ escaped.append(hex);
+ } else {
+ escaped.append(ch);
+ }
+ break;
+ }
+ }
+ return escaped.toString();
+ }
+}
diff --git a/src/main/java/burp/ls/CSVIteration.java b/src/main/java/burp/ls/CSVIteration.java
new file mode 100644
index 00000000..62a2f066
--- /dev/null
+++ b/src/main/java/burp/ls/CSVIteration.java
@@ -0,0 +1,36 @@
+package burp.ls;
+
+import burp.model.Iteration;
+import com.opencsv.CSVWriter;
+
+import java.io.StringWriter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class CSVIteration extends Iteration {
+ public final Map map = new LinkedHashMap<>();
+
+ public CSVIteration(String[] header, String[] rec, Set