@tehw0lf/n8n-nodes-toon 1.3.3 → 1.4.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 +40 -8
- package/dist/nodes/Toon/Toon.node.json +1 -1
- package/dist/nodes/Toon/ToonDecoder.d.ts +29 -10
- package/dist/nodes/Toon/ToonDecoder.js +420 -383
- package/dist/nodes/Toon/ToonDecoder.js.map +1 -1
- package/dist/nodes/Toon/ToonEncoder.d.ts +13 -5
- package/dist/nodes/Toon/ToonEncoder.js +182 -119
- package/dist/nodes/Toon/ToonEncoder.js.map +1 -1
- package/dist/nodes/Toon/ToonUtils.d.ts +37 -2
- package/dist/nodes/Toon/ToonUtils.js +447 -33
- package/dist/nodes/Toon/ToonUtils.js.map +1 -1
- package/dist/nodes/Toon/types.d.ts +8 -1
- package/dist/nodes/Toon/types.js.map +1 -1
- package/dist/package.json +2 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -38,476 +38,513 @@ const types_1 = require("./types");
|
|
|
38
38
|
const utils = __importStar(require("./ToonUtils"));
|
|
39
39
|
class ToonDecoder {
|
|
40
40
|
constructor(options) {
|
|
41
|
+
this.lines = [];
|
|
42
|
+
this.pos = 0;
|
|
43
|
+
this.inHeaderSpan = false;
|
|
41
44
|
this.options = options;
|
|
42
45
|
}
|
|
43
46
|
decode(toonText) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
const rootForm = this.determineRootForm(nonEmptyLines);
|
|
50
|
-
let result;
|
|
51
|
-
if (rootForm === 'array') {
|
|
52
|
-
result = this.parseArray(nonEmptyLines, 0);
|
|
53
|
-
}
|
|
54
|
-
else if (rootForm === 'primitive') {
|
|
55
|
-
const token = nonEmptyLines[0].content.trim();
|
|
56
|
-
result = this.parseTokenValue(token);
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
result = this.parseObject(nonEmptyLines, 0, 0);
|
|
47
|
+
this.lines = this.parseLines(toonText);
|
|
48
|
+
this.pos = 0;
|
|
49
|
+
if (this.lines.length === 0) {
|
|
50
|
+
return {};
|
|
60
51
|
}
|
|
52
|
+
let result = this.parseRoot();
|
|
61
53
|
if (this.options.expandPaths === 'safe' && utils.isPlainObject(result)) {
|
|
62
54
|
result = this.expandPaths(result);
|
|
63
55
|
}
|
|
64
56
|
return result;
|
|
65
57
|
}
|
|
66
58
|
parseLines(text) {
|
|
67
|
-
|
|
59
|
+
var _a, _b;
|
|
60
|
+
const withoutBom = text.startsWith('') ? text.slice(1) : text;
|
|
61
|
+
const rawLines = withoutBom.split('\n');
|
|
68
62
|
const parsed = [];
|
|
63
|
+
let blankBefore = false;
|
|
69
64
|
for (let i = 0; i < rawLines.length; i++) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
});
|
|
65
|
+
let line = rawLines[i].replace(/\r$/, '');
|
|
66
|
+
line = line.replace(/ +$/, '');
|
|
67
|
+
const lineNumber = i + 1;
|
|
68
|
+
if (/^ *#/.test(line)) {
|
|
69
|
+
continue;
|
|
76
70
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
if (line.trim() === '') {
|
|
72
|
+
blankBefore = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (/^[ \t]*\t/.test(line)) {
|
|
76
|
+
if (this.options.strict) {
|
|
77
|
+
throw new types_1.ToonDecodingError('Tabs not allowed in indentation', {
|
|
78
|
+
lineNumber,
|
|
79
|
+
line,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
line = line.replace(/^[ \t]+/, (ws) => ws.replace(/\t/g, ''));
|
|
83
|
+
}
|
|
84
|
+
const indent = (_b = (_a = line.match(/^( *)/)) === null || _a === void 0 ? void 0 : _a[1].length) !== null && _b !== void 0 ? _b : 0;
|
|
85
|
+
if (this.options.strict && indent % this.options.indent !== 0) {
|
|
86
|
+
throw new types_1.ToonDecodingError(`Indentation must be multiple of ${this.options.indent}, got ${indent} spaces`, { lineNumber, line });
|
|
84
87
|
}
|
|
85
88
|
parsed.push({
|
|
86
|
-
content: line,
|
|
89
|
+
content: line.slice(indent),
|
|
87
90
|
indent,
|
|
88
|
-
lineNumber
|
|
89
|
-
isEmpty:
|
|
91
|
+
lineNumber,
|
|
92
|
+
isEmpty: false,
|
|
93
|
+
blankBefore,
|
|
90
94
|
});
|
|
95
|
+
blankBefore = false;
|
|
91
96
|
}
|
|
92
97
|
return parsed;
|
|
93
98
|
}
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (firstLine === '[]') {
|
|
100
|
-
return 'array';
|
|
99
|
+
withinHeaderSpan(parse) {
|
|
100
|
+
const previous = this.inHeaderSpan;
|
|
101
|
+
this.inHeaderSpan = true;
|
|
102
|
+
try {
|
|
103
|
+
return parse();
|
|
101
104
|
}
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
finally {
|
|
106
|
+
this.inHeaderSpan = previous;
|
|
104
107
|
}
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
}
|
|
109
|
+
checkBlankInSpan(line, seen) {
|
|
110
|
+
const insideSpan = seen > 0 || this.inHeaderSpan;
|
|
111
|
+
if (this.options.strict && insideSpan && line.blankBefore) {
|
|
112
|
+
throw new types_1.ToonDecodingError('Blank line inside a header span', {
|
|
113
|
+
lineNumber: line.lineNumber,
|
|
114
|
+
line: line.content,
|
|
115
|
+
});
|
|
107
116
|
}
|
|
108
|
-
return 'object';
|
|
109
117
|
}
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
depthOf(line) {
|
|
119
|
+
return Math.floor(line.indent / this.options.indent);
|
|
120
|
+
}
|
|
121
|
+
peek() {
|
|
122
|
+
return this.pos < this.lines.length ? this.lines[this.pos] : null;
|
|
123
|
+
}
|
|
124
|
+
parseRoot() {
|
|
125
|
+
const first = this.lines[0];
|
|
126
|
+
const content = first.content;
|
|
112
127
|
if (content === '[]') {
|
|
113
|
-
|
|
128
|
+
this.pos = 1;
|
|
129
|
+
this.checkNoTrailingContent();
|
|
130
|
+
return [];
|
|
114
131
|
}
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
117
|
-
if (
|
|
118
|
-
|
|
132
|
+
const header = this.tryParseHeader(first);
|
|
133
|
+
if (header !== null && header.key === null && this.depthOf(first) === 0) {
|
|
134
|
+
if (header.keyed) {
|
|
135
|
+
const value = this.parseKeyedTabular(header, 0);
|
|
136
|
+
this.checkNoTrailingContent();
|
|
137
|
+
return value;
|
|
119
138
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
139
|
+
const value = this.parseArrayBody(header, 0);
|
|
140
|
+
this.checkNoTrailingContent();
|
|
141
|
+
return value;
|
|
124
142
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (key.startsWith('"') && key.endsWith('"')) {
|
|
129
|
-
key = utils.unescapeString(key.slice(1, -1));
|
|
130
|
-
}
|
|
143
|
+
if (this.lines.length === 1 && utils.findUnquoted(content, ':') < 0) {
|
|
144
|
+
this.pos = 1;
|
|
145
|
+
return this.parseValueToken(content, first);
|
|
131
146
|
}
|
|
132
|
-
|
|
133
|
-
|
|
147
|
+
const scalarLines = this.lines.filter((line) => this.depthOf(line) === 0 &&
|
|
148
|
+
utils.findUnquoted(line.content, ':') < 0 &&
|
|
149
|
+
!line.content.startsWith('- '));
|
|
150
|
+
if (scalarLines.length >= 2) {
|
|
151
|
+
throw new types_1.ToonDecodingError('Multiple depth-0 lines that are neither headers nor key-value lines', { lineNumber: scalarLines[1].lineNumber, line: scalarLines[1].content });
|
|
134
152
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
return this.parseObject(0);
|
|
154
|
+
}
|
|
155
|
+
checkNoTrailingContent() {
|
|
156
|
+
const next = this.peek();
|
|
157
|
+
if (next === null) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (this.options.strict) {
|
|
161
|
+
throw new types_1.ToonDecodingError('Trailing content after completed root form', {
|
|
162
|
+
lineNumber: next.lineNumber,
|
|
163
|
+
line: next.content,
|
|
139
164
|
});
|
|
140
165
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (fieldsPart) {
|
|
150
|
-
fields = this.parseDelimitedTokens(fieldsPart, delimiter).map((token) => {
|
|
151
|
-
if (token.startsWith('"') && token.endsWith('"')) {
|
|
152
|
-
return utils.unescapeString(token.slice(1, -1));
|
|
153
|
-
}
|
|
154
|
-
return token;
|
|
166
|
+
this.pos = this.lines.length;
|
|
167
|
+
}
|
|
168
|
+
tryParseHeader(line) {
|
|
169
|
+
const outcome = utils.tryParseHeader(line.content);
|
|
170
|
+
if (outcome.error !== null && this.options.strict) {
|
|
171
|
+
throw new types_1.ToonDecodingError(outcome.error, {
|
|
172
|
+
lineNumber: line.lineNumber,
|
|
173
|
+
line: line.content,
|
|
155
174
|
});
|
|
156
175
|
}
|
|
157
|
-
return
|
|
158
|
-
key,
|
|
159
|
-
length,
|
|
160
|
-
delimiter,
|
|
161
|
-
fields,
|
|
162
|
-
rawLine: content,
|
|
163
|
-
lineNumber: line.lineNumber,
|
|
164
|
-
};
|
|
176
|
+
return outcome.header;
|
|
165
177
|
}
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (escaped) {
|
|
174
|
-
current += char;
|
|
175
|
-
escaped = false;
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
if (char === '\\') {
|
|
179
|
-
current += char;
|
|
180
|
-
escaped = true;
|
|
181
|
-
continue;
|
|
178
|
+
parseObject(depth) {
|
|
179
|
+
const obj = {};
|
|
180
|
+
while (this.pos < this.lines.length) {
|
|
181
|
+
const line = this.lines[this.pos];
|
|
182
|
+
const lineDepth = this.depthOf(line);
|
|
183
|
+
if (lineDepth < depth) {
|
|
184
|
+
break;
|
|
182
185
|
}
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
+
if (lineDepth > depth) {
|
|
187
|
+
if (this.options.strict) {
|
|
188
|
+
throw new types_1.ToonDecodingError('Over-indented line: no enclosing scope was opened', { lineNumber: line.lineNumber, line: line.content });
|
|
189
|
+
}
|
|
190
|
+
this.pos++;
|
|
186
191
|
continue;
|
|
187
192
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
this.checkBlankInSpan(line, 0);
|
|
194
|
+
const { key, value } = this.parseFieldLine(line, depth);
|
|
195
|
+
this.assignKey(obj, key, line);
|
|
196
|
+
utils.setDecodedKey(obj, key, value);
|
|
197
|
+
}
|
|
198
|
+
return obj;
|
|
199
|
+
}
|
|
200
|
+
assignKey(obj, key, line) {
|
|
201
|
+
if (Object.prototype.hasOwnProperty.call(obj, key) && this.options.strict) {
|
|
202
|
+
throw new types_1.ToonDecodingError(`Duplicate key '${key}' at the same depth`, {
|
|
203
|
+
lineNumber: line.lineNumber,
|
|
204
|
+
line: line.content,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
parseFieldLine(line, depth) {
|
|
209
|
+
const header = this.tryParseHeader(line);
|
|
210
|
+
if (header !== null) {
|
|
211
|
+
if (header.key === null) {
|
|
212
|
+
if (this.options.strict) {
|
|
213
|
+
throw new types_1.ToonDecodingError('Keyless header is not valid in object-field position', { lineNumber: line.lineNumber, line: line.content });
|
|
199
214
|
}
|
|
200
215
|
}
|
|
201
|
-
|
|
216
|
+
else {
|
|
217
|
+
this.pos++;
|
|
218
|
+
const value = header.keyed
|
|
219
|
+
? this.parseKeyedTabularBody(header, depth + 1)
|
|
220
|
+
: this.parseArrayBodyAfterHeader(header, depth);
|
|
221
|
+
return { key: header.key, value };
|
|
222
|
+
}
|
|
202
223
|
}
|
|
203
|
-
|
|
204
|
-
|
|
224
|
+
const colonIndex = utils.findUnquoted(line.content, ':');
|
|
225
|
+
if (colonIndex < 0) {
|
|
226
|
+
throw new types_1.ToonDecodingError('Missing colon in key context', {
|
|
227
|
+
lineNumber: line.lineNumber,
|
|
228
|
+
line: line.content,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const key = this.decodeKeyToken(line.content.slice(0, colonIndex), line);
|
|
232
|
+
const rest = utils.trimSpaces(line.content.slice(colonIndex + 1));
|
|
233
|
+
this.pos++;
|
|
234
|
+
if (rest === '[]') {
|
|
235
|
+
return { key, value: [] };
|
|
205
236
|
}
|
|
206
|
-
|
|
237
|
+
if (rest !== '') {
|
|
238
|
+
return { key, value: this.parseValueToken(rest, line) };
|
|
239
|
+
}
|
|
240
|
+
return { key, value: this.parseNestedScope(depth) };
|
|
207
241
|
}
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return [];
|
|
242
|
+
parseNestedScope(depth) {
|
|
243
|
+
const next = this.peek();
|
|
244
|
+
if (next === null || this.depthOf(next) <= depth) {
|
|
245
|
+
return {};
|
|
213
246
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
247
|
+
if (this.options.strict && this.depthOf(next) > depth + 1) {
|
|
248
|
+
throw new types_1.ToonDecodingError('Indentation depth jump: nested scope must start one level deeper', { lineNumber: next.lineNumber, line: next.content });
|
|
249
|
+
}
|
|
250
|
+
return this.parseObject(depth + 1);
|
|
251
|
+
}
|
|
252
|
+
parseArrayBodyAfterHeader(header, headerDepth) {
|
|
253
|
+
return this.parseArrayContent(header, headerDepth + 1);
|
|
254
|
+
}
|
|
255
|
+
parseArrayBody(header, headerDepth) {
|
|
256
|
+
this.pos++;
|
|
257
|
+
return this.parseArrayContent(header, headerDepth + 1);
|
|
258
|
+
}
|
|
259
|
+
parseArrayContent(header, contentDepth) {
|
|
260
|
+
var _a;
|
|
261
|
+
if (header.fields !== null && header.inline !== '') {
|
|
262
|
+
throw new types_1.ToonDecodingError('Content after a fields-bearing header colon', {
|
|
263
|
+
lineNumber: (_a = this.lines[this.pos - 1]) === null || _a === void 0 ? void 0 : _a.lineNumber,
|
|
264
|
+
line: header.inline,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
if (header.fields !== null) {
|
|
268
|
+
return this.parseTabularRows(header, contentDepth);
|
|
269
|
+
}
|
|
270
|
+
if (header.inline !== '') {
|
|
271
|
+
const values = utils
|
|
272
|
+
.splitDelimited(header.inline, header.delimiter)
|
|
273
|
+
.map((token) => this.parseValueToken(token, this.lines[this.pos - 1]));
|
|
274
|
+
this.checkCount(values.length, header.length, 'inline array values');
|
|
224
275
|
return values;
|
|
225
276
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
const values = [];
|
|
232
|
-
let currentIndex = startIndex + 1;
|
|
233
|
-
let currentObj = null;
|
|
234
|
-
const seenKeys = new Set();
|
|
235
|
-
while (currentIndex < lines.length) {
|
|
236
|
-
const line = lines[currentIndex];
|
|
237
|
-
if (line.isEmpty) {
|
|
238
|
-
if (this.options.strict) {
|
|
239
|
-
throw new types_1.ToonDecodingError('Blank lines not allowed in arrays in strict mode', {
|
|
240
|
-
lineNumber: line.lineNumber,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
currentIndex++;
|
|
244
|
-
continue;
|
|
277
|
+
if (header.length === 0) {
|
|
278
|
+
const next = this.peek();
|
|
279
|
+
if (next === null || this.depthOf(next) < contentDepth) {
|
|
280
|
+
return [];
|
|
245
281
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
282
|
+
}
|
|
283
|
+
return this.parseListItems(header, contentDepth);
|
|
284
|
+
}
|
|
285
|
+
parseTabularRows(header, rowDepth) {
|
|
286
|
+
const fields = header.fields;
|
|
287
|
+
const leafCount = utils.countLeafFields(fields);
|
|
288
|
+
const rows = [];
|
|
289
|
+
while (this.pos < this.lines.length) {
|
|
290
|
+
const line = this.lines[this.pos];
|
|
291
|
+
if (this.depthOf(line) !== rowDepth) {
|
|
251
292
|
break;
|
|
252
293
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
const content = line.content.trim();
|
|
259
|
-
if (this.options.strict && this.isInvalidArrayHeader(content)) {
|
|
260
|
-
throw new types_1.ToonDecodingError('Non-whitespace content between bracket segment and colon/fields: line MUST NOT be parsed as array header', { lineNumber: line.lineNumber, line: content });
|
|
261
|
-
}
|
|
262
|
-
if (/^\[\d+[\t|]?\]/.test(content) && this.isValidArrayHeader(content)) {
|
|
263
|
-
if (currentObj !== null) {
|
|
264
|
-
values.push(currentObj);
|
|
265
|
-
currentObj = null;
|
|
266
|
-
seenKeys.clear();
|
|
267
|
-
}
|
|
268
|
-
const nestedArray = this.parseArray(lines, currentIndex);
|
|
269
|
-
values.push(nestedArray);
|
|
270
|
-
const arrayHeader = this.parseArrayHeader(lines[currentIndex]);
|
|
271
|
-
const inlineMatch = arrayHeader.rawLine.match(/:\s*(.+)$/);
|
|
272
|
-
if (inlineMatch && inlineMatch[1].trim()) {
|
|
273
|
-
currentIndex++;
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
currentIndex = this.findNextSiblingIndex(lines, currentIndex, valueIndent);
|
|
277
|
-
}
|
|
294
|
+
const colonIndex = utils.findUnquoted(line.content, ':');
|
|
295
|
+
const delimIndex = utils.findUnquoted(line.content, header.delimiter);
|
|
296
|
+
if (colonIndex >= 0 && (delimIndex < 0 || colonIndex < delimIndex)) {
|
|
297
|
+
break;
|
|
278
298
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
const obj = this.parseObject(lines, currentIndex, valueIndent);
|
|
298
|
-
values.push(obj);
|
|
299
|
-
currentIndex = this.findNextSiblingIndex(lines, currentIndex, valueIndent);
|
|
300
|
-
}
|
|
301
|
-
else {
|
|
302
|
-
if (seenKeys.has(key)) {
|
|
303
|
-
if (currentObj !== null) {
|
|
304
|
-
values.push(currentObj);
|
|
305
|
-
}
|
|
306
|
-
currentObj = {};
|
|
307
|
-
seenKeys.clear();
|
|
308
|
-
}
|
|
309
|
-
if (currentObj === null) {
|
|
310
|
-
currentObj = {};
|
|
311
|
-
}
|
|
312
|
-
currentObj[key] = this.parseTokenValue(valuePart);
|
|
313
|
-
seenKeys.add(key);
|
|
314
|
-
currentIndex++;
|
|
299
|
+
this.checkBlankInSpan(line, rows.length);
|
|
300
|
+
const cells = utils.splitDelimited(line.content, header.delimiter);
|
|
301
|
+
this.checkWidth(cells.length, leafCount, line);
|
|
302
|
+
rows.push(this.materializeRow(cells, fields, line));
|
|
303
|
+
this.pos++;
|
|
304
|
+
}
|
|
305
|
+
this.checkCount(rows.length, header.length, 'tabular rows');
|
|
306
|
+
return rows;
|
|
307
|
+
}
|
|
308
|
+
materializeRow(cells, fields, line) {
|
|
309
|
+
let index = 0;
|
|
310
|
+
const walk = (entries) => {
|
|
311
|
+
const obj = {};
|
|
312
|
+
for (const entry of entries) {
|
|
313
|
+
if (entry.children === null) {
|
|
314
|
+
if (index < cells.length) {
|
|
315
|
+
utils.setDecodedKey(obj, entry.name, this.parseValueToken(cells[index], line));
|
|
315
316
|
}
|
|
317
|
+
index++;
|
|
316
318
|
}
|
|
317
319
|
else {
|
|
318
|
-
|
|
319
|
-
values.push(currentObj);
|
|
320
|
-
currentObj = null;
|
|
321
|
-
seenKeys.clear();
|
|
322
|
-
}
|
|
323
|
-
values.push(this.parseTokenValue(content));
|
|
324
|
-
currentIndex++;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
if (currentObj !== null) {
|
|
329
|
-
values.push(currentObj);
|
|
330
|
-
currentObj = null;
|
|
331
|
-
seenKeys.clear();
|
|
320
|
+
utils.setDecodedKey(obj, entry.name, walk(entry.children));
|
|
332
321
|
}
|
|
333
|
-
values.push(this.parseTokenValue(content));
|
|
334
|
-
currentIndex++;
|
|
335
322
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
323
|
+
return obj;
|
|
324
|
+
};
|
|
325
|
+
return walk(fields);
|
|
326
|
+
}
|
|
327
|
+
parseKeyedTabular(header, headerDepth) {
|
|
328
|
+
this.pos++;
|
|
329
|
+
return this.parseKeyedTabularBody(header, headerDepth + 1);
|
|
330
|
+
}
|
|
331
|
+
parseKeyedTabularBody(header, entryDepth) {
|
|
332
|
+
var _a;
|
|
333
|
+
if (header.inline !== '') {
|
|
334
|
+
throw new types_1.ToonDecodingError('Content after a keyed header colon', {
|
|
335
|
+
lineNumber: (_a = this.lines[this.pos - 1]) === null || _a === void 0 ? void 0 : _a.lineNumber,
|
|
336
|
+
line: header.inline,
|
|
343
337
|
});
|
|
344
338
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
339
|
+
const fields = header.fields;
|
|
340
|
+
const leafCount = utils.countLeafFields(fields);
|
|
341
|
+
const obj = {};
|
|
342
|
+
let count = 0;
|
|
343
|
+
while (this.pos < this.lines.length) {
|
|
344
|
+
const line = this.lines[this.pos];
|
|
345
|
+
if (this.depthOf(line) < entryDepth) {
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
if (this.depthOf(line) > entryDepth) {
|
|
354
349
|
if (this.options.strict) {
|
|
355
|
-
throw new types_1.ToonDecodingError('
|
|
350
|
+
throw new types_1.ToonDecodingError('Over-indented line in keyed tabular scope', {
|
|
356
351
|
lineNumber: line.lineNumber,
|
|
352
|
+
line: line.content,
|
|
357
353
|
});
|
|
358
354
|
}
|
|
359
|
-
|
|
355
|
+
this.pos++;
|
|
360
356
|
continue;
|
|
361
357
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const row = {};
|
|
373
|
-
for (let i = 0; i < header.fields.length; i++) {
|
|
374
|
-
const field = header.fields[i];
|
|
375
|
-
const value = i < tokens.length ? this.parseTokenValue(tokens[i]) : null;
|
|
376
|
-
row[field] = value;
|
|
358
|
+
const colonIndex = utils.findUnquoted(line.content, ':');
|
|
359
|
+
if (colonIndex < 0) {
|
|
360
|
+
if (this.options.strict) {
|
|
361
|
+
throw new types_1.ToonDecodingError('Line at entry depth without an unquoted colon', {
|
|
362
|
+
lineNumber: line.lineNumber,
|
|
363
|
+
line: line.content,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
this.pos++;
|
|
367
|
+
continue;
|
|
377
368
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
369
|
+
this.checkBlankInSpan(line, count);
|
|
370
|
+
const entryKey = this.decodeKeyToken(line.content.slice(0, colonIndex), line);
|
|
371
|
+
const rest = utils.trimSpaces(line.content.slice(colonIndex + 1));
|
|
372
|
+
const cells = rest === '' ? [] : utils.splitDelimited(rest, header.delimiter);
|
|
373
|
+
this.checkWidth(cells.length, leafCount, line);
|
|
374
|
+
this.assignKey(obj, entryKey, line);
|
|
375
|
+
utils.setDecodedKey(obj, entryKey, this.materializeRow(cells, fields, line));
|
|
376
|
+
count++;
|
|
377
|
+
this.pos++;
|
|
378
|
+
}
|
|
379
|
+
this.checkCount(count, header.length, 'keyed entry rows');
|
|
380
|
+
return obj;
|
|
382
381
|
}
|
|
383
|
-
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const
|
|
388
|
-
if (
|
|
389
|
-
|
|
390
|
-
continue;
|
|
382
|
+
parseListItems(header, itemDepth) {
|
|
383
|
+
const items = [];
|
|
384
|
+
while (this.pos < this.lines.length) {
|
|
385
|
+
const line = this.lines[this.pos];
|
|
386
|
+
const lineDepth = this.depthOf(line);
|
|
387
|
+
if (lineDepth < itemDepth) {
|
|
388
|
+
break;
|
|
391
389
|
}
|
|
392
|
-
if (
|
|
390
|
+
if (lineDepth === itemDepth && !this.isListItemLine(line)) {
|
|
393
391
|
break;
|
|
394
392
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (headerMatch && headerMatch[1].trim()) {
|
|
402
|
-
const header = this.parseArrayHeader(line);
|
|
403
|
-
const array = this.parseArray(lines, currentIndex);
|
|
404
|
-
if (header.key) {
|
|
405
|
-
obj[header.key] = array;
|
|
406
|
-
}
|
|
407
|
-
currentIndex = this.findNextSiblingIndex(lines, currentIndex, expectedIndent);
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
currentIndex++;
|
|
393
|
+
if (lineDepth > itemDepth) {
|
|
394
|
+
if (this.options.strict) {
|
|
395
|
+
throw new types_1.ToonDecodingError('Over-indented line in list scope', {
|
|
396
|
+
lineNumber: line.lineNumber,
|
|
397
|
+
line: line.content,
|
|
398
|
+
});
|
|
411
399
|
}
|
|
400
|
+
this.pos++;
|
|
412
401
|
continue;
|
|
413
402
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
403
|
+
this.checkBlankInSpan(line, items.length);
|
|
404
|
+
items.push(this.withinHeaderSpan(() => this.parseListItem(line, itemDepth)));
|
|
405
|
+
}
|
|
406
|
+
this.checkCount(items.length, header.length, 'list items');
|
|
407
|
+
return items;
|
|
408
|
+
}
|
|
409
|
+
isListItemLine(line) {
|
|
410
|
+
return line.content === '-' || line.content.startsWith('- ');
|
|
411
|
+
}
|
|
412
|
+
parseListItem(line, itemDepth) {
|
|
413
|
+
if (line.content === '-') {
|
|
414
|
+
this.pos++;
|
|
415
|
+
return {};
|
|
416
|
+
}
|
|
417
|
+
const rest = line.content.slice(2);
|
|
418
|
+
if (rest === '[]') {
|
|
419
|
+
this.pos++;
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
const inner = {
|
|
423
|
+
content: rest,
|
|
424
|
+
indent: line.indent + 2,
|
|
425
|
+
lineNumber: line.lineNumber,
|
|
426
|
+
isEmpty: false,
|
|
427
|
+
blankBefore: false,
|
|
428
|
+
};
|
|
429
|
+
const header = this.tryParseHeader(inner);
|
|
430
|
+
if (header !== null) {
|
|
431
|
+
this.pos++;
|
|
432
|
+
if (header.key === null) {
|
|
433
|
+
if (header.fields !== null && this.options.strict) {
|
|
434
|
+
throw new types_1.ToonDecodingError('Keyless fields-bearing header is not valid as a list item', { lineNumber: line.lineNumber, line: line.content });
|
|
445
435
|
}
|
|
436
|
+
return this.parseArrayContent(header, itemDepth + 1);
|
|
437
|
+
}
|
|
438
|
+
const obj = {};
|
|
439
|
+
utils.setDecodedKey(obj, header.key, header.keyed
|
|
440
|
+
? this.parseKeyedTabularBody(header, itemDepth + 2)
|
|
441
|
+
: this.parseArrayContent(header, itemDepth + 2));
|
|
442
|
+
this.parseListItemRestFields(obj, itemDepth + 1);
|
|
443
|
+
return obj;
|
|
444
|
+
}
|
|
445
|
+
const colonIndex = utils.findUnquoted(rest, ':');
|
|
446
|
+
if (colonIndex >= 0) {
|
|
447
|
+
const key = this.decodeKeyToken(rest.slice(0, colonIndex), line);
|
|
448
|
+
const valuePart = utils.trimSpaces(rest.slice(colonIndex + 1));
|
|
449
|
+
this.pos++;
|
|
450
|
+
const obj = {};
|
|
451
|
+
if (valuePart === '[]') {
|
|
452
|
+
utils.setDecodedKey(obj, key, []);
|
|
453
|
+
}
|
|
454
|
+
else if (valuePart !== '') {
|
|
455
|
+
utils.setDecodedKey(obj, key, this.parseValueToken(valuePart, line));
|
|
446
456
|
}
|
|
447
457
|
else {
|
|
448
|
-
|
|
449
|
-
obj[key] = [];
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
obj[key] = this.parseTokenValue(valuePart);
|
|
453
|
-
}
|
|
454
|
-
currentIndex++;
|
|
458
|
+
utils.setDecodedKey(obj, key, this.parseNestedScope(itemDepth + 1));
|
|
455
459
|
}
|
|
460
|
+
this.parseListItemRestFields(obj, itemDepth + 1);
|
|
461
|
+
return obj;
|
|
456
462
|
}
|
|
457
|
-
|
|
463
|
+
this.pos++;
|
|
464
|
+
return this.parseValueToken(rest, line);
|
|
458
465
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
isInvalidArrayHeader(content) {
|
|
465
|
-
return /\[\d+[\t|]?\]/.test(content) && !this.isValidArrayHeader(content);
|
|
466
|
-
}
|
|
467
|
-
findUnquotedColon(text) {
|
|
468
|
-
let inQuotes = false;
|
|
469
|
-
let escaped = false;
|
|
470
|
-
for (let i = 0; i < text.length; i++) {
|
|
471
|
-
const char = text[i];
|
|
472
|
-
if (escaped) {
|
|
473
|
-
escaped = false;
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
if (char === '\\') {
|
|
477
|
-
escaped = true;
|
|
478
|
-
continue;
|
|
466
|
+
parseListItemRestFields(obj, fieldDepth) {
|
|
467
|
+
while (this.pos < this.lines.length) {
|
|
468
|
+
const line = this.lines[this.pos];
|
|
469
|
+
if (this.depthOf(line) !== fieldDepth || this.isListItemLine(line)) {
|
|
470
|
+
break;
|
|
479
471
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
472
|
+
this.checkBlankInSpan(line, 1);
|
|
473
|
+
const { key, value } = this.parseFieldLine(line, fieldDepth);
|
|
474
|
+
this.assignKey(obj, key, line);
|
|
475
|
+
utils.setDecodedKey(obj, key, value);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
checkCount(actual, declared, what) {
|
|
479
|
+
if (this.options.strict && actual !== declared) {
|
|
480
|
+
throw new types_1.ToonDecodingError(`Count mismatch for ${what}: expected ${declared}, got ${actual}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
checkWidth(actual, expected, line) {
|
|
484
|
+
if (this.options.strict && actual !== expected) {
|
|
485
|
+
throw new types_1.ToonDecodingError(`Row width mismatch: expected ${expected} cells, got ${actual}`, { lineNumber: line.lineNumber, line: line.content });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
decodeKeyToken(token, line) {
|
|
489
|
+
const trimmed = utils.trimSpaces(token);
|
|
490
|
+
if (trimmed.startsWith('"')) {
|
|
491
|
+
if (!trimmed.endsWith('"') || trimmed.length < 2) {
|
|
492
|
+
throw new types_1.ToonDecodingError('Unterminated quoted key', {
|
|
493
|
+
lineNumber: line.lineNumber,
|
|
494
|
+
line: line.content,
|
|
495
|
+
});
|
|
483
496
|
}
|
|
484
|
-
|
|
485
|
-
|
|
497
|
+
const unescaped = utils.tryUnescapeString(trimmed.slice(1, -1));
|
|
498
|
+
if (unescaped.error !== null) {
|
|
499
|
+
throw new types_1.ToonDecodingError(unescaped.error, {
|
|
500
|
+
lineNumber: line.lineNumber,
|
|
501
|
+
line: line.content,
|
|
502
|
+
});
|
|
486
503
|
}
|
|
504
|
+
return unescaped.value;
|
|
487
505
|
}
|
|
488
|
-
return
|
|
506
|
+
return trimmed;
|
|
489
507
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const
|
|
494
|
-
if (
|
|
495
|
-
|
|
496
|
-
|
|
508
|
+
parseValueToken(token, line) {
|
|
509
|
+
const trimmed = utils.trimSpaces(token);
|
|
510
|
+
if (trimmed.startsWith('"')) {
|
|
511
|
+
const closing = this.findClosingQuote(trimmed);
|
|
512
|
+
if (closing < 0) {
|
|
513
|
+
throw new types_1.ToonDecodingError('Unterminated quoted string', {
|
|
514
|
+
lineNumber: line === null || line === void 0 ? void 0 : line.lineNumber,
|
|
515
|
+
line: line === null || line === void 0 ? void 0 : line.content,
|
|
516
|
+
});
|
|
497
517
|
}
|
|
498
|
-
if (
|
|
499
|
-
|
|
518
|
+
if (closing !== trimmed.length - 1) {
|
|
519
|
+
throw new types_1.ToonDecodingError('Characters after closing quote', {
|
|
520
|
+
lineNumber: line === null || line === void 0 ? void 0 : line.lineNumber,
|
|
521
|
+
line: line === null || line === void 0 ? void 0 : line.content,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
const unescaped = utils.tryUnescapeString(trimmed.slice(1, -1));
|
|
525
|
+
if (unescaped.error !== null) {
|
|
526
|
+
throw new types_1.ToonDecodingError(unescaped.error, {
|
|
527
|
+
lineNumber: line === null || line === void 0 ? void 0 : line.lineNumber,
|
|
528
|
+
line: line === null || line === void 0 ? void 0 : line.content,
|
|
529
|
+
});
|
|
500
530
|
}
|
|
501
|
-
|
|
531
|
+
return unescaped.value;
|
|
502
532
|
}
|
|
503
|
-
return
|
|
533
|
+
return utils.parseToken(trimmed);
|
|
504
534
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
535
|
+
findClosingQuote(token) {
|
|
536
|
+
let i = 1;
|
|
537
|
+
while (i < token.length) {
|
|
538
|
+
if (token[i] === '\\') {
|
|
539
|
+
i += 2;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (token[i] === '"') {
|
|
543
|
+
return i;
|
|
544
|
+
}
|
|
545
|
+
i++;
|
|
509
546
|
}
|
|
510
|
-
return
|
|
547
|
+
return -1;
|
|
511
548
|
}
|
|
512
549
|
expandPaths(obj) {
|
|
513
550
|
const result = {};
|