From 989cc33a8363480ac62ffb2348d2b443ffed8341 Mon Sep 17 00:00:00 2001 From: jstet Date: Wed, 4 Nov 2026 20:55:33 +0100 Subject: [PATCH] feat(lstsv)!: write allowprev=Y and showxquestions=N as survey settings by default (#240) Without limesurvey_allowprev / limesurvey_showxquestions columns, the TSV now carries S allowprev=Y and S showxquestions=N next to shownoanswer=N. A column overrides them; --no-setting-defaults (surveySettingDefaults: false) turns them off. The way back skips all three defaults, so forms round-trip as authored. DDI is unaffected. Snapshots re-blessed: only the two S rows added. Co-Authored-By: Claude Opus 5.5 --- README.md | 17 ++++-- registry/conventions/surveySettings.jsonld | 6 +- registry/entities/date/tsv.tsv | 2 + registry/entities/decimal/tsv.tsv | 2 + registry/entities/grid/tsv.tsv | 2 + registry/entities/hidden/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 | 5 ++ src/config/types.ts | 8 +++ src/conventions/surveySettings.ts | 4 +- src/generated/conventions.json | 6 +- src/generated/conventions.ts | 6 +- src/pipelines/lstsv2xlsform/README.md | 2 +- .../xlsform2lstsv/surveySettingsEmitter.ts | 26 ++++---- .../fixtures/surveys/all_types_survey/tsv.tsv | 2 + .../surveys/appearances_survey/tsv.tsv | 2 + tests/fixtures/surveys/basic_survey/tsv.tsv | 2 + .../fixtures/surveys/bilingual_survey/tsv.tsv | 2 + .../surveys/choice_filter_survey/tsv.tsv | 2 + tests/fixtures/surveys/complex_survey/tsv.tsv | 2 + .../surveys/complex_xpath_survey/tsv.tsv | 2 + tests/fixtures/surveys/grid_survey/tsv.tsv | 2 + tests/fixtures/surveys/hints_survey/tsv.tsv | 2 + .../surveys/multilingual_survey/tsv.tsv | 2 + .../fixtures/surveys/multipage_survey/tsv.tsv | 2 + .../surveys/numeric_table_survey/tsv.tsv | 2 + .../surveys/selection_count_survey/tsv.tsv | 2 + .../fixtures/surveys/settings_survey/tsv.tsv | 1 + tests/fixtures/surveys/testA/tsv.tsv | 2 + tests/fixtures/surveys/testB/tsv.tsv | 2 + .../validation_relevance_survey/tsv.tsv | 2 + tests/ts/integration/cli.test.ts | 11 ++++ tests/ts/unit/limesurveySettings.test.ts | 60 ++++++++++++++++--- 43 files changed, 183 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 7f3bc14..8c15ffd 100644 --- a/README.md +++ b/README.md @@ -251,8 +251,8 @@ gives these `S` rows: | S | format | A | | S | shownoanswer | N | | S | allowprev | Y | -| S | allowsave | Y | | S | showxquestions | N | +| S | allowsave | Y | | S | showqnumcode | X | Only the settings in `convention:surveySettings` (`limesurveySettings`) pass @@ -262,11 +262,20 @@ setting or an invalid value is dropped with a `setting-invalid` warning. `format`, `language` and `additional_languages` come from the form (`style: pages`, `default_language`, the `label::` columns), so their columns are dropped with that warning too. `limesurvey_shownoanswer` overrides -formtransform's default `shownoanswer=N` (`--show-no-answer`). LimeSurvey's own -defaults are not changed: without the columns the TSV is as before. +formtransform's default `shownoanswer=N` (`--show-no-answer`). + +**Defaults (behaviour change, #240).** Without the columns, formtransform +writes `allowprev=Y` (a Previous button) and `showxquestions=N` (no "This +survey contains 74 questions" line, which counts hidden and filtered questions +and overstates the length) next to `shownoanswer=N`, because LimeSurvey's own +defaults (`allowprev=N`, `showxquestions=Y`) are poor for most XLSForm surveys. +A form that sets `limesurvey_allowprev` or `limesurvey_showxquestions` wins, +including `N` / `Y`. `--no-setting-defaults` (option `surveySettingDefaults: +false`) leaves both out, so LimeSurvey's defaults apply. The reverse path +skips the three defaults, so a form round-trips as authored. The way back reads the `S` rows into the same columns (except -`shownoanswer=N`, formtransform's default), and the DDI keeps each as a +formtransform's three defaults), and the DDI keeps each as a `cdl:setting` note like any other setting, so XLSForm → DDI → XLSForm keeps them. ## Errors and warnings diff --git a/registry/conventions/surveySettings.jsonld b/registry/conventions/surveySettings.jsonld index 1c53eb5..51ede87 100644 --- a/registry/conventions/surveySettings.jsonld +++ b/registry/conventions/surveySettings.jsonld @@ -78,7 +78,7 @@ } ], "limesurveySettings": { - "description": "LimeSurvey survey settings (the surveys table) set from optional settings-sheet columns named , e.g. limesurvey_allowprev = Y. Each becomes an `S ` row of the TSV, which LimeSurvey's TSV import writes into the survey (formtransform#228). ODK and Kobo ignore the columns. Only the settings listed here are passed through: an unknown setting or a value outside its list is dropped with a `setting-invalid` warning. Y/N settings also take yes/no and true/false, in any case. The reverse path reads every listed S row back into its column, except a value formtransform writes by default. In DDI each is a cdl:setting note like any other setting (convention:ddiFields).", + "description": "LimeSurvey survey settings (the surveys table) set from optional settings-sheet columns named , e.g. limesurvey_allowprev = Y. Each becomes an `S ` row of the TSV, which LimeSurvey's TSV import writes into the survey (formtransform#228). ODK and Kobo ignore the columns. Only the settings listed here are passed through: an unknown setting or a value outside its list is dropped with a `setting-invalid` warning. Y/N settings also take yes/no and true/false, in any case. The reverse path reads every listed S row back into its column, except a value formtransform writes by default (the `defaults`: shownoanswer=N, allowprev=Y, showxquestions=N; a form's column overrides them, and the allowprev and showxquestions defaults can be turned off with the surveySettingDefaults option). In DDI each is a cdl:setting note like any other setting (convention:ddiFields).", "prefix": "limesurvey_", "lsTsvRow": "S ", "ddiPath": "stdyDscr/notes[@type='cdl:setting'][@subject='limesurvey_']", @@ -96,7 +96,9 @@ "format": "`style: pages` gives G (a page per group), anything else A (all in one)" }, "defaults": { - "shownoanswer": "N" + "shownoanswer": "N", + "allowprev": "Y", + "showxquestions": "N" }, "settings": { "allowprev": { diff --git a/registry/entities/date/tsv.tsv b/registry/entities/date/tsv.tsv index ac1f142..31e5cb5 100644 --- a/registry/entities/date/tsv.tsv +++ b/registry/entities/date/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey 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 2d60e7d..0b2ee93 100644 --- a/registry/entities/decimal/tsv.tsv +++ b/registry/entities/decimal/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey 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/grid/tsv.tsv b/registry/entities/grid/tsv.tsv index c88a348..2300f74 100644 --- a/registry/entities/grid/tsv.tsv +++ b/registry/entities/grid/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey en G 1 Vertrauen in Institutionen 1 en Q F institutionen 1 en 1 diff --git a/registry/entities/hidden/tsv.tsv b/registry/entities/hidden/tsv.tsv index 0e3362b..cae2894 100644 --- a/registry/entities/hidden/tsv.tsv +++ b/registry/entities/hidden/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey en G Questions 1 en Q S partner 1 Partnerkennung (über den Link vorbelegt) en 1 1 diff --git a/registry/entities/integer/tsv.tsv b/registry/entities/integer/tsv.tsv index 8ebb44c..89478c0 100644 --- a/registry/entities/integer/tsv.tsv +++ b/registry/entities/integer/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey en G Questions 1 en Q N alter 1 Wie alt sind Sie? en 1 1 \ No newline at end of file diff --git a/registry/entities/note/tsv.tsv b/registry/entities/note/tsv.tsv index 67b3afc..efebf15 100644 --- a/registry/entities/note/tsv.tsv +++ b/registry/entities/note/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey 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 a20f01c..817fa5e 100644 --- a/registry/entities/range/tsv.tsv +++ b/registry/entities/range/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey 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 4ed2d03..b33e6d6 100644 --- a/registry/entities/select_multiple/tsv.tsv +++ b/registry/entities/select_multiple/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey en G Questions 1 en Q M wochenende 1 An welchen Tagen des Wochenendes sind Sie erreichbar? en 1 diff --git a/registry/entities/select_multiple_long_list/tsv.tsv b/registry/entities/select_multiple_long_list/tsv.tsv index c0ecc77..306d0df 100644 --- a/registry/entities/select_multiple_long_list/tsv.tsv +++ b/registry/entities/select_multiple_long_list/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey 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 diff --git a/registry/entities/select_multiple_other/tsv.tsv b/registry/entities/select_multiple_other/tsv.tsv index 456eb8a..487894d 100644 --- a/registry/entities/select_multiple_other/tsv.tsv +++ b/registry/entities/select_multiple_other/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 de de S format 1 A de S shownoanswer 1 N de +S allowprev 1 Y de +S showxquestions 1 N de SL surveyls_title 1 Untitled Survey de G Questions 1 de Q M geraetebesitz 1 Welche dieser Geräte besitzen Sie? de Y 1 Sonstiges (bitte angeben) diff --git a/registry/entities/select_one/tsv.tsv b/registry/entities/select_one/tsv.tsv index 586dbdf..46e2824 100644 --- a/registry/entities/select_one/tsv.tsv +++ b/registry/entities/select_one/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey en G Questions 1 en Q L bildungsgrad 1 Was ist Ihr höchster Bildungsabschluss? en 1 diff --git a/registry/entities/select_one_long_list/tsv.tsv b/registry/entities/select_one_long_list/tsv.tsv index 7ad1c9c..931c38b 100644 --- a/registry/entities/select_one_long_list/tsv.tsv +++ b/registry/entities/select_one_long_list/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey en G Questions 1 en Q L geburtsland 1 In welchem Land wurden Sie geboren? en cdlvocab-iso_3166_1 1 diff --git a/registry/entities/select_one_other/tsv.tsv b/registry/entities/select_one_other/tsv.tsv index 23e7485..03b7ab2 100644 --- a/registry/entities/select_one_other/tsv.tsv +++ b/registry/entities/select_one_other/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 de de S format 1 A de S shownoanswer 1 N de +S allowprev 1 Y de +S showxquestions 1 N de SL surveyls_title 1 Untitled Survey de G Questions 1 de Q L aufmerksam 1 Wie sind Sie auf unser Angebot aufmerksam geworden? de Y 1 Sonstiges (bitte angeben) diff --git a/registry/entities/text/tsv.tsv b/registry/entities/text/tsv.tsv index 404166e..775588a 100644 --- a/registry/entities/text/tsv.tsv +++ b/registry/entities/text/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey 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 df9e220..5220076 100644 --- a/registry/entities/time/tsv.tsv +++ b/registry/entities/time/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey 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 3883bdf..2781fb3 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -71,6 +71,9 @@ Options: --no-end-note Do not promote an "end" note to surveyls_endtext --no-other-pattern Do not collapse the _other question pattern into a native "other" --show-no-answer Keep the "no answer" option on non-mandatory questions + --no-setting-defaults + Do not write the survey settings allowprev=Y and + showxquestions=N (LimeSurvey's own defaults apply) --sanitize Accept names and answer codes outside LimeSurvey's limits (k23_ars, -98): they are sanitized (k23ars, n98) and each change is recorded in the mapping @@ -261,6 +264,7 @@ async function cmdXlsform2lstsv(argv: string[]): Promise { 'no-end-note': { type: 'boolean', default: false }, 'no-other-pattern': { type: 'boolean', default: false }, 'show-no-answer': { type: 'boolean', default: false }, + 'no-setting-defaults': { type: 'boolean', default: false }, sanitize: { type: 'boolean', default: false }, mapping: { type: 'string' }, 'skip-validation': { type: 'boolean', default: false }, @@ -277,6 +281,7 @@ async function cmdXlsform2lstsv(argv: string[]): Promise { convertEndNote: !values['no-end-note'], convertOtherPattern: !values['no-other-pattern'], hideNoAnswer: !values['show-no-answer'], + surveySettingDefaults: !values['no-setting-defaults'], }; if (values.language || values.title || values['group-name']) { config.defaults = { diff --git a/src/config/types.ts b/src/config/types.ts index b4103a6..e3f7e99 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -49,6 +49,13 @@ export interface LstsvConfig { */ hideNoAnswer?: boolean; + /** + * Write formtransform's LimeSurvey survey-setting defaults `allowprev=Y` + * (Previous button) and `showxquestions=N` (no question count) unless the + * form sets them with `limesurvey_*` columns (default: true, #240) + */ + surveySettingDefaults?: boolean; + /** * Suppress LimeSurvey's automatic per-question tips — e.g. "Only numbers may * be entered in this field." (numeric) or "Select all that apply" (multiple @@ -87,6 +94,7 @@ export const defaultConfig: LstsvConfig = { convertOtherPattern: true, convertMarkdown: true, hideNoAnswer: true, + surveySettingDefaults: true, hideQuestionTips: true, defaults: { diff --git a/src/conventions/surveySettings.ts b/src/conventions/surveySettings.ts index f8ed450..5061505 100644 --- a/src/conventions/surveySettings.ts +++ b/src/conventions/surveySettings.ts @@ -20,7 +20,7 @@ export const LIMESURVEY_SETTING_PREFIX: string = RULE.prefix; const SETTINGS: Readonly> = RULE.settings; const DERIVED: Readonly> = RULE.derived; -/** What formtransform writes when the form sets nothing (`shownoanswer=N`). */ +/** What formtransform writes when the form sets nothing (`shownoanswer=N`, `allowprev=Y`, `showxquestions=N`). */ export const LIMESURVEY_SETTING_DEFAULTS: Readonly> = RULE.defaults; const TRUE_VALUES = new Set(RULE.trueValues); @@ -90,7 +90,7 @@ export function limesurveySettingsOf( /** * The way back: a TSV's `S` rows as `limesurvey_` columns. Only the * supported settings with a valid value, and not a value formtransform writes - * by default (`shownoanswer=N`), so a converted form comes back as authored. + * by default (`LIMESURVEY_SETTING_DEFAULTS`), so a converted form comes back as authored. */ export function limesurveySettingColumns( sRows: readonly { name: string; text: string }[], diff --git a/src/generated/conventions.json b/src/generated/conventions.json index 6c97b46..275f8f2 100644 --- a/src/generated/conventions.json +++ b/src/generated/conventions.json @@ -1119,7 +1119,7 @@ } ], "limesurveySettings": { - "description": "LimeSurvey survey settings (the surveys table) set from optional settings-sheet columns named , e.g. limesurvey_allowprev = Y. Each becomes an `S ` row of the TSV, which LimeSurvey's TSV import writes into the survey (formtransform#228). ODK and Kobo ignore the columns. Only the settings listed here are passed through: an unknown setting or a value outside its list is dropped with a `setting-invalid` warning. Y/N settings also take yes/no and true/false, in any case. The reverse path reads every listed S row back into its column, except a value formtransform writes by default. In DDI each is a cdl:setting note like any other setting (convention:ddiFields).", + "description": "LimeSurvey survey settings (the surveys table) set from optional settings-sheet columns named , e.g. limesurvey_allowprev = Y. Each becomes an `S ` row of the TSV, which LimeSurvey's TSV import writes into the survey (formtransform#228). ODK and Kobo ignore the columns. Only the settings listed here are passed through: an unknown setting or a value outside its list is dropped with a `setting-invalid` warning. Y/N settings also take yes/no and true/false, in any case. The reverse path reads every listed S row back into its column, except a value formtransform writes by default (the `defaults`: shownoanswer=N, allowprev=Y, showxquestions=N; a form's column overrides them, and the allowprev and showxquestions defaults can be turned off with the surveySettingDefaults option). In DDI each is a cdl:setting note like any other setting (convention:ddiFields).", "prefix": "limesurvey_", "lsTsvRow": "S ", "ddiPath": "stdyDscr/notes[@type='cdl:setting'][@subject='limesurvey_']", @@ -1137,7 +1137,9 @@ "format": "`style: pages` gives G (a page per group), anything else A (all in one)" }, "defaults": { - "shownoanswer": "N" + "shownoanswer": "N", + "allowprev": "Y", + "showxquestions": "N" }, "settings": { "allowprev": { diff --git a/src/generated/conventions.ts b/src/generated/conventions.ts index 9591c2a..a1cafc2 100644 --- a/src/generated/conventions.ts +++ b/src/generated/conventions.ts @@ -1128,7 +1128,7 @@ const conventions = { } ], "limesurveySettings": { - "description": "LimeSurvey survey settings (the surveys table) set from optional settings-sheet columns named , e.g. limesurvey_allowprev = Y. Each becomes an `S ` row of the TSV, which LimeSurvey's TSV import writes into the survey (formtransform#228). ODK and Kobo ignore the columns. Only the settings listed here are passed through: an unknown setting or a value outside its list is dropped with a `setting-invalid` warning. Y/N settings also take yes/no and true/false, in any case. The reverse path reads every listed S row back into its column, except a value formtransform writes by default. In DDI each is a cdl:setting note like any other setting (convention:ddiFields).", + "description": "LimeSurvey survey settings (the surveys table) set from optional settings-sheet columns named , e.g. limesurvey_allowprev = Y. Each becomes an `S ` row of the TSV, which LimeSurvey's TSV import writes into the survey (formtransform#228). ODK and Kobo ignore the columns. Only the settings listed here are passed through: an unknown setting or a value outside its list is dropped with a `setting-invalid` warning. Y/N settings also take yes/no and true/false, in any case. The reverse path reads every listed S row back into its column, except a value formtransform writes by default (the `defaults`: shownoanswer=N, allowprev=Y, showxquestions=N; a form's column overrides them, and the allowprev and showxquestions defaults can be turned off with the surveySettingDefaults option). In DDI each is a cdl:setting note like any other setting (convention:ddiFields).", "prefix": "limesurvey_", "lsTsvRow": "S ", "ddiPath": "stdyDscr/notes[@type='cdl:setting'][@subject='limesurvey_']", @@ -1146,7 +1146,9 @@ const conventions = { "format": "`style: pages` gives G (a page per group), anything else A (all in one)" }, "defaults": { - "shownoanswer": "N" + "shownoanswer": "N", + "allowprev": "Y", + "showxquestions": "N" }, "settings": { "allowprev": { diff --git a/src/pipelines/lstsv2xlsform/README.md b/src/pipelines/lstsv2xlsform/README.md index 35f7b0f..f895c8a 100644 --- a/src/pipelines/lstsv2xlsform/README.md +++ b/src/pipelines/lstsv2xlsform/README.md @@ -46,7 +46,7 @@ CLI: `formtransform lstsv2xlsform `. | F + SQ + A | `begin_group appearance=table-list` + child `select_one`s | inverts the grid → F encoding | | SL welcome/end text | `note` named `welcome` / `end` | inverts note promotion | | per-language rows | `label::` / `hint::` | direct | -| S rows | settings (`default_language`, `form_title`, `style`, and `limesurvey_` for each supported survey setting) | convention:surveySettings `limesurveySettings`; `shownoanswer=N`, formtransform's default, isn't read back | +| S rows | settings (`default_language`, `form_title`, `style`, and `limesurvey_` for each supported survey setting) | convention:surveySettings `limesurveySettings`; `shownoanswer=N`, `allowprev=Y` and `showxquestions=N`, formtransform's defaults, aren't read back | | `relevance`, `em_validation_q` | `relevant`, `constraint` | reverse-transpiled — see below | | `answer_order=random` (L, `!`), `subquestion_order=random` (M), legacy `random_order=1` | `parameters: randomize=true` | convention:randomOrder; a `keep_codes_order` code that isn't in `exclude_all_others` comes back as `fixed=yes` on its choice; exclusive answers come back from `exclude_all_others` | | `array_filter = q` | `choice_filter: selected(${q}, name)` | convention:choiceFilter. A filter the forward path recoded (`selected(${q}, )`) comes back on the codes LimeSurvey stores, not the list's own | diff --git a/src/pipelines/xlsform2lstsv/surveySettingsEmitter.ts b/src/pipelines/xlsform2lstsv/surveySettingsEmitter.ts index 259daa5..781131e 100644 --- a/src/pipelines/xlsform2lstsv/surveySettingsEmitter.ts +++ b/src/pipelines/xlsform2lstsv/surveySettingsEmitter.ts @@ -46,7 +46,7 @@ export class SurveySettingsEmitter { /** * Emit the S rows (language, additional_languages, format, shownoanswer, - * then the form's `limesurvey_` columns) and + * allowprev, showxquestions, then the form's `limesurvey_` columns) and * the SL rows (title + optional welcome/end text) for the survey. */ emit(settings: SettingsRow): void { @@ -89,18 +89,24 @@ export class SurveySettingsEmitter { }), ); - // The form's limesurvey_ columns (#228). shownoanswer=N is - // formtransform's default unless hideNoAnswer is false; a column overrides it. + // The form's limesurvey_ columns (#228) over formtransform's + // defaults (shownoanswer=N unless hideNoAnswer is false; allowprev=Y and + // showxquestions=N unless surveySettingDefaults is false, #240). + const config = this.configManager.getConfig(); const lsSettings = limesurveySettingsOf(settings, this.warn); - if ( - this.configManager.getConfig().hideNoAnswer !== false && - !lsSettings.has('shownoanswer') - ) { - lsSettings.set('shownoanswer', LIMESURVEY_SETTING_DEFAULTS.shownoanswer); + const defaulted = Object.keys(LIMESURVEY_SETTING_DEFAULTS).filter((name) => + name === 'shownoanswer' + ? config.hideNoAnswer !== false + : config.surveySettingDefaults !== false, + ); + for (const name of defaulted) { + if (!lsSettings.has(name)) { + lsSettings.set(name, LIMESURVEY_SETTING_DEFAULTS[name]); + } } const ordered = [ - ...(lsSettings.has('shownoanswer') ? ['shownoanswer'] : []), - ...[...lsSettings.keys()].filter((name) => name !== 'shownoanswer'), + ...defaulted, + ...[...lsSettings.keys()].filter((name) => !defaulted.includes(name)), ]; for (const name of ordered) { this.rowEmitter.addRow( diff --git a/tests/fixtures/surveys/all_types_survey/tsv.tsv b/tests/fixtures/surveys/all_types_survey/tsv.tsv index 0df6cf2..c59fdd1 100644 --- a/tests/fixtures/surveys/all_types_survey/tsv.tsv +++ b/tests/fixtures/surveys/all_types_survey/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 de de S format 1 A de S shownoanswer 1 N de +S allowprev 1 Y de +S showxquestions 1 N de SL surveyls_title 1 Workshop-Feedback-Umfrage de G 1 Questions 1 de Q S hidden1 1 hidden1 de 1 1 diff --git a/tests/fixtures/surveys/appearances_survey/tsv.tsv b/tests/fixtures/surveys/appearances_survey/tsv.tsv index 17c6158..0fef2a9 100644 --- a/tests/fixtures/surveys/appearances_survey/tsv.tsv +++ b/tests/fixtures/surveys/appearances_survey/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 de de S format 1 A de S shownoanswer 1 N de +S allowprev 1 Y de +S showxquestions 1 N de SL surveyls_title 1 Darstellungen de G 1 Über Sie 1 de Q ! wohnort 1 Wo wohnen Sie? de 1 diff --git a/tests/fixtures/surveys/basic_survey/tsv.tsv b/tests/fixtures/surveys/basic_survey/tsv.tsv index 8eb13ec..9fb4394 100644 --- a/tests/fixtures/surveys/basic_survey/tsv.tsv +++ b/tests/fixtures/surveys/basic_survey/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Basic Survey en G Questions 1 en Q S respondentname 1 What is your name? en Y 1 diff --git a/tests/fixtures/surveys/bilingual_survey/tsv.tsv b/tests/fixtures/surveys/bilingual_survey/tsv.tsv index cbea799..2b2cd5e 100644 --- a/tests/fixtures/surveys/bilingual_survey/tsv.tsv +++ b/tests/fixtures/surveys/bilingual_survey/tsv.tsv @@ -3,6 +3,8 @@ S language 1 de de S additional_languages 1 en de S format 1 A de S shownoanswer 1 N de +S allowprev 1 Y de +S showxquestions 1 N de SL surveyls_title 1 Zweisprachige Befragung de SL surveyls_title 1 Zweisprachige Befragung en G 1 Questions 1 de diff --git a/tests/fixtures/surveys/choice_filter_survey/tsv.tsv b/tests/fixtures/surveys/choice_filter_survey/tsv.tsv index 9a4da71..7243de8 100644 --- a/tests/fixtures/surveys/choice_filter_survey/tsv.tsv +++ b/tests/fixtures/surveys/choice_filter_survey/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 de de S format 1 A de S shownoanswer 1 N de +S allowprev 1 Y de +S showxquestions 1 N de SL surveyls_title 1 Auswahl filtern de G Questions 1 de Q M k04 1 Welche Themen sind Ihnen wichtig? de 1 diff --git a/tests/fixtures/surveys/complex_survey/tsv.tsv b/tests/fixtures/surveys/complex_survey/tsv.tsv index 20b7352..6fe607b 100644 --- a/tests/fixtures/surveys/complex_survey/tsv.tsv +++ b/tests/fixtures/surveys/complex_survey/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Complex Survey with Groups and Logic en G 1 Demographics 1 en Q S fullname 1 Full Name en Y 1 diff --git a/tests/fixtures/surveys/complex_xpath_survey/tsv.tsv b/tests/fixtures/surveys/complex_xpath_survey/tsv.tsv index 6c5d987..489039f 100644 --- a/tests/fixtures/surveys/complex_xpath_survey/tsv.tsv +++ b/tests/fixtures/surveys/complex_xpath_survey/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Complex XPath Survey en G 1 Consent and Demographics 1 en Q S consent 1 Do you consent to participate? en 1 diff --git a/tests/fixtures/surveys/grid_survey/tsv.tsv b/tests/fixtures/surveys/grid_survey/tsv.tsv index 5d0be74..ab4408d 100644 --- a/tests/fixtures/surveys/grid_survey/tsv.tsv +++ b/tests/fixtures/surveys/grid_survey/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 de de S format 1 A de S shownoanswer 1 N de +S allowprev 1 Y de +S showxquestions 1 N de SL surveyls_title 1 Grid-Hinweise de G 1 Questions 1 de Q X intro 1 Einige Fragen zu Ihrem Vertrauen. de diff --git a/tests/fixtures/surveys/hints_survey/tsv.tsv b/tests/fixtures/surveys/hints_survey/tsv.tsv index 58e6384..bc5bf4f 100644 --- a/tests/fixtures/surveys/hints_survey/tsv.tsv +++ b/tests/fixtures/surveys/hints_survey/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 de de S format 1 A de S shownoanswer 1 N de +S allowprev 1 Y de +S showxquestions 1 N de SL surveyls_title 1 Hinweise de G Questions 1 de Q X intro 1 Die folgenden Fragen betreffen Ihren Haushalt. de diff --git a/tests/fixtures/surveys/multilingual_survey/tsv.tsv b/tests/fixtures/surveys/multilingual_survey/tsv.tsv index 2fab8f2..ba2794b 100644 --- a/tests/fixtures/surveys/multilingual_survey/tsv.tsv +++ b/tests/fixtures/surveys/multilingual_survey/tsv.tsv @@ -3,6 +3,8 @@ S language 1 en en S additional_languages 1 es en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Multilingual Survey en SL surveyls_title 1 Encuesta multilingüe es G 1 Demographics 1 en diff --git a/tests/fixtures/surveys/multipage_survey/tsv.tsv b/tests/fixtures/surveys/multipage_survey/tsv.tsv index e619d33..756f3d2 100644 --- a/tests/fixtures/surveys/multipage_survey/tsv.tsv +++ b/tests/fixtures/surveys/multipage_survey/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 G en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Multi-Page Survey en G 1 Page One 1 en Q S name 1 Your name en 1 diff --git a/tests/fixtures/surveys/numeric_table_survey/tsv.tsv b/tests/fixtures/surveys/numeric_table_survey/tsv.tsv index 292bfe8..9fb5db2 100644 --- a/tests/fixtures/surveys/numeric_table_survey/tsv.tsv +++ b/tests/fixtures/surveys/numeric_table_survey/tsv.tsv @@ -3,6 +3,8 @@ S language 1 de de S additional_languages 1 en de S format 1 A de S shownoanswer 1 N de +S allowprev 1 Y de +S showxquestions 1 N de SL surveyls_title 1 Tabellen mit Zahlen de SL surveyls_title 1 Tabellen mit Zahlen en G 1 Questions 1 de diff --git a/tests/fixtures/surveys/selection_count_survey/tsv.tsv b/tests/fixtures/surveys/selection_count_survey/tsv.tsv index f347813..40bb2d3 100644 --- a/tests/fixtures/surveys/selection_count_survey/tsv.tsv +++ b/tests/fixtures/surveys/selection_count_survey/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 de de S format 1 A de S shownoanswer 1 N de +S allowprev 1 Y de +S showxquestions 1 N de SL surveyls_title 1 Auswahl zählen de G Questions 1 de Q M k04 1 Welche Themen sind Ihnen wichtig? Bitte wählen Sie bis zu drei Themen. de Y 1 1 3 Bitte wählen Sie ein bis drei Themen. diff --git a/tests/fixtures/surveys/settings_survey/tsv.tsv b/tests/fixtures/surveys/settings_survey/tsv.tsv index fd3d2a2..bc389df 100644 --- a/tests/fixtures/surveys/settings_survey/tsv.tsv +++ b/tests/fixtures/surveys/settings_survey/tsv.tsv @@ -3,6 +3,7 @@ S language 1 en en S format 1 A en S shownoanswer 1 N en S allowprev 1 Y en +S showxquestions 1 N en S showqnumcode 1 X en SL surveyls_title 1 Settings Test Survey en SL surveyls_welcometext 1 Welcome to the survey! en diff --git a/tests/fixtures/surveys/testA/tsv.tsv b/tests/fixtures/surveys/testA/tsv.tsv index 9998f87..ce80f69 100644 --- a/tests/fixtures/surveys/testA/tsv.tsv +++ b/tests/fixtures/surveys/testA/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 1 N en SL surveyls_title 1 Untitled Survey en G 1 intro 1 en Q X introtext 1 Hey! Wir möchten gerne wissen, wie TestWerk für dich ist und was es dir bringt. Deine Antworten sind anonym und helfen uns, unser Angebot zu verbessern. Es gibt keine richtigen oder falschen Antworten - sag einfach deine Meinung! en diff --git a/tests/fixtures/surveys/testB/tsv.tsv b/tests/fixtures/surveys/testB/tsv.tsv index 9ff5b6c..500c7cd 100644 --- a/tests/fixtures/surveys/testB/tsv.tsv +++ b/tests/fixtures/surveys/testB/tsv.tsv @@ -3,6 +3,8 @@ S language 1 de de S additional_languages 1 en de S format 1 A de S shownoanswer 1 N de +S allowprev 1 Y de +S showxquestions 1 N de SL surveyls_title 1 Untitled Survey de SL surveyls_title 1 Untitled Survey en G 1 Hallo! 1 de diff --git a/tests/fixtures/surveys/validation_relevance_survey/tsv.tsv b/tests/fixtures/surveys/validation_relevance_survey/tsv.tsv index 47bab95..fb2977f 100644 --- a/tests/fixtures/surveys/validation_relevance_survey/tsv.tsv +++ b/tests/fixtures/surveys/validation_relevance_survey/tsv.tsv @@ -2,6 +2,8 @@ class type/scale name relevance text help language validation em_validation_q ma S language 1 en en S format 1 A en S shownoanswer 1 N en +S allowprev 1 Y en +S showxquestions 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 en diff --git a/tests/ts/integration/cli.test.ts b/tests/ts/integration/cli.test.ts index f7a7e3a..d025368 100644 --- a/tests/ts/integration/cli.test.ts +++ b/tests/ts/integration/cli.test.ts @@ -188,6 +188,17 @@ describe('xlsform2lstsv', () => { expect(sRow(shown, 'shownoanswer')).toBeUndefined(); }); + test('--no-setting-defaults drops allowprev=Y and showxquestions=N (#240)', () => { + const form = writeXlsx('simple', SIMPLE); + const on = cli('xlsform2lstsv', form).stdout; + expect(sRow(on, 'allowprev')?.text).toBe('Y'); + expect(sRow(on, 'showxquestions')?.text).toBe('N'); + const off = cli('xlsform2lstsv', form, '--no-setting-defaults').stdout; + expect(sRow(off, 'allowprev')).toBeUndefined(); + expect(sRow(off, 'showxquestions')).toBeUndefined(); + expect(sRow(off, 'shownoanswer')?.text).toBe('N'); + }); + test('--no-welcome-note keeps a `welcome` note as a question', () => { const form = writeXlsx('welcome', { survey: [ diff --git a/tests/ts/unit/limesurveySettings.test.ts b/tests/ts/unit/limesurveySettings.test.ts index 8c64aa4..2f36873 100644 --- a/tests/ts/unit/limesurveySettings.test.ts +++ b/tests/ts/unit/limesurveySettings.test.ts @@ -20,7 +20,7 @@ const survey = [{ type: 'text', name: 'q1', label: 'Q' }]; async function convert( settings: Record, - config: { hideNoAnswer?: boolean } = {}, + config: { hideNoAnswer?: boolean; surveySettingDefaults?: boolean } = {}, ) { const warnings: Diagnostic[] = []; const tsv = await new XLSFormToTSVConverter({ @@ -131,8 +131,8 @@ describe('xlsform → LimeSurvey S rows', () => { ['format', 'A'], ['shownoanswer', 'N'], ['allowprev', 'Y'], - ['allowsave', 'Y'], ['showxquestions', 'N'], + ['allowsave', 'Y'], ['showgroupinfo', 'B'], ['showqnumcode', 'X'], ['showprogress', 'Y'], @@ -140,15 +140,46 @@ describe('xlsform → LimeSurvey S rows', () => { expect(warnings).toEqual([]); }); - test('without columns the S rows are unchanged', async () => { + test('without columns the S rows carry the defaults (#240)', async () => { const { sRows } = await convert({}); expect(sRows).toEqual([ ['language', 'de'], ['format', 'A'], ['shownoanswer', 'N'], + ['allowprev', 'Y'], + ['showxquestions', 'N'], + ]); + }); + + test('a column overrides allowprev / showxquestions: one row each (#240)', async () => { + const { sRows } = await convert({ + limesurvey_allowprev: 'N', + limesurvey_showxquestions: 'Y', + }); + expect(sRows.filter(([n]) => n === 'allowprev')).toEqual([ + ['allowprev', 'N'], + ]); + expect(sRows.filter(([n]) => n === 'showxquestions')).toEqual([ + ['showxquestions', 'Y'], ]); }); + test('surveySettingDefaults=false leaves both out; columns still count (#240)', async () => { + expect((await convert({}, { surveySettingDefaults: false })).sRows).toEqual( + [ + ['language', 'de'], + ['format', 'A'], + ['shownoanswer', 'N'], + ], + ); + const { sRows } = await convert( + { limesurvey_allowprev: 'Y' }, + { surveySettingDefaults: false }, + ); + expect(sRows).toContainEqual(['allowprev', 'Y']); + expect(sRows.map(([n]) => n)).not.toContain('showxquestions'); + }); + test('limesurvey_shownoanswer overrides the default: one row', async () => { const { sRows } = await convert({ limesurvey_shownoanswer: 'Y' }); expect(sRows.filter(([name]) => name === 'shownoanswer')).toEqual([ @@ -160,6 +191,8 @@ describe('xlsform → LimeSurvey S rows', () => { expect((await convert({}, { hideNoAnswer: false })).sRows).toEqual([ ['language', 'de'], ['format', 'A'], + ['allowprev', 'Y'], + ['showxquestions', 'N'], ]); const { sRows } = await convert( { limesurvey_shownoanswer: 'N' }, @@ -194,20 +227,22 @@ describe('LimeSurvey S rows → xlsform', () => { { name: 'format', text: 'G' }, { name: 'shownoanswer', text: 'N' }, { name: 'allowprev', text: 'Y' }, + { name: 'showxquestions', text: 'N' }, + { name: 'allowsave', text: 'Y' }, { name: 'admin', text: 'Jane' }, { name: 'showqnumcode', text: 'Q' }, { name: 'questionindex', text: '1' }, ]), ).toEqual({ - limesurvey_allowprev: 'Y', + limesurvey_allowsave: 'Y', limesurvey_questionindex: '1', }); }); test('round trip: xlsform → TSV → xlsform keeps the columns', async () => { const settings = { - limesurvey_allowprev: 'Y', - limesurvey_showxquestions: 'N', + limesurvey_allowprev: 'N', + limesurvey_showxquestions: 'Y', limesurvey_showqnumcode: 'X', limesurvey_shownoanswer: 'Y', }; @@ -223,8 +258,9 @@ describe('LimeSurvey S rows → xlsform', () => { expect(again).toBe(tsv); }); - test('a form without columns gets none back', async () => { - const { tsv } = await convert({}); + test('a form without columns gets none back, defaults included (#240)', async () => { + const { tsv, sRows } = await convert({}); + expect(sRows).toContainEqual(['allowprev', 'Y']); const back = lstsvToXlsform(tsv).settings[0] ?? {}; expect( Object.keys(back).filter((k) => k.startsWith('limesurvey_')), @@ -233,6 +269,14 @@ describe('LimeSurvey S rows → xlsform', () => { }); describe('DDI', () => { + test('the survey-setting defaults are not in the DDI (#240)', () => { + const xml = buildDdiXml(survey, [], { + settings: { form_title: 'T', form_id: 't' }, + }); + expect(xml).not.toContain('allowprev'); + expect(xml).not.toContain('showxquestions'); + }); + test('each column is a cdl:setting note and comes back from it', () => { const settings = { form_title: 'T',