xlsform2lstsv 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,7 +35,7 @@ Convert XLSForm surveys to LimeSurvey TSV format.
35
35
  - its a complex task to ensure the transpiler covers everything and we currently cannot guarantee error free/complete transpiling
36
36
 
37
37
  - constraint_message ❌
38
- - XLSForms Calculation
38
+ - XLSForms Calculation ✅ (`calculate` type → LimeSurvey Equation question `*`; `${var}` references in labels/hints converted to EM `{var}` syntax)
39
39
  - XLSForms Trigger ❌
40
40
  - Repeats ❌
41
41
  - LimeSurvey Assessments ❌
@@ -38,7 +38,7 @@ function sanitizeName(name) {
38
38
  * @returns The transpiled LimeSurvey expression string
39
39
  * @throws Error if an unsupported node structure is encountered
40
40
  */
41
- function transpile(node, lookupAnswerCode) {
41
+ function transpile(node, ctx) {
42
42
  if (!node)
43
43
  return '';
44
44
  // https://getodk.github.io/xforms-spec/#xpath-functions
@@ -46,29 +46,30 @@ function transpile(node, lookupAnswerCode) {
46
46
  if (node.id) {
47
47
  switch (node.id) {
48
48
  case 'count':
49
- return `count(${node.args?.map(arg => transpile(arg, lookupAnswerCode)).join(', ') || ''})`;
49
+ return `count(${node.args?.map(arg => transpile(arg, ctx)).join(', ') || ''})`;
50
50
  case 'concat':
51
- return node.args?.map(arg => transpile(arg, lookupAnswerCode)).join(' + ') || '';
51
+ return node.args?.map(arg => transpile(arg, ctx)).join(' + ') || '';
52
52
  case 'regex':
53
- return `regexMatch(${node.args?.map(arg => transpile(arg, lookupAnswerCode)).join(', ') || ''})`;
53
+ return `regexMatch(${node.args?.map(arg => transpile(arg, ctx)).join(', ') || ''})`;
54
54
  case 'contains':
55
55
  // Custom handling for contains
56
56
  if (node.args?.length === 2) {
57
- return `contains(${transpile(node.args[0], lookupAnswerCode)}, ${transpile(node.args[1], lookupAnswerCode)})`;
57
+ return `contains(${transpile(node.args[0], ctx)}, ${transpile(node.args[1], ctx)})`;
58
58
  }
59
59
  break;
60
60
  case 'selected':
61
- // Handle selected(${field}, 'value') -> (field=="value")
61
+ // Handle selected(${field}, 'value')
62
62
  if (node.args?.length === 2) {
63
63
  const fieldArg = node.args[0];
64
64
  const valueArg = node.args[1];
65
- const fieldName = transpile(fieldArg, lookupAnswerCode);
66
- let value = transpile(valueArg, lookupAnswerCode);
67
- // Remove any existing quotes and use double quotes
65
+ const fieldName = transpile(fieldArg, ctx);
66
+ let value = transpile(valueArg, ctx);
67
+ // Remove any existing quotes
68
68
  value = value.replace(/^['"]|['"]$/g, "");
69
69
  const sanitizedField = sanitizeName(fieldName);
70
- if (lookupAnswerCode) {
71
- value = lookupAnswerCode(sanitizedField, value);
70
+ // Use buildSelectedExpr if available (handles select_one vs select_multiple)
71
+ if (ctx?.buildSelectedExpr) {
72
+ return ctx.buildSelectedExpr(sanitizedField, value);
72
73
  }
73
74
  return `(${sanitizedField}=="${value}")`;
74
75
  }
@@ -76,66 +77,73 @@ function transpile(node, lookupAnswerCode) {
76
77
  case 'string':
77
78
  // string() function - just return the argument
78
79
  if (node.args?.length === 1) {
79
- return transpile(node.args[0], lookupAnswerCode);
80
+ return transpile(node.args[0], ctx);
80
81
  }
81
82
  break;
82
83
  case 'number':
83
84
  // number() function - just return the argument
84
85
  if (node.args?.length === 1) {
85
- return transpile(node.args[0], lookupAnswerCode);
86
+ return transpile(node.args[0], ctx);
86
87
  }
87
88
  break;
88
89
  case 'floor':
89
90
  if (node.args?.length === 1) {
90
- return `floor(${transpile(node.args[0], lookupAnswerCode)})`;
91
+ return `floor(${transpile(node.args[0], ctx)})`;
91
92
  }
92
93
  break;
93
94
  case 'ceiling':
94
95
  if (node.args?.length === 1) {
95
- return `ceil(${transpile(node.args[0], lookupAnswerCode)})`;
96
+ return `ceil(${transpile(node.args[0], ctx)})`;
96
97
  }
97
98
  break;
98
99
  case 'round':
99
100
  if (node.args?.length === 1) {
100
- return `round(${transpile(node.args[0], lookupAnswerCode)})`;
101
+ return `round(${transpile(node.args[0], ctx)})`;
101
102
  }
102
103
  break;
103
104
  case 'sum':
104
105
  if (node.args?.length === 1) {
105
- return `sum(${transpile(node.args[0], lookupAnswerCode)})`;
106
+ return `sum(${transpile(node.args[0], ctx)})`;
106
107
  }
107
108
  break;
108
109
  case 'substring':
109
110
  if (node.args && node.args.length >= 2) {
110
- const stringArg = transpile(node.args[0], lookupAnswerCode);
111
- const startArg = transpile(node.args[1], lookupAnswerCode);
112
- const lengthArg = node.args.length > 2 ? transpile(node.args[2], lookupAnswerCode) : '';
111
+ const stringArg = transpile(node.args[0], ctx);
112
+ const startArg = transpile(node.args[1], ctx);
113
+ const lengthArg = node.args.length > 2 ? transpile(node.args[2], ctx) : '';
113
114
  return `substr(${stringArg}, ${startArg}${lengthArg ? ', ' + lengthArg : ''})`;
114
115
  }
115
116
  break;
116
117
  case 'string-length':
117
118
  if (node.args?.length === 1) {
118
- return `strlen(${transpile(node.args[0], lookupAnswerCode)})`;
119
+ return `strlen(${transpile(node.args[0], ctx)})`;
119
120
  }
120
121
  break;
121
122
  case 'starts-with':
122
123
  if (node.args?.length === 2) {
123
- return `startsWith(${transpile(node.args[0], lookupAnswerCode)}, ${transpile(node.args[1], lookupAnswerCode)})`;
124
+ return `startsWith(${transpile(node.args[0], ctx)}, ${transpile(node.args[1], ctx)})`;
124
125
  }
125
126
  break;
126
127
  case 'ends-with':
127
128
  if (node.args?.length === 2) {
128
- return `endsWith(${transpile(node.args[0], lookupAnswerCode)}, ${transpile(node.args[1], lookupAnswerCode)})`;
129
+ return `endsWith(${transpile(node.args[0], ctx)}, ${transpile(node.args[1], ctx)})`;
130
+ }
131
+ break;
132
+ case 'normalize-space':
133
+ if (node.args?.length === 1) {
134
+ return `trim(${transpile(node.args[0], ctx)})`;
129
135
  }
130
136
  break;
131
137
  case 'not':
132
138
  if (node.args?.length === 1) {
133
- return `!(${transpile(node.args[0], lookupAnswerCode)})`;
139
+ return `!(${transpile(node.args[0], ctx)})`;
134
140
  }
135
141
  break;
136
142
  case 'if':
143
+ // Use if() function instead of ternary (? :) because EM's ternary parser
144
+ // can misinterpret colons inside string literals (e.g. '2026-03-CHW: Chancenwerk')
137
145
  if (node.args?.length === 3) {
138
- return `(${transpile(node.args[0], lookupAnswerCode)} ? ${transpile(node.args[1], lookupAnswerCode)} : ${transpile(node.args[2], lookupAnswerCode)})`;
146
+ return `if(${transpile(node.args[0], ctx)}, ${transpile(node.args[1], ctx)}, ${transpile(node.args[2], ctx)})`;
139
147
  }
140
148
  break;
141
149
  case 'today':
@@ -146,15 +154,16 @@ function transpile(node, lookupAnswerCode) {
146
154
  throw new Error(`Unsupported function: ${node.id}`);
147
155
  }
148
156
  }
149
- // https://getodk.github.io/xforms-spec/#xpath-operators
157
+ // https://getodk.github.io/xforms-spec/#xpath-operators
150
158
  // to https://www.limesurvey.org/manual/ExpressionScript_-_Presentation (see syntax)
151
159
  if (node.type) {
160
+ const lookupAnswerCode = ctx?.lookupAnswerCode;
152
161
  switch (node.type) {
153
162
  // Comparison operators
154
163
  case '<=':
155
- return `${transpile(node.left, lookupAnswerCode)} <= ${transpile(node.right, lookupAnswerCode)}`;
164
+ return `${transpile(node.left, ctx)} <= ${transpile(node.right, ctx)}`;
156
165
  case '>=':
157
- return `${transpile(node.left, lookupAnswerCode)} >= ${transpile(node.right, lookupAnswerCode)}`;
166
+ return `${transpile(node.left, ctx)} >= ${transpile(node.right, ctx)}`;
158
167
  case '=':
159
168
  case '==': {
160
169
  const leftNode = node.left;
@@ -164,10 +173,10 @@ function transpile(node, lookupAnswerCode) {
164
173
  const rawValue = rightNode.value;
165
174
  const rewritten = lookupAnswerCode(fieldName, rawValue);
166
175
  if (rewritten !== rawValue) {
167
- return `${fieldName} == "${rewritten}"`;
176
+ return `${fieldName} == '${rewritten}'`;
168
177
  }
169
178
  }
170
- return `${transpile(leftNode, lookupAnswerCode)} == ${transpile(rightNode, lookupAnswerCode)}`;
179
+ return `${transpile(leftNode, ctx)} == ${transpile(rightNode, ctx)}`;
171
180
  }
172
181
  case '!=': {
173
182
  const leftNode = node.left;
@@ -177,31 +186,31 @@ function transpile(node, lookupAnswerCode) {
177
186
  const rawValue = rightNode.value;
178
187
  const rewritten = lookupAnswerCode(fieldName, rawValue);
179
188
  if (rewritten !== rawValue) {
180
- return `${fieldName} != "${rewritten}"`;
189
+ return `${fieldName} != '${rewritten}'`;
181
190
  }
182
191
  }
183
- return `${transpile(leftNode, lookupAnswerCode)} != ${transpile(rightNode, lookupAnswerCode)}`;
192
+ return `${transpile(leftNode, ctx)} != ${transpile(rightNode, ctx)}`;
184
193
  }
185
194
  case '<':
186
- return `${transpile(node.left, lookupAnswerCode)} < ${transpile(node.right, lookupAnswerCode)}`;
195
+ return `${transpile(node.left, ctx)} < ${transpile(node.right, ctx)}`;
187
196
  case '>':
188
- return `${transpile(node.left, lookupAnswerCode)} > ${transpile(node.right, lookupAnswerCode)}`;
197
+ return `${transpile(node.left, ctx)} > ${transpile(node.right, ctx)}`;
189
198
  // Arithmetic operators
190
199
  case '+':
191
- return `${transpile(node.left, lookupAnswerCode)} + ${transpile(node.right, lookupAnswerCode)}`;
200
+ return `${transpile(node.left, ctx)} + ${transpile(node.right, ctx)}`;
192
201
  case '-':
193
- return `${transpile(node.left, lookupAnswerCode)} - ${transpile(node.right, lookupAnswerCode)}`;
202
+ return `${transpile(node.left, ctx)} - ${transpile(node.right, ctx)}`;
194
203
  case '*':
195
- return `${transpile(node.left, lookupAnswerCode)} * ${transpile(node.right, lookupAnswerCode)}`;
204
+ return `${transpile(node.left, ctx)} * ${transpile(node.right, ctx)}`;
196
205
  case 'div':
197
- return `${transpile(node.left, lookupAnswerCode)} / ${transpile(node.right, lookupAnswerCode)}`;
206
+ return `${transpile(node.left, ctx)} / ${transpile(node.right, ctx)}`;
198
207
  case 'mod':
199
- return `${transpile(node.left, lookupAnswerCode)} % ${transpile(node.right, lookupAnswerCode)}`;
208
+ return `${transpile(node.left, ctx)} % ${transpile(node.right, ctx)}`;
200
209
  // Logical operators
201
210
  case 'and':
202
- return `${transpile(node.left, lookupAnswerCode)} and ${transpile(node.right, lookupAnswerCode)}`;
211
+ return `${transpile(node.left, ctx)} and ${transpile(node.right, ctx)}`;
203
212
  case 'or':
204
- return `${transpile(node.left, lookupAnswerCode)} or ${transpile(node.right, lookupAnswerCode)}`;
213
+ return `${transpile(node.left, ctx)} or ${transpile(node.right, ctx)}`;
205
214
  // Unsupported operators
206
215
  case '|':
207
216
  case '/':
@@ -251,7 +260,7 @@ function transpile(node, lookupAnswerCode) {
251
260
  * @param xpathExpr - The XPath expression to convert
252
261
  * @returns LimeSurvey Expression Manager syntax, or null if conversion fails
253
262
  */
254
- export async function xpathToLimeSurvey(xpathExpr, lookupAnswerCode) {
263
+ export async function xpathToLimeSurvey(xpathExpr, ctx) {
255
264
  if (!xpathExpr || xpathExpr.trim() === '') {
256
265
  return '1'; // Default relevance expression
257
266
  }
@@ -273,7 +282,7 @@ export async function xpathToLimeSurvey(xpathExpr, lookupAnswerCode) {
273
282
  throw new Error('js-xpath module does not export parse function');
274
283
  }
275
284
  const parsed = jxpath.parse(processedExpr);
276
- return transpile(parsed, lookupAnswerCode);
285
+ return transpile(parsed, ctx);
277
286
  }
278
287
  catch (error) {
279
288
  console.error(`Transpilation error: ${error.message}`);
@@ -417,14 +426,14 @@ function parseRegexMatchArguments(argsString) {
417
426
  * @param xpath - The XPath relevance expression
418
427
  * @returns LimeSurvey Expression Manager syntax
419
428
  */
420
- export async function convertRelevance(xpathExpr, lookupAnswerCode) {
429
+ export async function convertRelevance(xpathExpr, ctx) {
421
430
  if (!xpathExpr)
422
431
  return '1';
423
432
  // Preprocess: normalize operators to lowercase for jsxpath compatibility
424
433
  let normalizedXPath = xpathExpr
425
434
  .replace(/\bAND\b/gi, 'and')
426
435
  .replace(/\bOR\b/gi, 'or');
427
- const result = await xpathToLimeSurvey(normalizedXPath, lookupAnswerCode);
436
+ const result = await xpathToLimeSurvey(normalizedXPath, ctx);
428
437
  // Handle edge case: selected() with just {field} (without $)
429
438
  if (result && result.includes('selected(')) {
430
439
  return result.replace(/selected\s*\(\s*\{(\w+)\}\s*,\s*["']([^'"]+)["']\s*\)/g, (_match, fieldName, value) => {
@@ -20,11 +20,12 @@ export class TSVGenerator {
20
20
  'mandatory',
21
21
  'other',
22
22
  'default',
23
- 'same_default'
23
+ 'same_default',
24
+ 'hidden'
24
25
  ];
25
26
  const lines = [headers.join('\t')];
26
27
  for (const row of this.rows) {
27
- const values = headers.map((h) => this.escapeForTSV(row[h] || ''));
28
+ const values = headers.map((h) => this.escapeForTSV(row[h] ?? ''));
28
29
  lines.push(values.join('\t'));
29
30
  }
30
31
  return lines.join('\n');
@@ -20,6 +20,7 @@ export const TYPE_MAPPINGS = {
20
20
  select_multiple: { limeSurveyType: 'M', supportsOther: true, answerClass: 'SQ' },
21
21
  // Other types
22
22
  note: { limeSurveyType: 'X' },
23
+ calculate: { limeSurveyType: '*' },
23
24
  rank: { limeSurveyType: 'R', answerClass: 'A', supportsOther: true },
24
25
  };
25
26
  export class TypeMapper {
@@ -1,5 +1,5 @@
1
1
  import { ConfigManager } from './config/ConfigManager.js';
2
- import { convertRelevance, convertConstraint } from './converters/xpathTranspiler.js';
2
+ import { convertRelevance, convertConstraint, xpathToLimeSurvey } from './converters/xpathTranspiler.js';
3
3
  import { FieldSanitizer } from './processors/FieldSanitizer.js';
4
4
  import { TSVGenerator } from './processors/TSVGenerator.js';
5
5
  import { TypeMapper } from './processors/TypeMapper.js';
@@ -7,7 +7,7 @@ import { getBaseLanguage } from './utils/languageUtils.js';
7
7
  // Metadata types that should be silently skipped (no visual representation)
8
8
  const SKIP_TYPES = [
9
9
  'start', 'end', 'today', 'deviceid', 'username',
10
- 'calculate', 'hidden', 'audit'
10
+ 'hidden', 'audit'
11
11
  ];
12
12
  // Unimplemented XLSForm types that should raise an error
13
13
  const UNIMPLEMENTED_TYPES = [
@@ -51,6 +51,7 @@ export class XLSFormToTSVConverter {
51
51
  this.groupContentBuffer = [];
52
52
  this.answerCodeMap = new Map();
53
53
  this.questionToListMap = new Map();
54
+ this.questionBaseTypeMap = new Map();
54
55
  }
55
56
  /**
56
57
  * Get the current configuration
@@ -201,11 +202,13 @@ export class XLSFormToTSVConverter {
201
202
  }
202
203
  buildQuestionToListMap(surveyData) {
203
204
  this.questionToListMap = new Map();
205
+ this.questionBaseTypeMap = new Map();
204
206
  for (const row of surveyData) {
205
207
  const typeInfo = this.parseType(row.type || '');
206
208
  if (typeInfo.listName && row.name) {
207
209
  const sanitizedName = this.sanitizeName(row.name.trim());
208
210
  this.questionToListMap.set(sanitizedName, typeInfo.listName);
211
+ this.questionBaseTypeMap.set(sanitizedName, typeInfo.base);
209
212
  }
210
213
  }
211
214
  }
@@ -537,6 +540,21 @@ export class XLSFormToTSVConverter {
537
540
  sanitizeAnswerCode(code) {
538
541
  return this.fieldSanitizer.sanitizeAnswerCode(code);
539
542
  }
543
+ /**
544
+ * Convert ${varname} references in text to LimeSurvey EM syntax {sanitizedname}.
545
+ */
546
+ convertVariableReferences(text) {
547
+ return text.replace(/\$\{([^}]+)\}/g, (_, name) => {
548
+ const sanitized = name.replace(/[_-]/g, '');
549
+ return `{${sanitized}}`;
550
+ });
551
+ }
552
+ /**
553
+ * Transpile an XLSForm calculation expression to a LimeSurvey EM expression.
554
+ */
555
+ async convertCalculation(calculation) {
556
+ return await xpathToLimeSurvey(calculation, this.buildTranspilerContext());
557
+ }
540
558
  async addGroup(row) {
541
559
  // Auto-generate name if missing (matches LimeSurvey behavior)
542
560
  const groupName = row.name && row.name.trim() !== ''
@@ -554,10 +572,10 @@ export class XLSFormToTSVConverter {
554
572
  this.tsvGenerator.addRow({
555
573
  class: 'G',
556
574
  'type/scale': groupSeqKey,
557
- name: groupName,
575
+ name: this.getLanguageSpecificValue(row.label, lang) || groupName,
558
576
  relevance: await this.convertRelevance(row.relevant),
559
- text: this.getLanguageSpecificValue(row.label, lang) || groupName,
560
- help: this.getLanguageSpecificValue(row.hint, lang) || '',
577
+ text: this.getLanguageSpecificValue(row.hint, lang) || '',
578
+ help: '',
561
579
  language: lang,
562
580
  validation: '',
563
581
  em_validation_q: "",
@@ -609,22 +627,40 @@ export class XLSFormToTSVConverter {
609
627
  }
610
628
  // Notes have special handling
611
629
  const isNote = xfTypeInfo.base === 'note';
630
+ const isCalculate = xfTypeInfo.base === 'calculate';
631
+ // For calculate type, transpile the calculation expression to EM syntax
632
+ let calculationExpr = '';
633
+ if (isCalculate && row.calculation) {
634
+ calculationExpr = await this.convertCalculation(row.calculation);
635
+ }
612
636
  // Add main question for each language
613
637
  for (const lang of this.availableLanguages) {
638
+ let text;
639
+ if (isCalculate) {
640
+ // Equation question: the EM expression wrapped in {} IS the question text
641
+ text = `{${calculationExpr}}`;
642
+ }
643
+ else {
644
+ text = this.getLanguageSpecificValue(row.label, lang) || questionName;
645
+ }
646
+ // Convert ${var} references to EM {var} syntax in text and help
647
+ text = this.convertVariableReferences(text);
648
+ const help = this.convertVariableReferences(this.getLanguageSpecificValue(row.hint, lang) || '');
614
649
  this.bufferRow({
615
650
  class: 'Q',
616
651
  'type/scale': isNote ? 'X' : lsType.type,
617
652
  name: questionName,
618
653
  relevance: await this.convertRelevance(row.relevant),
619
- text: this.getLanguageSpecificValue(row.label, lang) || questionName,
620
- help: this.getLanguageSpecificValue(row.hint, lang) || '',
654
+ text,
655
+ help,
621
656
  language: lang,
622
657
  validation: "",
623
- em_validation_q: isNote ? "" : await convertConstraint(row.constraint || ""),
624
- mandatory: isNote ? '' : (row.required === 'yes' || row.required === 'true' ? 'Y' : ''),
625
- other: isNote ? '' : (lsType.other ? 'Y' : ''),
626
- default: isNote ? '' : (row.default || ''),
627
- same_default: ''
658
+ em_validation_q: (isNote || isCalculate) ? "" : await convertConstraint(row.constraint || ""),
659
+ mandatory: (isNote || isCalculate) ? '' : (row.required === 'yes' || row.required === 'true' ? 'Y' : ''),
660
+ other: (isNote || isCalculate) ? '' : (lsType.other ? 'Y' : ''),
661
+ default: (isNote || isCalculate) ? '' : (row.default || ''),
662
+ same_default: '',
663
+ hidden: isCalculate ? '1' : '',
628
664
  });
629
665
  }
630
666
  // Reset answer sequence for this question
@@ -784,20 +820,37 @@ export class XLSFormToTSVConverter {
784
820
  }
785
821
  }
786
822
  }
823
+ lookupAnswerCode(fieldName, choiceValue) {
824
+ // Truncate to 20 chars to match converter's field sanitization
825
+ // (the transpiler only removes _/- but doesn't truncate)
826
+ const truncated = fieldName.length > 20 ? fieldName.substring(0, 20) : fieldName;
827
+ const listName = this.questionToListMap.get(truncated);
828
+ if (!listName)
829
+ return { code: choiceValue, listName: undefined };
830
+ const codeMap = this.answerCodeMap.get(listName);
831
+ if (!codeMap)
832
+ return { code: choiceValue, listName };
833
+ return { code: codeMap.get(choiceValue) ?? choiceValue, listName };
834
+ }
835
+ buildTranspilerContext() {
836
+ return {
837
+ lookupAnswerCode: (fieldName, choiceValue) => {
838
+ return this.lookupAnswerCode(fieldName, choiceValue).code;
839
+ },
840
+ buildSelectedExpr: (fieldName, choiceValue) => {
841
+ const truncated = fieldName.length > 20 ? fieldName.substring(0, 20) : fieldName;
842
+ const { code } = this.lookupAnswerCode(fieldName, choiceValue);
843
+ const baseType = this.questionBaseTypeMap.get(truncated);
844
+ if (baseType === 'select_multiple') {
845
+ return `(${truncated}_${code}.NAOK == 'Y')`;
846
+ }
847
+ return `(${truncated}.NAOK=='${code}')`;
848
+ },
849
+ };
850
+ }
787
851
  async convertRelevance(relevant) {
788
852
  if (!relevant)
789
853
  return '1';
790
- return await convertRelevance(relevant, (questionName, choiceValue) => {
791
- // Truncate to 20 chars to match converter's field sanitization
792
- // (the transpiler only removes _/- but doesn't truncate)
793
- const truncated = questionName.length > 20 ? questionName.substring(0, 20) : questionName;
794
- const listName = this.questionToListMap.get(truncated);
795
- if (!listName)
796
- return choiceValue;
797
- const codeMap = this.answerCodeMap.get(listName);
798
- if (!codeMap)
799
- return choiceValue;
800
- return codeMap.get(choiceValue) ?? choiceValue;
801
- });
854
+ return await convertRelevance(relevant, this.buildTranspilerContext());
802
855
  }
803
856
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xlsform2lstsv",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Convert XLSForm surveys to LimeSurvey TSV format",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",