From c13acde2a86cb45f25376c7d47a09bb32d4267dc Mon Sep 17 00:00:00 2001 From: jstet Date: Fri, 25 Sep 2026 17:20:11 +0200 Subject: [PATCH] fix(xlsform2lstsv): default group gets no invented description; --group-name A form without groups gets one group, because LimeSurvey needs every question in a group. It was named "Questions" and also given "Questions" as its description, text nobody wrote. - The auto-created group's description is now empty; its name stays defaults.groupName ("Questions" unless configured). - CLI: xlsform2lstsv --group-name sets it. The "!Questions" title from the issue doesn't come from formtransform: imported into LimeSurvey 6.16, the group is stored as group_name "Questions" (checked live), so the "!" is added by whatever displays it. Snapshots: the G row's text column is now empty in every entity and survey without groups (13 + 2 files); nothing else changes. Live LimeSurvey suite passes. Closes #74 Co-Authored-By: Claude Opus 5.5 (1M context) --- registry/entities/date/tsv.tsv | 2 +- registry/entities/decimal/tsv.tsv | 2 +- registry/entities/integer/tsv.tsv | 2 +- registry/entities/note/tsv.tsv | 2 +- registry/entities/range/tsv.tsv | 2 +- registry/entities/select_multiple/tsv.tsv | 2 +- .../select_multiple_long_list/tsv.tsv | 2 +- .../entities/select_multiple_other/tsv.tsv | 2 +- registry/entities/select_one/tsv.tsv | 2 +- .../entities/select_one_long_list/tsv.tsv | 2 +- registry/entities/select_one_other/tsv.tsv | 2 +- registry/entities/text/tsv.tsv | 2 +- registry/entities/time/tsv.tsv | 2 +- src/cli.ts | 18 +++++++---- src/pipelines/xlsform2lstsv/groupEmitter.ts | 5 ++- tests/fixtures/surveys/basic_survey/tsv.tsv | 2 +- .../validation_relevance_survey/tsv.tsv | 2 +- tests/ts/unit/conversionSettings.test.ts | 31 +++++++++++++++++++ 18 files changed, 62 insertions(+), 22 deletions(-) diff --git a/registry/entities/date/tsv.tsv b/registry/entities/date/tsv.tsv index 5f4ba23..ac1f142 100644 --- a/registry/entities/date/tsv.tsv +++ b/registry/entities/date/tsv.tsv @@ -3,5 +3,5 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Untitled Survey en -G Questions 1 Questions en +G Questions 1 en Q D geburtsdatum 1 Wann wurden Sie geboren? en 1 \ No newline at end of file diff --git a/registry/entities/decimal/tsv.tsv b/registry/entities/decimal/tsv.tsv index e34a62f..2d60e7d 100644 --- a/registry/entities/decimal/tsv.tsv +++ b/registry/entities/decimal/tsv.tsv @@ -3,5 +3,5 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Untitled Survey en -G Questions 1 Questions en +G Questions 1 en Q N schlafdauer 1 Wie viele Stunden schlafen Sie durchschnittlich pro Nacht? en 1 \ No newline at end of file diff --git a/registry/entities/integer/tsv.tsv b/registry/entities/integer/tsv.tsv index 130c86f..7e0db34 100644 --- a/registry/entities/integer/tsv.tsv +++ b/registry/entities/integer/tsv.tsv @@ -3,5 +3,5 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Untitled Survey en -G Questions 1 Questions en +G Questions 1 en Q N alter 1 Wie alt sind Sie? en 1 \ No newline at end of file diff --git a/registry/entities/note/tsv.tsv b/registry/entities/note/tsv.tsv index 67edce8..67b3afc 100644 --- a/registry/entities/note/tsv.tsv +++ b/registry/entities/note/tsv.tsv @@ -3,5 +3,5 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Untitled Survey en -G Questions 1 Questions en +G Questions 1 en Q X hinweis 1 Im folgenden Abschnitt geht es um Ihre Lebenssituation. en \ No newline at end of file diff --git a/registry/entities/range/tsv.tsv b/registry/entities/range/tsv.tsv index 9fa0535..a20f01c 100644 --- a/registry/entities/range/tsv.tsv +++ b/registry/entities/range/tsv.tsv @@ -3,5 +3,5 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Untitled Survey en -G Questions 1 Questions en +G Questions 1 en Q N zufriedenheit 1 Wie zufrieden sind Sie insgesamt? (0 = gar nicht, 10 = voll) en 1 0 10 1 \ No newline at end of file diff --git a/registry/entities/select_multiple/tsv.tsv b/registry/entities/select_multiple/tsv.tsv index 7d7562e..4ed2d03 100644 --- a/registry/entities/select_multiple/tsv.tsv +++ b/registry/entities/select_multiple/tsv.tsv @@ -3,7 +3,7 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Untitled Survey en -G Questions 1 Questions en +G Questions 1 en Q M wochenende 1 An welchen Tagen des Wochenendes sind Sie erreichbar? en 1 SQ sa Samstag en SQ so Sonntag en \ No newline at end of file diff --git a/registry/entities/select_multiple_long_list/tsv.tsv b/registry/entities/select_multiple_long_list/tsv.tsv index a387e1f..c0ecc77 100644 --- a/registry/entities/select_multiple_long_list/tsv.tsv +++ b/registry/entities/select_multiple_long_list/tsv.tsv @@ -3,7 +3,7 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Untitled Survey en -G Questions 1 Questions en +G Questions 1 en Q M besuchtelaender 1 Welche dieser Länder haben Sie bereits besucht? Mehrere Antworten möglich. en cdlvocab-iso_3166_1 1 SQ AF Afghanistan en SQ AX Åland Islands en diff --git a/registry/entities/select_multiple_other/tsv.tsv b/registry/entities/select_multiple_other/tsv.tsv index e580d84..f469a2f 100644 --- a/registry/entities/select_multiple_other/tsv.tsv +++ b/registry/entities/select_multiple_other/tsv.tsv @@ -3,7 +3,7 @@ S language 1 de de S format 1 A de S shownoanswer 1 N de SL surveyls_title 1 Untitled Survey de -G Questions 1 Questions de +G Questions 1 de Q M geraetebesitz 1 Welche dieser Geräte besitzen Sie? de Y 1 SQ smart Smartphone de SQ lapt Laptop de diff --git a/registry/entities/select_one/tsv.tsv b/registry/entities/select_one/tsv.tsv index 953a176..586dbdf 100644 --- a/registry/entities/select_one/tsv.tsv +++ b/registry/entities/select_one/tsv.tsv @@ -3,7 +3,7 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Untitled Survey en -G Questions 1 Questions en +G Questions 1 en Q L bildungsgrad 1 Was ist Ihr höchster Bildungsabschluss? en 1 A 1 Kein Abschluss en A 2 Haupt- oder Realschulabschluss en diff --git a/registry/entities/select_one_long_list/tsv.tsv b/registry/entities/select_one_long_list/tsv.tsv index cc9d31a..7ad1c9c 100644 --- a/registry/entities/select_one_long_list/tsv.tsv +++ b/registry/entities/select_one_long_list/tsv.tsv @@ -3,7 +3,7 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Untitled Survey en -G Questions 1 Questions en +G Questions 1 en Q L geburtsland 1 In welchem Land wurden Sie geboren? en cdlvocab-iso_3166_1 1 A AF Afghanistan en A AX Åland Islands en diff --git a/registry/entities/select_one_other/tsv.tsv b/registry/entities/select_one_other/tsv.tsv index c2d5175..8cde7ed 100644 --- a/registry/entities/select_one_other/tsv.tsv +++ b/registry/entities/select_one_other/tsv.tsv @@ -3,7 +3,7 @@ S language 1 de de S format 1 A de S shownoanswer 1 N de SL surveyls_title 1 Untitled Survey de -G Questions 1 Questions de +G Questions 1 de Q L aufmerksam 1 Wie sind Sie auf unser Angebot aufmerksam geworden? de Y 1 A such Suchmaschine de A empf Persönliche Empfehlung de diff --git a/registry/entities/text/tsv.tsv b/registry/entities/text/tsv.tsv index d12c627..404166e 100644 --- a/registry/entities/text/tsv.tsv +++ b/registry/entities/text/tsv.tsv @@ -3,5 +3,5 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Untitled Survey en -G Questions 1 Questions en +G Questions 1 en Q S anmerkungen 1 Haben Sie weitere Anmerkungen? en 1 \ No newline at end of file diff --git a/registry/entities/time/tsv.tsv b/registry/entities/time/tsv.tsv index 02b09e9..df9e220 100644 --- a/registry/entities/time/tsv.tsv +++ b/registry/entities/time/tsv.tsv @@ -3,5 +3,5 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Untitled Survey en -G Questions 1 Questions en +G Questions 1 en Q D aufstehzeit 1 Um wie viel Uhr stehen Sie werktags normalerweise auf? en 1 HH:MM \ No newline at end of file diff --git a/src/cli.ts b/src/cli.ts index d868914..3aac4af 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,7 +1,8 @@ #!/usr/bin/env node import { basename, dirname, join } from 'node:path'; -import { ConversionConfig } from './config/ConfigManager.js'; +import { defaultConfig } from './config/types.js'; +import type { LstsvConfig } from './config/types.js'; import { resolveFileChoices } from './fileChoices.js'; import { lstsvToDataCsv, lstsvToDdiXml } from './pipelines/lstsv2ddi/index.js'; import { lstsvToXlsform } from './pipelines/lstsv2xlsform/index.js'; @@ -57,6 +58,8 @@ Options: -o, --output Write TSV to (default: stdout) --title Override the survey title --language Default language code (default: en, or settings default_language) + --group-name Name of the group created when the form has none + (LimeSurvey needs one; default: "Questions") --no-markdown Do not render markdown labels/hints to HTML --no-welcome-note Do not promote a "welcome" note to surveyls_welcometext --no-end-note Do not promote an "end" note to surveyls_endtext @@ -223,6 +226,7 @@ async function cmdXlsform2lstsv(argv: string[]): Promise { output: { type: 'string', short: 'o' }, title: { type: 'string' }, language: { type: 'string' }, + 'group-name': { type: 'string' }, 'no-markdown': { type: 'boolean', default: false }, 'no-welcome-note': { type: 'boolean', default: false }, 'no-end-note': { type: 'boolean', default: false }, @@ -236,18 +240,20 @@ async function cmdXlsform2lstsv(argv: string[]): Promise { const bytes = readInput(positionals, xlsform2lstsvHelp); - const config: Partial = { + const config: Partial = { convertMarkdown: !values['no-markdown'], convertWelcomeNote: !values['no-welcome-note'], convertEndNote: !values['no-end-note'], convertOtherPattern: !values['no-other-pattern'], hideNoAnswer: !values['show-no-answer'], }; - if (values.language || values.title) { + if (values.language || values.title || values['group-name']) { config.defaults = { - language: (values.language as string) ?? 'en', - groupName: 'Questions', - surveyTitle: (values.title as string) ?? 'Untitled Survey', + language: (values.language as string) ?? defaultConfig.defaults.language, + groupName: + (values['group-name'] as string) ?? defaultConfig.defaults.groupName, + surveyTitle: + (values.title as string) ?? defaultConfig.defaults.surveyTitle, description: '', }; } diff --git a/src/pipelines/xlsform2lstsv/groupEmitter.ts b/src/pipelines/xlsform2lstsv/groupEmitter.ts index 1935f69..78f2db0 100644 --- a/src/pipelines/xlsform2lstsv/groupEmitter.ts +++ b/src/pipelines/xlsform2lstsv/groupEmitter.ts @@ -57,8 +57,11 @@ export class GroupEmitter { // ('en'). A group row whose language differs from the survey base language // fails LimeSurvey's activation consistency check (e.g. a German-base survey // with an English-only "Questions" group). + // The group only exists because LimeSurvey needs one: give it the + // configured name (defaults.groupName) and no description, rather than + // repeating the name as a description nobody wrote. this.rowEmitter.emitForEachLanguage( - () => ({ class: 'G', name: groupName, text: groupName }), + () => ({ class: 'G', name: groupName, text: '' }), 'direct', ); diff --git a/tests/fixtures/surveys/basic_survey/tsv.tsv b/tests/fixtures/surveys/basic_survey/tsv.tsv index 01a8a6f..46707d4 100644 --- a/tests/fixtures/surveys/basic_survey/tsv.tsv +++ b/tests/fixtures/surveys/basic_survey/tsv.tsv @@ -3,7 +3,7 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Basic Survey en -G Questions 1 Questions en +G Questions 1 en Q S respondentname 1 What is your name? en Y 1 Q N age 1 What is your age? en Y 1 Q L consent 1 Do you consent to participate? en Y 1 diff --git a/tests/fixtures/surveys/validation_relevance_survey/tsv.tsv b/tests/fixtures/surveys/validation_relevance_survey/tsv.tsv index b03990a..7385f56 100644 --- a/tests/fixtures/surveys/validation_relevance_survey/tsv.tsv +++ b/tests/fixtures/surveys/validation_relevance_survey/tsv.tsv @@ -4,7 +4,7 @@ S format 1 A en S shownoanswer 1 N en SL surveyls_title 1 Validation and Relevance Test en SL surveyls_welcometext 1 Validation and Relevance Test Survey en -G Questions 1 Questions en +G Questions 1 en Q S username 1 Username en self != '' 1 Q N age 1 Age en self >= 18 and self <= 120 1 Q N price 1 Price en self > 0 and self < 1000 1 diff --git a/tests/ts/unit/conversionSettings.test.ts b/tests/ts/unit/conversionSettings.test.ts index 6178333..70029c5 100644 --- a/tests/ts/unit/conversionSettings.test.ts +++ b/tests/ts/unit/conversionSettings.test.ts @@ -344,3 +344,34 @@ describe('settings combinations', () => { expect(defaultRows).toEqual(explicitRows); }); }); + +describe('default group (#74)', () => { + const groupRow = async (config: Record = {}) => + ( + await convertAndParse( + [{ type: 'text', name: 'q', label: 'Q' }], + [], + undefined, + config, + ) + ).find((r) => r.class === 'G'); + + test('has the configured name and no invented description', async () => { + const g = await groupRow({ + defaults: { + language: 'en', + groupName: 'Fragen', + surveyTitle: 'T', + description: '', + }, + }); + expect(g?.name).toBe('Fragen'); + expect(g?.text ?? '').toBe(''); + }); + + test('defaults to "Questions", still without a description', async () => { + const g = await groupRow(); + expect(g?.name).toBe('Questions'); + expect(g?.text ?? '').toBe(''); + }); +});