From aec7dc457c85290ff6ebc572adefcce39a0d31b9 Mon Sep 17 00:00:00 2001 From: felix Date: Wed, 16 Sep 2026 17:15:16 +0200 Subject: [PATCH] fix issue with duplicate values --- src/runner/assemble.js | 17 ++++++++++++----- src/runner/check.js | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/runner/assemble.js b/src/runner/assemble.js index 228d1a0..bc9c3d9 100644 --- a/src/runner/assemble.js +++ b/src/runner/assemble.js @@ -1,7 +1,9 @@ // Values to rows. Subjects in stable order, dedup on term, collect per cell. // rep:self columns take the subject URI; a column with no values is an -// empty cell, never a dropped row. rep:row expands one subject into several -// lines; at most one column per spec may do that (checked in check.js). +// empty cell, never a dropped row. A column without rep:collect joins its +// values only when there is one; with several, each value gets its own row +// (the other columns repeat theirs). rep:row forces that expansion; at most +// one column per spec may do that explicitly (checked in check.js). const SH = 'http://www.w3.org/ns/shacl#'; const REP = 'http://mu.semte.ch/vocabularies/reporting/'; @@ -26,7 +28,8 @@ export function assemble(subjects, values, spec) { }); const lines = Math.max(1, ...cells.map(c => c.length)); for (let r = 0; r < lines; r++) { - // every cell except the expanding one keeps its value on each line + // the expanding cell cycles its values, every other cell keeps its + // value on each line rows.push(cells.map(c => c[Math.min(r, c.length - 1)])); } } @@ -36,9 +39,13 @@ export function assemble(subjects, values, spec) { function collect(terms, col) { switch (col.collect) { case SH + 'groupConcat': - case null: - case undefined: return [terms.map(t => t.value).join(col.separator ?? ',')]; + case undefined: + case null: + // no rep:collect: one value is a normal cell, several values each + // get their own row instead of a joined string + if (terms.length <= 1) return [terms.map(t => t.value).join(',')]; + return terms.map(t => t.value); case SH + 'min': return [extreme(terms, col, -1)]; case SH + 'max': diff --git a/src/runner/check.js b/src/runner/check.js index 6bfe7e9..efb994c 100644 --- a/src/runner/check.js +++ b/src/runner/check.js @@ -126,7 +126,7 @@ function collectMinMaxTyped(spec, profile) { : NUMERIC_DATATYPES.has(f.datatype) ? 'number' : null; if (!kind) { - return `"${f.name || last.predicate}" is text, so ${name} does not apply. Use rep:row, or leave it out to join the values.`; + return `"${f.name || last.predicate}" is text, so ${name} does not apply. Use rep:row (same as leaving rep:collect out: every value gets its own row).`; } } return null; @@ -150,7 +150,7 @@ function columnPure(spec) { return `a column carries ${which}. Constraints belong in sh:property.`; } if (col.collect && ![SH + 'groupConcat', SH + 'min', SH + 'max', REP + 'row'].includes(col.collect)) { - return `column "${col.label}" has an unknown rep:collect <${col.collect}>. Use sh:groupConcat (default), rep:row, sh:min or sh:max.`; + return `column "${col.label}" has an unknown rep:collect <${col.collect}>. Use sh:groupConcat (joins the values), rep:row or nothing (each value gets its own row), sh:min or sh:max.`; } } return null;