@antv/infographic 0.1.3 → 0.2.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 +54 -40
- package/README.zh-CN.md +52 -36
- package/dist/infographic.min.js +110 -105
- package/dist/infographic.min.js.map +1 -1
- package/esm/constants/element.d.ts +1 -1
- package/esm/constants/index.d.ts +1 -0
- package/esm/constants/index.js +1 -0
- package/esm/constants/service.d.ts +1 -0
- package/esm/constants/service.js +1 -0
- package/esm/designs/components/Illus.js +1 -1
- package/esm/designs/structures/chart-wordcloud.d.ts +11 -0
- package/esm/designs/structures/chart-wordcloud.js +156 -0
- package/esm/designs/structures/hierarchy-tree.d.ts +2 -0
- package/esm/designs/structures/hierarchy-tree.js +179 -50
- package/esm/designs/structures/index.d.ts +2 -0
- package/esm/designs/structures/index.js +2 -0
- package/esm/designs/structures/sequence-stairs-front.d.ts +8 -0
- package/esm/designs/structures/sequence-stairs-front.js +116 -0
- package/esm/designs/types.d.ts +8 -0
- package/esm/editor/managers/state.js +1 -1
- package/esm/exporter/font.js +4 -9
- package/esm/index.d.ts +2 -0
- package/esm/index.js +1 -0
- package/esm/options/parser.d.ts +1 -1
- package/esm/options/parser.js +33 -15
- package/esm/renderer/composites/icon.js +1 -1
- package/esm/renderer/composites/illus.js +1 -1
- package/esm/resource/loader.d.ts +2 -2
- package/esm/resource/loader.js +22 -11
- package/esm/resource/loaders/index.d.ts +1 -0
- package/esm/resource/loaders/index.js +1 -0
- package/esm/resource/loaders/remote.d.ts +1 -1
- package/esm/resource/loaders/remote.js +12 -3
- package/esm/resource/loaders/search.d.ts +1 -0
- package/esm/resource/loaders/search.js +52 -0
- package/esm/resource/types/index.d.ts +1 -0
- package/esm/resource/types/resource.d.ts +8 -1
- package/esm/resource/types/scene.d.ts +1 -0
- package/esm/resource/utils/data-uri.js +20 -11
- package/esm/resource/utils/parser.js +92 -1
- package/esm/resource/utils/ref.js +2 -2
- package/esm/runtime/Infographic.d.ts +10 -6
- package/esm/runtime/Infographic.js +66 -17
- package/esm/runtime/utils.d.ts +4 -2
- package/esm/runtime/utils.js +33 -13
- package/esm/syntax/index.d.ts +3 -0
- package/esm/syntax/index.js +101 -0
- package/esm/syntax/mapper.d.ts +3 -0
- package/esm/syntax/mapper.js +334 -0
- package/esm/syntax/parser.d.ts +14 -0
- package/esm/syntax/parser.js +142 -0
- package/esm/syntax/schema.d.ts +6 -0
- package/esm/syntax/schema.js +86 -0
- package/esm/syntax/types.d.ts +68 -0
- package/esm/syntax/types.js +1 -0
- package/esm/templates/built-in.js +4 -0
- package/esm/templates/hierarchy-tree.js +25 -11
- package/esm/templates/sequence-stairs.d.ts +2 -0
- package/esm/templates/sequence-stairs.js +42 -0
- package/esm/templates/word-cloud.d.ts +2 -0
- package/esm/templates/word-cloud.js +19 -0
- package/esm/themes/types.d.ts +1 -1
- package/esm/utils/design.d.ts +2 -0
- package/esm/utils/design.js +10 -0
- package/esm/utils/fetch.d.ts +1 -0
- package/esm/utils/fetch.js +61 -0
- package/esm/utils/font.js +11 -1
- package/esm/utils/index.d.ts +2 -0
- package/esm/utils/index.js +2 -0
- package/lib/constants/element.d.ts +1 -1
- package/lib/constants/index.d.ts +1 -0
- package/lib/constants/index.js +1 -0
- package/lib/constants/service.d.ts +1 -0
- package/lib/constants/service.js +4 -0
- package/lib/designs/components/Illus.js +1 -1
- package/lib/designs/structures/chart-wordcloud.d.ts +11 -0
- package/lib/designs/structures/chart-wordcloud.js +160 -0
- package/lib/designs/structures/hierarchy-tree.d.ts +2 -0
- package/lib/designs/structures/hierarchy-tree.js +179 -50
- package/lib/designs/structures/index.d.ts +2 -0
- package/lib/designs/structures/index.js +2 -0
- package/lib/designs/structures/sequence-stairs-front.d.ts +8 -0
- package/lib/designs/structures/sequence-stairs-front.js +120 -0
- package/lib/designs/types.d.ts +8 -0
- package/lib/editor/managers/state.js +1 -1
- package/lib/exporter/font.js +3 -8
- package/lib/index.d.ts +2 -0
- package/lib/index.js +4 -1
- package/lib/options/parser.d.ts +1 -1
- package/lib/options/parser.js +32 -14
- package/lib/renderer/composites/icon.js +1 -1
- package/lib/renderer/composites/illus.js +1 -1
- package/lib/resource/loader.d.ts +2 -2
- package/lib/resource/loader.js +21 -10
- package/lib/resource/loaders/index.d.ts +1 -0
- package/lib/resource/loaders/index.js +1 -0
- package/lib/resource/loaders/remote.d.ts +1 -1
- package/lib/resource/loaders/remote.js +12 -3
- package/lib/resource/loaders/search.d.ts +1 -0
- package/lib/resource/loaders/search.js +55 -0
- package/lib/resource/types/index.d.ts +1 -0
- package/lib/resource/types/resource.d.ts +8 -1
- package/lib/resource/types/scene.d.ts +1 -0
- package/lib/resource/utils/data-uri.js +20 -11
- package/lib/resource/utils/parser.js +92 -1
- package/lib/resource/utils/ref.js +2 -2
- package/lib/runtime/Infographic.d.ts +10 -6
- package/lib/runtime/Infographic.js +65 -16
- package/lib/runtime/utils.d.ts +4 -2
- package/lib/runtime/utils.js +35 -13
- package/lib/syntax/index.d.ts +3 -0
- package/lib/syntax/index.js +104 -0
- package/lib/syntax/mapper.d.ts +3 -0
- package/lib/syntax/mapper.js +341 -0
- package/lib/syntax/parser.d.ts +14 -0
- package/lib/syntax/parser.js +146 -0
- package/lib/syntax/schema.d.ts +6 -0
- package/lib/syntax/schema.js +89 -0
- package/lib/syntax/types.d.ts +68 -0
- package/lib/syntax/types.js +2 -0
- package/lib/templates/built-in.js +4 -0
- package/lib/templates/hierarchy-tree.js +25 -11
- package/lib/templates/sequence-stairs.d.ts +2 -0
- package/lib/templates/sequence-stairs.js +45 -0
- package/lib/templates/word-cloud.d.ts +2 -0
- package/lib/templates/word-cloud.js +22 -0
- package/lib/themes/types.d.ts +1 -1
- package/lib/utils/design.d.ts +2 -0
- package/lib/utils/design.js +13 -0
- package/lib/utils/fetch.d.ts +1 -0
- package/lib/utils/fetch.js +67 -0
- package/lib/utils/font.js +11 -1
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/index.js +2 -0
- package/package.json +14 -2
- package/src/constants/element.ts +1 -1
- package/src/constants/index.ts +1 -0
- package/src/constants/service.ts +1 -0
- package/src/designs/components/Illus.tsx +1 -1
- package/src/designs/structures/chart-wordcloud.tsx +278 -0
- package/src/designs/structures/hierarchy-tree.tsx +212 -59
- package/src/designs/structures/index.ts +2 -0
- package/src/designs/structures/sequence-stairs-front.tsx +291 -0
- package/src/designs/types.ts +9 -0
- package/src/editor/managers/state.ts +1 -1
- package/src/exporter/font.ts +4 -9
- package/src/index.ts +2 -0
- package/src/options/parser.ts +57 -28
- package/src/renderer/composites/icon.ts +1 -1
- package/src/renderer/composites/illus.ts +1 -1
- package/src/resource/loader.ts +22 -8
- package/src/resource/loaders/index.ts +1 -0
- package/src/resource/loaders/remote.ts +11 -3
- package/src/resource/loaders/search.ts +53 -0
- package/src/resource/types/index.ts +2 -1
- package/src/resource/types/resource.ts +12 -1
- package/src/resource/types/scene.ts +1 -0
- package/src/resource/utils/data-uri.ts +20 -11
- package/src/resource/utils/parser.ts +103 -2
- package/src/resource/utils/ref.ts +2 -2
- package/src/runtime/Infographic.tsx +103 -22
- package/src/runtime/utils.ts +38 -16
- package/src/syntax/index.ts +124 -0
- package/src/syntax/mapper.ts +496 -0
- package/src/syntax/parser.ts +171 -0
- package/src/syntax/schema.ts +112 -0
- package/src/syntax/types.ts +100 -0
- package/src/templates/built-in.ts +4 -0
- package/src/templates/hierarchy-tree.ts +34 -11
- package/src/templates/sequence-stairs.ts +44 -0
- package/src/templates/word-cloud.ts +21 -0
- package/src/themes/types.ts +1 -1
- package/src/utils/design.ts +14 -0
- package/src/utils/fetch.ts +90 -0
- package/src/utils/font.ts +11 -1
- package/src/utils/index.ts +2 -0
- package/esm/resource/types/font.d.ts +0 -12
- package/lib/resource/types/font.d.ts +0 -12
- package/src/resource/types/font.ts +0 -23
- /package/esm/resource/types/{font.js → scene.js} +0 -0
- /package/lib/resource/types/{font.js → scene.js} +0 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import tinycolor from 'tinycolor2';
|
|
2
|
+
import { parseInlineKeyValue } from './parser';
|
|
3
|
+
import type {
|
|
4
|
+
SchemaNode,
|
|
5
|
+
SyntaxError,
|
|
6
|
+
SyntaxNode,
|
|
7
|
+
UnionSchema,
|
|
8
|
+
ValueNode,
|
|
9
|
+
} from './types';
|
|
10
|
+
|
|
11
|
+
function createValueNode(value: string, line: number): ValueNode {
|
|
12
|
+
return { kind: 'value', line, value };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const HEX_COLOR_PATTERN =
|
|
16
|
+
/^(#[0-9a-f]{8}|#[0-9a-f]{6}|#[0-9a-f]{4}|#[0-9a-f]{3})/i;
|
|
17
|
+
const FUNCTION_COLOR_PATTERN = /^((?:rgb|rgba|hsl|hsla)\([^)]*\))/i;
|
|
18
|
+
|
|
19
|
+
function parseScalar(value: string) {
|
|
20
|
+
const trimmed = value.trim();
|
|
21
|
+
if (trimmed === 'true') return true;
|
|
22
|
+
if (trimmed === 'false') return false;
|
|
23
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) return parseFloat(trimmed);
|
|
24
|
+
return trimmed;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readScalar(node: SyntaxNode): string | undefined {
|
|
28
|
+
if (node.kind === 'value') return node.value;
|
|
29
|
+
if (node.kind === 'object') return node.value;
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function addError(
|
|
34
|
+
errors: SyntaxError[],
|
|
35
|
+
node: SyntaxNode,
|
|
36
|
+
path: string,
|
|
37
|
+
code: SyntaxError['code'],
|
|
38
|
+
message: string,
|
|
39
|
+
raw?: string,
|
|
40
|
+
) {
|
|
41
|
+
errors.push({
|
|
42
|
+
path,
|
|
43
|
+
line: node.line,
|
|
44
|
+
code,
|
|
45
|
+
message,
|
|
46
|
+
raw,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function splitArrayValue(
|
|
51
|
+
value: string,
|
|
52
|
+
split: 'space' | 'comma' | 'any' = 'any',
|
|
53
|
+
) {
|
|
54
|
+
let text = value.trim();
|
|
55
|
+
if (text.startsWith('[') && text.endsWith(']')) {
|
|
56
|
+
text = text.slice(1, -1).trim();
|
|
57
|
+
}
|
|
58
|
+
if (!text) return [];
|
|
59
|
+
let parts: string[];
|
|
60
|
+
if (split === 'comma') {
|
|
61
|
+
parts = text.split(',');
|
|
62
|
+
} else if (split === 'space') {
|
|
63
|
+
parts = text.split(/\s+/);
|
|
64
|
+
} else {
|
|
65
|
+
parts = text.split(/[,\s]+/);
|
|
66
|
+
}
|
|
67
|
+
const normalized: string[] = [];
|
|
68
|
+
for (const part of parts) {
|
|
69
|
+
const trimmedPart = part.trim();
|
|
70
|
+
if (!trimmedPart) continue;
|
|
71
|
+
if (trimmedPart === '#' || trimmedPart === '//') break;
|
|
72
|
+
normalized.push(trimmedPart);
|
|
73
|
+
}
|
|
74
|
+
return normalized;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function stripColorComments(value: string) {
|
|
78
|
+
let trimmed = value.trim();
|
|
79
|
+
const hashIndex = trimmed.search(/\s+#(?![0-9a-f])/i);
|
|
80
|
+
if (hashIndex >= 0) {
|
|
81
|
+
trimmed = trimmed.slice(0, hashIndex).trimEnd();
|
|
82
|
+
}
|
|
83
|
+
const slashIndex = trimmed.indexOf('//');
|
|
84
|
+
if (slashIndex >= 0) {
|
|
85
|
+
trimmed = trimmed.slice(0, slashIndex).trimEnd();
|
|
86
|
+
}
|
|
87
|
+
return trimmed;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function normalizeExplicitColor(value: string) {
|
|
91
|
+
const trimmed = stripColorComments(value);
|
|
92
|
+
if (!trimmed) return undefined;
|
|
93
|
+
const hexMatch = trimmed.match(HEX_COLOR_PATTERN);
|
|
94
|
+
if (hexMatch && hexMatch[0].length === trimmed.length) {
|
|
95
|
+
return trimmed;
|
|
96
|
+
}
|
|
97
|
+
const funcMatch = trimmed.match(FUNCTION_COLOR_PATTERN);
|
|
98
|
+
if (funcMatch && funcMatch[1].length === trimmed.length) {
|
|
99
|
+
return trimmed;
|
|
100
|
+
}
|
|
101
|
+
if (tinycolor(trimmed).isValid()) return trimmed;
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function mapColor(
|
|
106
|
+
node: SyntaxNode,
|
|
107
|
+
path: string,
|
|
108
|
+
errors: SyntaxError[],
|
|
109
|
+
options: { soft?: boolean } = {},
|
|
110
|
+
) {
|
|
111
|
+
const value = readScalar(node);
|
|
112
|
+
if (value === undefined) {
|
|
113
|
+
addError(errors, node, path, 'schema_mismatch', 'Expected color value.');
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const normalized = normalizeExplicitColor(value);
|
|
117
|
+
if (!normalized) {
|
|
118
|
+
if (!options.soft) {
|
|
119
|
+
addError(
|
|
120
|
+
errors,
|
|
121
|
+
node,
|
|
122
|
+
path,
|
|
123
|
+
'invalid_value',
|
|
124
|
+
'Invalid color value.',
|
|
125
|
+
value,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
return normalized;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function shouldTreatPaletteStringAsArray(
|
|
134
|
+
trimmed: string,
|
|
135
|
+
parts: string[],
|
|
136
|
+
): boolean {
|
|
137
|
+
if (parts.length > 1) return true;
|
|
138
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) return true;
|
|
139
|
+
return normalizeExplicitColor(trimmed) !== undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function mapUnknown(node: SyntaxNode): any {
|
|
143
|
+
if (node.kind === 'array') {
|
|
144
|
+
return node.items.map((item) => mapUnknown(item));
|
|
145
|
+
}
|
|
146
|
+
if (node.kind === 'value') return parseScalar(node.value);
|
|
147
|
+
const hasEntries = Object.keys(node.entries).length > 0;
|
|
148
|
+
if (!hasEntries && node.value !== undefined) {
|
|
149
|
+
return parseScalar(node.value);
|
|
150
|
+
}
|
|
151
|
+
const result: Record<string, any> = {};
|
|
152
|
+
if (node.value !== undefined) {
|
|
153
|
+
result.value = parseScalar(node.value);
|
|
154
|
+
}
|
|
155
|
+
Object.entries(node.entries).forEach(([key, child]) => {
|
|
156
|
+
result[key] = mapUnknown(child);
|
|
157
|
+
});
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function mapUnion(
|
|
162
|
+
node: SyntaxNode,
|
|
163
|
+
schema: UnionSchema,
|
|
164
|
+
path: string,
|
|
165
|
+
errors: SyntaxError[],
|
|
166
|
+
) {
|
|
167
|
+
let bestValue: any = undefined;
|
|
168
|
+
let bestErrors: SyntaxError[] | null = null;
|
|
169
|
+
for (const variant of schema.variants) {
|
|
170
|
+
const variantErrors: SyntaxError[] = [];
|
|
171
|
+
const value = mapWithSchema(node, variant, path, variantErrors);
|
|
172
|
+
if (bestErrors === null || variantErrors.length < bestErrors.length) {
|
|
173
|
+
bestErrors = variantErrors;
|
|
174
|
+
bestValue = value;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (bestErrors) errors.push(...bestErrors);
|
|
178
|
+
return bestValue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function mapWithSchema(
|
|
182
|
+
node: SyntaxNode,
|
|
183
|
+
schema: SchemaNode,
|
|
184
|
+
path: string,
|
|
185
|
+
errors: SyntaxError[],
|
|
186
|
+
): any {
|
|
187
|
+
switch (schema.kind) {
|
|
188
|
+
case 'union':
|
|
189
|
+
return mapUnion(node, schema, path, errors);
|
|
190
|
+
case 'string': {
|
|
191
|
+
const value = readScalar(node);
|
|
192
|
+
if (value === undefined) {
|
|
193
|
+
addError(
|
|
194
|
+
errors,
|
|
195
|
+
node,
|
|
196
|
+
path,
|
|
197
|
+
'schema_mismatch',
|
|
198
|
+
'Expected string value.',
|
|
199
|
+
);
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
case 'number': {
|
|
205
|
+
const value = readScalar(node);
|
|
206
|
+
if (value === undefined) {
|
|
207
|
+
addError(
|
|
208
|
+
errors,
|
|
209
|
+
node,
|
|
210
|
+
path,
|
|
211
|
+
'schema_mismatch',
|
|
212
|
+
'Expected number value.',
|
|
213
|
+
);
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
const trimmed = value.trim();
|
|
217
|
+
const match = trimmed.match(/^(-?\d+(\.\d+)?)(?:\s*(#|\/\/).*)?$/);
|
|
218
|
+
if (!match) {
|
|
219
|
+
addError(
|
|
220
|
+
errors,
|
|
221
|
+
node,
|
|
222
|
+
path,
|
|
223
|
+
'invalid_value',
|
|
224
|
+
'Invalid number value.',
|
|
225
|
+
value,
|
|
226
|
+
);
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
return parseFloat(match[1]);
|
|
230
|
+
}
|
|
231
|
+
case 'boolean': {
|
|
232
|
+
const value = readScalar(node);
|
|
233
|
+
if (value === undefined) {
|
|
234
|
+
addError(
|
|
235
|
+
errors,
|
|
236
|
+
node,
|
|
237
|
+
path,
|
|
238
|
+
'schema_mismatch',
|
|
239
|
+
'Expected boolean value.',
|
|
240
|
+
);
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
if (value !== 'true' && value !== 'false') {
|
|
244
|
+
addError(
|
|
245
|
+
errors,
|
|
246
|
+
node,
|
|
247
|
+
path,
|
|
248
|
+
'invalid_value',
|
|
249
|
+
'Invalid boolean value.',
|
|
250
|
+
value,
|
|
251
|
+
);
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
return value === 'true';
|
|
255
|
+
}
|
|
256
|
+
case 'enum': {
|
|
257
|
+
const value = readScalar(node);
|
|
258
|
+
if (value === undefined) {
|
|
259
|
+
addError(errors, node, path, 'schema_mismatch', 'Expected enum value.');
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
if (!schema.values.includes(value)) {
|
|
263
|
+
addError(
|
|
264
|
+
errors,
|
|
265
|
+
node,
|
|
266
|
+
path,
|
|
267
|
+
'invalid_value',
|
|
268
|
+
'Invalid enum value.',
|
|
269
|
+
value,
|
|
270
|
+
);
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
return value;
|
|
274
|
+
}
|
|
275
|
+
case 'array': {
|
|
276
|
+
if (node.kind === 'array') {
|
|
277
|
+
return node.items
|
|
278
|
+
.map((item, index) =>
|
|
279
|
+
mapWithSchema(item, schema.item, `${path}[${index}]`, errors),
|
|
280
|
+
)
|
|
281
|
+
.filter((value) => value !== undefined);
|
|
282
|
+
}
|
|
283
|
+
if (node.kind === 'object' && Object.keys(node.entries).length > 0) {
|
|
284
|
+
addError(
|
|
285
|
+
errors,
|
|
286
|
+
node,
|
|
287
|
+
path,
|
|
288
|
+
'schema_mismatch',
|
|
289
|
+
'Expected array value.',
|
|
290
|
+
);
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
const scalar = readScalar(node);
|
|
294
|
+
if (scalar === undefined) {
|
|
295
|
+
addError(
|
|
296
|
+
errors,
|
|
297
|
+
node,
|
|
298
|
+
path,
|
|
299
|
+
'schema_mismatch',
|
|
300
|
+
'Expected array value.',
|
|
301
|
+
);
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
const trimmed = scalar.trim();
|
|
305
|
+
if (trimmed.startsWith('[') && !trimmed.endsWith(']')) {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
const parts = splitArrayValue(scalar, schema.split);
|
|
309
|
+
return parts
|
|
310
|
+
.map((part, index) =>
|
|
311
|
+
mapWithSchema(
|
|
312
|
+
createValueNode(part, node.line),
|
|
313
|
+
schema.item,
|
|
314
|
+
`${path}[${index}]`,
|
|
315
|
+
errors,
|
|
316
|
+
),
|
|
317
|
+
)
|
|
318
|
+
.filter((value) => value !== undefined);
|
|
319
|
+
}
|
|
320
|
+
case 'palette': {
|
|
321
|
+
if (node.kind === 'array') {
|
|
322
|
+
const values = mapWithSchema(
|
|
323
|
+
node,
|
|
324
|
+
{ kind: 'array', item: { kind: 'color' }, split: 'any' },
|
|
325
|
+
path,
|
|
326
|
+
errors,
|
|
327
|
+
);
|
|
328
|
+
return Array.isArray(values) && values.length > 0 ? values : undefined;
|
|
329
|
+
}
|
|
330
|
+
if (node.kind === 'object' && Object.keys(node.entries).length > 0) {
|
|
331
|
+
addError(
|
|
332
|
+
errors,
|
|
333
|
+
node,
|
|
334
|
+
path,
|
|
335
|
+
'schema_mismatch',
|
|
336
|
+
'Expected palette value.',
|
|
337
|
+
);
|
|
338
|
+
return undefined;
|
|
339
|
+
}
|
|
340
|
+
const scalar = readScalar(node);
|
|
341
|
+
if (scalar === undefined) {
|
|
342
|
+
addError(
|
|
343
|
+
errors,
|
|
344
|
+
node,
|
|
345
|
+
path,
|
|
346
|
+
'schema_mismatch',
|
|
347
|
+
'Expected palette value.',
|
|
348
|
+
);
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
351
|
+
const trimmed = scalar.trim();
|
|
352
|
+
if (trimmed.startsWith('[') && !trimmed.endsWith(']')) {
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
const directColor = normalizeExplicitColor(trimmed);
|
|
356
|
+
if (directColor) {
|
|
357
|
+
return [directColor];
|
|
358
|
+
}
|
|
359
|
+
const parts = splitArrayValue(scalar, 'any');
|
|
360
|
+
if (shouldTreatPaletteStringAsArray(trimmed, parts)) {
|
|
361
|
+
const values = parts
|
|
362
|
+
.map((part, index) =>
|
|
363
|
+
mapColor(
|
|
364
|
+
createValueNode(part, node.line),
|
|
365
|
+
`${path}[${index}]`,
|
|
366
|
+
errors,
|
|
367
|
+
),
|
|
368
|
+
)
|
|
369
|
+
.filter((value): value is string => value !== undefined);
|
|
370
|
+
return values.length > 0 ? values : undefined;
|
|
371
|
+
}
|
|
372
|
+
return scalar;
|
|
373
|
+
}
|
|
374
|
+
case 'color': {
|
|
375
|
+
return mapColor(node, path, errors, { soft: schema.soft });
|
|
376
|
+
}
|
|
377
|
+
case 'object': {
|
|
378
|
+
if (node.kind === 'array') {
|
|
379
|
+
addError(
|
|
380
|
+
errors,
|
|
381
|
+
node,
|
|
382
|
+
path,
|
|
383
|
+
'schema_mismatch',
|
|
384
|
+
'Expected object value.',
|
|
385
|
+
);
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
const result: Record<string, any> = {};
|
|
389
|
+
if (node.kind === 'value') {
|
|
390
|
+
if (schema.shorthandKey) {
|
|
391
|
+
result[schema.shorthandKey] = node.value;
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
const inline = parseInlineKeyValue(node.value);
|
|
395
|
+
if (inline?.value !== undefined) {
|
|
396
|
+
if (schema.fields[inline.key]) {
|
|
397
|
+
const value = mapWithSchema(
|
|
398
|
+
createValueNode(inline.value, node.line),
|
|
399
|
+
schema.fields[inline.key],
|
|
400
|
+
`${path}.${inline.key}`,
|
|
401
|
+
errors,
|
|
402
|
+
);
|
|
403
|
+
if (value !== undefined) result[inline.key] = value;
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
if (schema.allowUnknown) {
|
|
407
|
+
result[inline.key] = parseScalar(inline.value);
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
410
|
+
addError(
|
|
411
|
+
errors,
|
|
412
|
+
node,
|
|
413
|
+
`${path}.${inline.key}`,
|
|
414
|
+
'unknown_key',
|
|
415
|
+
'Unknown key in object.',
|
|
416
|
+
inline.key,
|
|
417
|
+
);
|
|
418
|
+
return result;
|
|
419
|
+
}
|
|
420
|
+
if (schema.allowUnknown) {
|
|
421
|
+
result.value = parseScalar(node.value);
|
|
422
|
+
return result;
|
|
423
|
+
}
|
|
424
|
+
addError(errors, node, path, 'invalid_value', 'Expected object value.');
|
|
425
|
+
return undefined;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (node.value !== undefined) {
|
|
429
|
+
if (schema.shorthandKey && result[schema.shorthandKey] === undefined) {
|
|
430
|
+
result[schema.shorthandKey] = node.value;
|
|
431
|
+
} else {
|
|
432
|
+
const inline = parseInlineKeyValue(node.value);
|
|
433
|
+
if (inline?.value !== undefined) {
|
|
434
|
+
if (schema.fields[inline.key]) {
|
|
435
|
+
const value = mapWithSchema(
|
|
436
|
+
createValueNode(inline.value, node.line),
|
|
437
|
+
schema.fields[inline.key],
|
|
438
|
+
`${path}.${inline.key}`,
|
|
439
|
+
errors,
|
|
440
|
+
);
|
|
441
|
+
if (value !== undefined && result[inline.key] === undefined) {
|
|
442
|
+
result[inline.key] = value;
|
|
443
|
+
}
|
|
444
|
+
} else if (schema.allowUnknown) {
|
|
445
|
+
result[inline.key] = parseScalar(inline.value);
|
|
446
|
+
} else {
|
|
447
|
+
addError(
|
|
448
|
+
errors,
|
|
449
|
+
node,
|
|
450
|
+
`${path}.${inline.key}`,
|
|
451
|
+
'unknown_key',
|
|
452
|
+
'Unknown key in object.',
|
|
453
|
+
inline.key,
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
} else if (schema.allowUnknown) {
|
|
457
|
+
result.value = parseScalar(node.value);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
Object.entries(node.entries).forEach(([key, child]) => {
|
|
463
|
+
const fieldSchema = schema.fields[key];
|
|
464
|
+
if (!fieldSchema) {
|
|
465
|
+
if (schema.allowUnknown) {
|
|
466
|
+
result[key] = mapUnknown(child);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
addError(
|
|
470
|
+
errors,
|
|
471
|
+
child,
|
|
472
|
+
`${path}.${key}`,
|
|
473
|
+
'unknown_key',
|
|
474
|
+
'Unknown key in object.',
|
|
475
|
+
key,
|
|
476
|
+
);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
const value = mapWithSchema(
|
|
480
|
+
child,
|
|
481
|
+
fieldSchema,
|
|
482
|
+
`${path}.${key}`,
|
|
483
|
+
errors,
|
|
484
|
+
);
|
|
485
|
+
if (value !== undefined) result[key] = value;
|
|
486
|
+
});
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
default:
|
|
490
|
+
return undefined;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function mapUnknownToObject(node: SyntaxNode) {
|
|
495
|
+
return mapUnknown(node);
|
|
496
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { ArrayNode, ObjectNode, SyntaxError } from './types';
|
|
2
|
+
|
|
3
|
+
interface StackFrame {
|
|
4
|
+
indent: number;
|
|
5
|
+
node: ObjectNode | ArrayNode;
|
|
6
|
+
parent?: ObjectNode | ArrayNode | null;
|
|
7
|
+
key?: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ParseResult {
|
|
11
|
+
ast: ObjectNode;
|
|
12
|
+
errors: SyntaxError[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isWhitespace(char: string) {
|
|
16
|
+
return char === ' ' || char === '\t';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getIndentInfo(line: string) {
|
|
20
|
+
let indent = 0;
|
|
21
|
+
let index = 0;
|
|
22
|
+
while (index < line.length) {
|
|
23
|
+
const char = line[index];
|
|
24
|
+
if (char === ' ') {
|
|
25
|
+
indent += 1;
|
|
26
|
+
index += 1;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (char === '\t') {
|
|
30
|
+
indent += 2;
|
|
31
|
+
index += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
return { indent, content: line.slice(index) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function stripComments(content: string) {
|
|
40
|
+
return content.trimEnd();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseKeyValue(raw: string) {
|
|
44
|
+
const text = raw.trim();
|
|
45
|
+
if (!text) return null;
|
|
46
|
+
const match = text.match(/^([^:\s=]+)\s*[:=]\s*(.*)$/);
|
|
47
|
+
if (match) {
|
|
48
|
+
return { key: match[1], value: match[2].trim() };
|
|
49
|
+
}
|
|
50
|
+
const matchSpace = text.match(/^([^\s]+)\s+(.*)$/);
|
|
51
|
+
if (matchSpace) {
|
|
52
|
+
return { key: matchSpace[1], value: matchSpace[2].trim() };
|
|
53
|
+
}
|
|
54
|
+
return { key: text, value: undefined };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createObjectNode(line: number, value?: string): ObjectNode {
|
|
58
|
+
return { kind: 'object', line, value, entries: {} };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createArrayNode(line: number): ArrayNode {
|
|
62
|
+
return { kind: 'array', line, items: [] };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function parseSyntaxToAst(input: string): ParseResult {
|
|
66
|
+
const errors: SyntaxError[] = [];
|
|
67
|
+
const root: ObjectNode = createObjectNode(0);
|
|
68
|
+
const stack: StackFrame[] = [
|
|
69
|
+
{ indent: -1, node: root, parent: null, key: null },
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const lines = input.split(/\r?\n/);
|
|
73
|
+
lines.forEach((line, index) => {
|
|
74
|
+
const lineNumber = index + 1;
|
|
75
|
+
if (!line.trim()) return;
|
|
76
|
+
const { indent, content } = getIndentInfo(line);
|
|
77
|
+
const stripped = stripComments(content);
|
|
78
|
+
if (!stripped.trim()) return;
|
|
79
|
+
|
|
80
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
81
|
+
stack.pop();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const parentFrame = stack[stack.length - 1];
|
|
85
|
+
let parentNode = parentFrame.node;
|
|
86
|
+
|
|
87
|
+
const trimmed = stripped.trim();
|
|
88
|
+
if (
|
|
89
|
+
trimmed.startsWith('-') &&
|
|
90
|
+
(trimmed.length === 1 || isWhitespace(trimmed[1]))
|
|
91
|
+
) {
|
|
92
|
+
if (parentNode.kind !== 'array') {
|
|
93
|
+
if (
|
|
94
|
+
parentNode.kind === 'object' &&
|
|
95
|
+
Object.keys(parentNode.entries).length === 0 &&
|
|
96
|
+
parentNode.value === undefined &&
|
|
97
|
+
parentFrame.parent &&
|
|
98
|
+
parentFrame.key
|
|
99
|
+
) {
|
|
100
|
+
const arrayNode = createArrayNode(parentNode.line);
|
|
101
|
+
if (parentFrame.parent.kind === 'object') {
|
|
102
|
+
parentFrame.parent.entries[parentFrame.key] = arrayNode;
|
|
103
|
+
} else if (parentFrame.parent.kind === 'array') {
|
|
104
|
+
const indexInParent = parentFrame.parent.items.indexOf(parentNode);
|
|
105
|
+
if (indexInParent >= 0)
|
|
106
|
+
parentFrame.parent.items[indexInParent] = arrayNode;
|
|
107
|
+
}
|
|
108
|
+
parentFrame.node = arrayNode;
|
|
109
|
+
parentNode = arrayNode;
|
|
110
|
+
} else {
|
|
111
|
+
errors.push({
|
|
112
|
+
path: '',
|
|
113
|
+
line: lineNumber,
|
|
114
|
+
code: 'bad_list',
|
|
115
|
+
message: 'List item is not under an array container.',
|
|
116
|
+
raw: trimmed,
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const itemContent = trimmed.slice(1).trim();
|
|
123
|
+
const itemNode = createObjectNode(lineNumber, itemContent || undefined);
|
|
124
|
+
parentNode.items.push(itemNode);
|
|
125
|
+
stack.push({
|
|
126
|
+
indent,
|
|
127
|
+
node: itemNode,
|
|
128
|
+
parent: parentNode,
|
|
129
|
+
});
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const parsed = parseKeyValue(trimmed);
|
|
134
|
+
if (!parsed) {
|
|
135
|
+
errors.push({
|
|
136
|
+
path: '',
|
|
137
|
+
line: lineNumber,
|
|
138
|
+
code: 'bad_syntax',
|
|
139
|
+
message: 'Invalid syntax line.',
|
|
140
|
+
raw: trimmed,
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (parentNode.kind !== 'object') {
|
|
146
|
+
errors.push({
|
|
147
|
+
path: '',
|
|
148
|
+
line: lineNumber,
|
|
149
|
+
code: 'bad_syntax',
|
|
150
|
+
message: 'Key-value pair is not under an object container.',
|
|
151
|
+
raw: trimmed,
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const node = createObjectNode(lineNumber, parsed.value);
|
|
157
|
+
parentNode.entries[parsed.key] = node;
|
|
158
|
+
stack.push({
|
|
159
|
+
indent,
|
|
160
|
+
node,
|
|
161
|
+
parent: parentNode,
|
|
162
|
+
key: parsed.key,
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return { ast: root, errors };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function parseInlineKeyValue(value: string) {
|
|
170
|
+
return parseKeyValue(value);
|
|
171
|
+
}
|