From 9d65208de5f944291db646ee3d6c2052787a9cb4 Mon Sep 17 00:00:00 2001
From: dachafra <12400271+dachafra@users.noreply.github.com>
Date: Sat, 30 May 2026 13:00:59 +0200
Subject: [PATCH] chore: regenerate docs from data update
---
docs/2026-05-30/index.html | 733 ++++++++++++++++++++++++
docs/2026-05-30/resources/css/style.css | 63 ++
docs/2026-05-30/resources/js/app.js | 518 +++++++++++++++++
docs/index.html | 2 +-
4 files changed, 1315 insertions(+), 1 deletion(-)
create mode 100644 docs/2026-05-30/index.html
create mode 100644 docs/2026-05-30/resources/css/style.css
create mode 100644 docs/2026-05-30/resources/js/app.js
diff --git a/docs/2026-05-30/index.html b/docs/2026-05-30/index.html
new file mode 100644
index 0000000..d4dbb44
--- /dev/null
+++ b/docs/2026-05-30/index.html
@@ -0,0 +1,733 @@
+
+
+
+
+
+
+
+
+RML Implementation Report
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Abstract
+
+ This document reports implementation results for the RML specification family across six modules:
+ RML-Core, RML-IO, RML-FNML, RML-CC, RML-LV, and RML-star. Engine metadata is loaded from this repository, while test-case metadata is pulled from the module repositories and engine results are loaded from one CSV file per engine.
+
+
+
+Status of This Document
+ This document is a draft of a potential specification. It has no official
+ standing of any kind and does not represent the support or consensus of
+ any standards organization.
+
+
+ This is a living implementation report intended to be regenerated automatically from module test-case inventories and per-engine result CSV files, without requiring any server-side component.
+
+Table of Contents Abstract Status of This Document 1. Introduction2. Modules covered3. Processors4. Coverage summary by engine and module5. Implementation test results5.1 RML-Core5.2 RML-IO5.3 RML-FNML5.4 RML-CC5.5 RML-LV5.6 RML-star
+
+
+
+
+ The goal of this report is to provide a lightweight, static, GitHub-friendly implementation report for the RML test suite. The report groups test cases by module and computes implementation coverage automatically in the browser.
+
+
+
+
+
+
+ RML-Core : schema transformations
+ RML-IO : source and target data
+ RML-FNML : transformation functions
+ RML-CC : collections & containers
+ RML-LV : views over data sources
+ RML-star : test-cases are not provide because the spec needs to adapted for RDF 1.2
+
+
+
+
+
+
+
+
CSV data could not be loaded automatically.
+
Failed to fetch
+
If you open dev.html directly from your disk, the browser may block access to local or remote files.
+
For local testing, run a static server in this folder, for example:
+
python3 -m http.server 8000
+
and then open http://localhost:8000/dev.html.
+
+
+
+
+
+
+
+
CSV data could not be loaded automatically.
+
Failed to fetch
+
If you open dev.html directly from your disk, the browser may block access to local or remote files.
+
For local testing, run a static server in this folder, for example:
+
python3 -m http.server 8000
+
and then open http://localhost:8000/dev.html.
+
+
+
+
+
+
+
+
+
+
CSV data could not be loaded automatically.
+
Failed to fetch
+
If you open dev.html directly from your disk, the browser may block access to local or remote files.
+
For local testing, run a static server in this folder, for example:
+
python3 -m http.server 8000
+
and then open http://localhost:8000/dev.html.
+
+
+
+
+
+
+
CSV data could not be loaded automatically.
+
Failed to fetch
+
If you open dev.html directly from your disk, the browser may block access to local or remote files.
+
For local testing, run a static server in this folder, for example:
+
python3 -m http.server 8000
+
and then open http://localhost:8000/dev.html.
+
+
+
+
+
+
+
CSV data could not be loaded automatically.
+
Failed to fetch
+
If you open dev.html directly from your disk, the browser may block access to local or remote files.
+
For local testing, run a static server in this folder, for example:
+
python3 -m http.server 8000
+
and then open http://localhost:8000/dev.html.
+
+
+
+
+
+
+
CSV data could not be loaded automatically.
+
Failed to fetch
+
If you open dev.html directly from your disk, the browser may block access to local or remote files.
+
For local testing, run a static server in this folder, for example:
+
python3 -m http.server 8000
+
and then open http://localhost:8000/dev.html.
+
+
+
+
+
+
+
CSV data could not be loaded automatically.
+
Failed to fetch
+
If you open dev.html directly from your disk, the browser may block access to local or remote files.
+
For local testing, run a static server in this folder, for example:
+
python3 -m http.server 8000
+
and then open http://localhost:8000/dev.html.
+
+
+
+
+
+
+
CSV data could not be loaded automatically.
+
Failed to fetch
+
If you open dev.html directly from your disk, the browser may block access to local or remote files.
+
For local testing, run a static server in this folder, for example:
+
python3 -m http.server 8000
+
and then open http://localhost:8000/dev.html.
+
+
+
+
+
+
+
+
+
+ ↑
+
\ No newline at end of file
diff --git a/docs/2026-05-30/resources/css/style.css b/docs/2026-05-30/resources/css/style.css
new file mode 100644
index 0000000..f3cc129
--- /dev/null
+++ b/docs/2026-05-30/resources/css/style.css
@@ -0,0 +1,63 @@
+body { font-family: system-ui, sans-serif; }
+table { width:100%; border-collapse:separate; border-spacing:0; margin:1rem 0 2rem; font-size:.95rem; table-layout:fixed; box-shadow:0 8px 22px rgba(15, 23, 42, 0.06); border-radius:10px; overflow:hidden; }
+th, td { border-right:1px solid #d9d9d9; border-bottom:1px solid #d9d9d9; padding:.65rem .7rem; vertical-align:middle; overflow-wrap:anywhere; word-break:break-word; }
+th:first-child, td:first-child { border-left:1px solid #d9d9d9; }
+thead th { background:#eef3f8; color:#243447; font-weight:700; }
+thead tr:first-child th { border-top:1px solid #d9d9d9; }
+tbody tr:nth-child(even) td:first-child { background:#fafbfd; }
+tbody tr:hover td:first-child { background:#eef6ff; }
+section[id^="results-rml-"] { margin-top:1.5rem; }
+section[id^="results-rml-"] > h3 { margin-bottom:.35rem; }
+th:first-child, td:first-child { text-align:left; background:#fff; }
+th:not(:first-child), td:not(:first-child) { text-align:center; }
+th code, td code { white-space:normal; overflow-wrap:anywhere; word-break:break-word; }
+.summary-table { table-layout:auto; }
+.summary-table th:first-child, .summary-table td:first-child { min-width:12rem; }
+.summary-table .summary-metric {
+ white-space: nowrap;
+ min-width: 0;
+ width: 1%;
+ padding-left: .45rem;
+ padding-right: .45rem;
+ font-size: .82rem;
+ letter-spacing: -.01em;
+}
+section[id^="results-rml-"] table th:first-child,
+section[id^="results-rml-"] table td:first-child { width:15rem; min-width:15rem; }
+.wrap-header { white-space:normal; line-height:1.15; }
+.result-td { padding:0; text-align:center; }
+.result-td-empty { padding:.65rem .7rem; }
+.status { display:flex; align-items:center; justify-content:center; width:100%; min-height:3rem; padding:.7rem .35rem; font-weight:700; text-transform:lowercase; box-sizing:border-box; }
+.status-passed { background:#1f7a1f; color:white; }
+.status-failed { background:#b42318; color:white; }
+.status-inapplicable { background:#6b7280; color:white; }
+.result-cell { display:block; position:relative; cursor:default; }
+.result-cell.is-empty { color:#7a7a7a; cursor:default; }
+.result-note {
+ position: absolute;
+ top: .35rem;
+ right: .35rem;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 1.1rem;
+ min-width: 1.1rem;
+ height: 1.1rem;
+ padding: 0;
+ border: 0;
+ border-radius: 50%;
+ background: rgba(255,255,255,.24);
+ color: #fff;
+ font-size: .72rem;
+ line-height: 1;
+ font-weight: 800;
+ cursor: pointer;
+ appearance: none;
+ -webkit-appearance: none;
+}
+.result-popover { position:absolute; top:calc(100% + .35rem); right:.35rem; z-index:3; width:min(20rem, calc(100vw - 3rem)); padding:.65rem .75rem; border-radius:.6rem; background:#18212f; color:#fff; text-align:left; font-size:.85rem; line-height:1.35; box-shadow:0 10px 28px rgba(15, 23, 42, .28); }
+.result-popover[hidden] { display:none; }
+.result-td.note-open { overflow:visible; position:relative; z-index:2; }
+.small { color:#555; font-size:.9rem; }
+.summary-tail { white-space: nowrap; }
+.error-box { border:1px solid #ef4444; background:#fef2f2; padding:1rem; border-radius:8px; }
diff --git a/docs/2026-05-30/resources/js/app.js b/docs/2026-05-30/resources/js/app.js
new file mode 100644
index 0000000..a0f57e8
--- /dev/null
+++ b/docs/2026-05-30/resources/js/app.js
@@ -0,0 +1,518 @@
+const VALID_STATUSES = ['passed', 'failed', 'inapplicable'];
+
+function csvTextToObjects(text) {
+ const cleaned = text.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
+ if (!cleaned.trim()) return [];
+
+ const rows = [];
+ let row = [];
+ let value = '';
+ let inQuotes = false;
+
+ for (let i = 0; i < cleaned.length; i++) {
+ const ch = cleaned[i];
+
+ if (ch === '"') {
+ if (inQuotes && cleaned[i + 1] === '"') {
+ value += '"';
+ i++;
+ } else {
+ inQuotes = !inQuotes;
+ }
+ continue;
+ }
+
+ if (ch === ',' && !inQuotes) {
+ row.push(value);
+ value = '';
+ continue;
+ }
+
+ if (ch === '\n' && !inQuotes) {
+ row.push(value);
+ if (row.some(cell => cell !== '')) rows.push(row);
+ row = [];
+ value = '';
+ continue;
+ }
+
+ value += ch;
+ }
+
+ row.push(value);
+ if (row.some(cell => cell !== '')) rows.push(row);
+ if (!rows.length) return [];
+
+ const headers = rows[0];
+ return rows.slice(1).map(values => {
+ const obj = {};
+ headers.forEach((header, index) => {
+ obj[header] = values[index] ?? '';
+ });
+ return obj;
+ });
+}
+
+async function fetchCsv(path) {
+ const response = await fetch(path, { cache: 'no-store' });
+ if (!response.ok) {
+ const error = new Error(`Could not load ${path} (${response.status})`);
+ error.status = response.status;
+ error.path = path;
+ throw error;
+ }
+ return csvTextToObjects(await response.text());
+}
+
+function readEmbeddedData() {
+ const element = document.getElementById('report-data');
+ if (!element) return null;
+
+ try {
+ return JSON.parse(element.textContent);
+ } catch (error) {
+ console.error('Could not parse embedded report data', error);
+ return null;
+ }
+}
+
+function escapeHtml(str) {
+ return String(str)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+}
+
+function plainCell(value, fallback = '-') {
+ return value ? escapeHtml(value) : fallback;
+}
+
+function linkCell(url) {
+ if (!url) return '-';
+ return `${escapeHtml(url)} `;
+}
+
+function statusCell(status) {
+ return `${escapeHtml(status)} `;
+}
+
+function formatModuleHeader(moduleName) {
+ if (moduleName.startsWith('RML-')) {
+ return `RML- ${escapeHtml(moduleName.slice(4))}`;
+ }
+ return escapeHtml(moduleName);
+}
+
+function formatEngineHeader(name) {
+ const camelSplit = name.match(/^([A-Z]?[a-z0-9]+)([A-Z].*)$/);
+ if (camelSplit) {
+ return `${escapeHtml(camelSplit[1])} ${escapeHtml(camelSplit[2])}`;
+ }
+
+ const hyphenIndex = name.indexOf('-');
+ if (hyphenIndex > 0) {
+ return `${escapeHtml(name.slice(0, hyphenIndex))} ${escapeHtml(name.slice(hyphenIndex + 1))}`;
+ }
+
+ return escapeHtml(name);
+}
+
+function resultCell(result) {
+ if (!result) return '- ';
+
+ const note = (result.notes || '').trim();
+ const noteButton = note
+ ? `+ ${escapeHtml(note)} `
+ : '';
+ return `${statusCell(result.status)}${noteButton} `;
+}
+
+function normalizeSpecificationSlug(value) {
+ const normalized = String(value || '').trim().toLowerCase().replace(/\/+$/, '');
+ const known = {
+ core: 'core',
+ io: 'io',
+ fnml: 'fnml',
+ cc: 'cc',
+ lv: 'lv',
+ 'rml-core': 'core',
+ 'rml-io': 'io',
+ 'rml-fnml': 'fnml',
+ 'rml-cc': 'cc',
+ 'rml-lv': 'lv',
+ 'http://w3id.org/rml/core': 'core',
+ 'http://w3id.org/rml/io': 'io',
+ 'http://w3id.org/rml/fnml': 'fnml',
+ 'http://w3id.org/rml/cc': 'cc',
+ 'http://w3id.org/rml/lv': 'lv'
+ };
+ return known[normalized] || normalized;
+}
+
+function titleCaseSpecification(specification) {
+ const slug = normalizeSpecificationSlug(specification);
+ if (!slug) return 'Unknown';
+ if (slug === 'star') return 'RML-star';
+ return `RML-${slug.toUpperCase()}`;
+}
+
+function testcaseLink(testcaseId, specificationSlug) {
+ if (!testcaseId || !specificationSlug) return '';
+ return `https://kg-construct.github.io/rml-${specificationSlug}/test-cases/docs/#${encodeURIComponent(testcaseId)}`;
+}
+
+function normalizeTestcase(row, moduleInfo = null) {
+ const specificationSlug = normalizeSpecificationSlug(moduleInfo?.specification_slug || row.specification);
+ return {
+ testcase_id: row.ID || row.testcase_id || '',
+ module: moduleInfo?.module_name || titleCaseSpecification(specificationSlug),
+ title: row.title || '',
+ description: row.description || '',
+ specification: specificationSlug,
+ link: testcaseLink(row.ID || row.testcase_id || '', specificationSlug),
+ source_csv: moduleInfo?.testcases_csv || '',
+ error: String(row.error || '').toLowerCase() === 'true'
+ };
+}
+
+async function fetchModules() {
+ return fetchCsv('data/modules.csv');
+}
+
+async function fetchAllTestcases(modules) {
+ const settled = await Promise.allSettled(
+ modules.map(async moduleInfo => {
+ const rows = await fetchCsv(moduleInfo.testcases_csv);
+ return rows.map(row => normalizeTestcase(row, moduleInfo));
+ })
+ );
+
+ const notices = [];
+ const testcases = [];
+
+ settled.forEach((entry, index) => {
+ const moduleInfo = modules[index];
+ if (entry.status === 'fulfilled') {
+ testcases.push(...entry.value);
+ return;
+ }
+
+ const error = entry.reason;
+ notices.push(`Could not load test cases for ${moduleInfo.module_name}: ${error.message}`);
+ });
+
+ return { testcases, notices };
+}
+
+function resolveResultsPath(processor) {
+ const configured = (processor.results_file || '').trim();
+ const fallback = `results/${processor.processor_id}.csv`;
+ const path = configured || fallback;
+
+ if (
+ path.startsWith('data/') ||
+ path.startsWith('./') ||
+ path.startsWith('../') ||
+ /^https?:\/\//.test(path)
+ ) {
+ return path;
+ }
+
+ return `data/${path}`;
+}
+
+async function fetchResultsForProcessor(processor) {
+ const path = resolveResultsPath(processor);
+ const rows = await fetchCsv(path);
+ return rows.map(row => ({
+ ...row,
+ processor_id: row.processor_id || processor.processor_id
+ }));
+}
+
+async function fetchAllResults(processors) {
+ const settled = await Promise.allSettled(processors.map(fetchResultsForProcessor));
+ const notices = [];
+ const results = [];
+
+ settled.forEach((entry, index) => {
+ const processor = processors[index];
+ if (entry.status === 'fulfilled') {
+ results.push(...entry.value);
+ return;
+ }
+
+ const error = entry.reason;
+ if (error && error.status === 404) {
+ notices.push(`No results file found for ${processor.name || processor.processor_id}: ${resolveResultsPath(processor)}`);
+ return;
+ }
+
+ notices.push(`Could not load results for ${processor.name || processor.processor_id}: ${error.message}`);
+ });
+
+ return { results, notices };
+}
+
+async function loadLiveData() {
+ const [processors, modules] = await Promise.all([
+ fetchCsv('data/processors.csv'),
+ fetchModules()
+ ]);
+ const { testcases, notices: testcaseNotices } = await fetchAllTestcases(modules);
+ const { results, notices: resultNotices } = await fetchAllResults(processors);
+
+ return {
+ processors,
+ modules,
+ testcases,
+ results,
+ notices: [...testcaseNotices, ...resultNotices]
+ };
+}
+
+function buildProcessorTable(processors) {
+ const rows = processors.map(processor => `
+
+ ${escapeHtml(processor.name)}
+ ${plainCell(processor.version)}
+ ${plainCell(processor.release_date)}
+ ${plainCell(processor.contact)}
+ ${linkCell(processor.homepage)}
+ `).join('');
+
+ return `Name Version Test date Contact Web page ${rows}
`;
+}
+
+function summarizeEngineByModule(processorId, processors, testcases, results) {
+ const moduleNames = [...new Set(testcases.map(testcase => testcase.module))];
+ const summary = {
+ passed: 0,
+ failed: 0,
+ inapplicable: 0,
+ reported: 0,
+ modules: {}
+ };
+
+ moduleNames.forEach(moduleName => {
+ summary.modules[moduleName] = { covered: 0, total: 0, passed: 0, failed: 0, inapplicable: 0 };
+ });
+
+ const testcaseMap = Object.fromEntries(testcases.map(testcase => [testcase.testcase_id, testcase]));
+ testcases.forEach(testcase => {
+ summary.modules[testcase.module].total++;
+ });
+
+ results
+ .filter(result => result.processor_id === processorId)
+ .forEach(result => {
+ const testcase = testcaseMap[result.testcase_id];
+ if (!testcase) return;
+
+ const moduleSummary = summary.modules[testcase.module];
+ moduleSummary.covered++;
+ if (VALID_STATUSES.includes(result.status)) {
+ moduleSummary[result.status]++;
+ summary[result.status]++;
+ }
+ summary.reported++;
+ });
+
+ return summary;
+}
+
+function buildSummaryTable(processors, testcases, results) {
+ const moduleNames = [...new Set(testcases.map(testcase => testcase.module))];
+ const header = moduleNames.map(moduleName => ``).join('');
+
+ const rows = processors.map(processor => {
+ const summary = summarizeEngineByModule(processor.processor_id, processors, testcases, results);
+ const moduleCells = moduleNames.map(moduleName => {
+ const item = summary.modules[moduleName];
+ return `${item.passed}/${item.total}F ${item.failed} · I ${item.inapplicable}
`;
+ }).join('');
+
+ return `
+ ${escapeHtml(processor.name)}
+ ${summary.reported}
+ ${summary.passed}
+ ${summary.failed}
+ ${summary.inapplicable}
+ ${moduleCells}
+ `;
+ }).join('');
+
+ if (!rows) {
+ return 'No engines found in data/processors.csv.
';
+ }
+
+ return `Engine Reported Passed Failed Inappl. ${header}${rows}
`;
+}
+
+function buildResultsTable(moduleName, testcases, processors, results) {
+ const moduleTestcases = testcases
+ .filter(testcase => testcase.module === moduleName)
+ .sort((a, b) => a.testcase_id.localeCompare(b.testcase_id));
+ const resultMap = new Map(
+ results.map(result => [`${result.testcase_id}::${result.processor_id}`, result])
+ );
+
+ const headerCells = processors
+ .map(processor => ``)
+ .join('');
+
+ const rows = moduleTestcases.map(testcase => {
+ const testcaseLabel = testcase.link
+ ? `${escapeHtml(testcase.testcase_id)} `
+ : `${escapeHtml(testcase.testcase_id)}`;
+ const processorCells = processors.map(processor => {
+ const result = resultMap.get(`${testcase.testcase_id}::${processor.processor_id}`);
+ const tdClass = result ? 'result-td' : 'result-td result-td-empty';
+ return `${resultCell(result)} `;
+ }).join('');
+
+ return `
+ ${testcaseLabel}
+ ${processorCells}
+ `;
+ }).join('');
+
+ if (!rows) {
+ return 'No matching results for this module.
';
+ }
+
+ return `Test case ${headerCells}${rows}
`;
+}
+
+function renderResultsTables(testcases, processors, results) {
+ const moduleNames = [...new Set(testcases.map(testcase => testcase.module))];
+
+ document.querySelectorAll('[data-module-results]').forEach(container => {
+ const moduleName = container.getAttribute('data-module-results');
+ const section = container.closest('section');
+
+ if (section) {
+ section.hidden = !moduleNames.includes(moduleName);
+ }
+
+ container.innerHTML = moduleNames.includes(moduleName)
+ ? buildResultsTable(moduleName, testcases, processors, results)
+ : '';
+ });
+
+ wireResultNotes();
+}
+
+function wireResultNotes() {
+ if (document.body.dataset.resultNotesBound === 'true') return;
+ document.body.dataset.resultNotesBound = 'true';
+
+ document.addEventListener('click', event => {
+ const trigger = event.target.closest('[data-note-trigger]');
+
+ document.querySelectorAll('.result-td.note-open').forEach(cell => {
+ if (trigger && cell.contains(trigger)) return;
+ cell.classList.remove('note-open');
+ const popover = cell.querySelector('.result-popover');
+ if (popover) popover.hidden = true;
+ });
+
+ if (!trigger) return;
+
+ event.preventDefault();
+ const cell = trigger.closest('.result-td');
+ const popover = cell?.querySelector('.result-popover');
+ if (!cell || !popover) return;
+
+ const isOpen = cell.classList.contains('note-open');
+ cell.classList.toggle('note-open', !isOpen);
+ popover.hidden = isOpen;
+ });
+}
+
+function setNotices() {}
+
+function collectDataNotices(testcases, processors, results) {
+ const testcaseIds = new Set();
+ const processorIds = new Set();
+ const notices = [];
+
+ testcases.forEach(testcase => {
+ if (testcaseIds.has(testcase.testcase_id)) {
+ notices.push(`Duplicate test case id detected: ${testcase.testcase_id}`);
+ }
+ testcaseIds.add(testcase.testcase_id);
+ });
+
+ processors.forEach(processor => {
+ if (processorIds.has(processor.processor_id)) {
+ notices.push(`Duplicate engine id detected: ${processor.processor_id}`);
+ }
+ processorIds.add(processor.processor_id);
+ });
+
+ results.forEach(result => {
+ if (!processorIds.has(result.processor_id)) {
+ notices.push(`Result references an unknown engine id: ${result.processor_id}`);
+ }
+ if (!testcaseIds.has(result.testcase_id)) {
+ notices.push(`Result references an unknown test case id: ${result.testcase_id}`);
+ }
+ if (!VALID_STATUSES.includes(result.status)) {
+ notices.push(`Unexpected status "${result.status}" in result ${result.testcase_id}/${result.processor_id}`);
+ }
+ });
+
+ return [...new Set(notices)];
+}
+
+function setError(message) {
+ const html = `
+
+
CSV data could not be loaded automatically.
+
${escapeHtml(message)}
+
If you open dev.html directly from your disk, the browser may block access to local or remote files.
+
For local testing, run a static server in this folder, for example:
+
python3 -m http.server 8000
+
and then open http://localhost:8000/dev.html.
+
`;
+
+ document.getElementById('processors-table').innerHTML = html;
+ document.getElementById('summary-table').innerHTML = html;
+ document.querySelectorAll('[data-module-results]').forEach(container => {
+ container.innerHTML = html;
+ const section = container.closest('section');
+ if (section) {
+ section.hidden = false;
+ }
+ });
+ setNotices();
+}
+
+async function init() {
+ try {
+ const embedded = readEmbeddedData();
+ const liveData = embedded || await loadLiveData();
+ const { processors, testcases, results } = liveData;
+ const notices = [
+ ...(liveData.notices || []),
+ ...collectDataNotices(testcases, processors, results)
+ ];
+
+ document.getElementById('processors-table').innerHTML = buildProcessorTable(processors);
+ document.getElementById('summary-table').innerHTML = buildSummaryTable(processors, testcases, results);
+ setNotices(notices);
+ renderResultsTables(testcases, processors, results);
+ } catch (error) {
+ console.error(error);
+ setError(error.message || 'Unknown error');
+ }
+}
+
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init, { once: true });
+} else {
+ init();
+}
diff --git a/docs/index.html b/docs/index.html
index 86f929f..d4dbb44 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -368,7 +368,7 @@ RML Implementation Report
-
+