@willwade/aac-processors 0.0.9 → 0.0.11
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 +85 -11
- package/dist/cli/index.js +87 -0
- package/dist/core/analyze.js +1 -0
- package/dist/core/baseProcessor.d.ts +6 -0
- package/dist/core/fileProcessor.js +1 -0
- package/dist/core/treeStructure.d.ts +3 -1
- package/dist/core/treeStructure.js +3 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/optional/symbolTools.js +4 -2
- package/dist/processors/gridset/colorUtils.d.ts +18 -0
- package/dist/processors/gridset/colorUtils.js +36 -0
- package/dist/processors/gridset/commands.d.ts +103 -0
- package/dist/processors/gridset/commands.js +958 -0
- package/dist/processors/gridset/helpers.d.ts +1 -1
- package/dist/processors/gridset/helpers.js +5 -3
- package/dist/processors/gridset/index.d.ts +45 -0
- package/dist/processors/gridset/index.js +153 -0
- package/dist/processors/gridset/password.d.ts +11 -0
- package/dist/processors/gridset/password.js +37 -0
- package/dist/processors/gridset/pluginTypes.d.ts +109 -0
- package/dist/processors/gridset/pluginTypes.js +285 -0
- package/dist/processors/gridset/resolver.d.ts +14 -1
- package/dist/processors/gridset/resolver.js +47 -5
- package/dist/processors/gridset/styleHelpers.d.ts +22 -0
- package/dist/processors/gridset/styleHelpers.js +35 -1
- package/dist/processors/gridset/symbolExtractor.d.ts +121 -0
- package/dist/processors/gridset/symbolExtractor.js +362 -0
- package/dist/processors/gridset/symbolSearch.d.ts +117 -0
- package/dist/processors/gridset/symbolSearch.js +280 -0
- package/dist/processors/gridset/symbols.d.ts +199 -0
- package/dist/processors/gridset/symbols.js +468 -0
- package/dist/processors/gridset/wordlistHelpers.d.ts +2 -2
- package/dist/processors/gridset/wordlistHelpers.js +7 -4
- package/dist/processors/gridsetProcessor.d.ts +15 -1
- package/dist/processors/gridsetProcessor.js +98 -22
- package/dist/processors/index.d.ts +10 -1
- package/dist/processors/index.js +94 -2
- package/dist/processors/obfProcessor.d.ts +7 -0
- package/dist/processors/obfProcessor.js +9 -0
- package/dist/processors/snapProcessor.d.ts +7 -0
- package/dist/processors/snapProcessor.js +9 -0
- package/dist/processors/touchchatProcessor.d.ts +7 -0
- package/dist/processors/touchchatProcessor.js +9 -0
- package/dist/types/aac.d.ts +17 -0
- package/dist/utilities/screenshotConverter.d.ts +69 -0
- package/dist/utilities/screenshotConverter.js +453 -0
- package/dist/validation/baseValidator.d.ts +80 -0
- package/dist/validation/baseValidator.js +160 -0
- package/dist/validation/gridsetValidator.d.ts +36 -0
- package/dist/validation/gridsetValidator.js +288 -0
- package/dist/validation/index.d.ts +13 -0
- package/dist/validation/index.js +69 -0
- package/dist/validation/obfValidator.d.ts +44 -0
- package/dist/validation/obfValidator.js +530 -0
- package/dist/validation/snapValidator.d.ts +33 -0
- package/dist/validation/snapValidator.js +237 -0
- package/dist/validation/touchChatValidator.d.ts +33 -0
- package/dist/validation/touchChatValidator.js +229 -0
- package/dist/validation/validationTypes.d.ts +64 -0
- package/dist/validation/validationTypes.js +15 -0
- package/examples/README.md +7 -0
- package/examples/demo.js +143 -0
- package/examples/obf/aboutme.json +376 -0
- package/examples/obf/array.json +6 -0
- package/examples/obf/hash.json +4 -0
- package/examples/obf/links.obz +0 -0
- package/examples/obf/simple.obf +53 -0
- package/examples/package-lock.json +1326 -0
- package/examples/package.json +10 -0
- package/examples/styling-example.ts +316 -0
- package/examples/translate.js +39 -0
- package/examples/translate_demo.js +254 -0
- package/examples/typescript-demo.ts +251 -0
- package/package.json +3 -1
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ScreenshotConverter = void 0;
|
|
7
|
+
const treeStructure_1 = require("../core/treeStructure");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
class ScreenshotConverter {
|
|
10
|
+
/**
|
|
11
|
+
* Parse filename to extract page hierarchy and names
|
|
12
|
+
* Examples:
|
|
13
|
+
* - "Home.png" → pageName: "Home", parentPath: ""
|
|
14
|
+
* - "Home->Fragen.png" → pageName: "Fragen", parentPath: "Home"
|
|
15
|
+
* - "Home->Settings->Profile.jpg" → pageName: "Profile", parentPath: "Home->Settings"
|
|
16
|
+
*/
|
|
17
|
+
static parseFilename(filename, delimiter = '->') {
|
|
18
|
+
const baseName = path_1.default.parse(filename).name;
|
|
19
|
+
const parts = baseName.split(delimiter).map((part) => part.trim());
|
|
20
|
+
return {
|
|
21
|
+
pageName: parts[parts.length - 1] || baseName,
|
|
22
|
+
parentPath: parts.slice(0, -1).join(delimiter),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build page hierarchy from an array of screenshots
|
|
27
|
+
*/
|
|
28
|
+
static buildPageHierarchy(screenshots) {
|
|
29
|
+
const hierarchy = {};
|
|
30
|
+
const delimiter = this.defaultOptions.filenameDelimiter || '->';
|
|
31
|
+
// First pass: parse all filenames
|
|
32
|
+
screenshots.forEach((screenshot, index) => {
|
|
33
|
+
const { pageName, parentPath } = this.parseFilename(screenshot.filename, delimiter);
|
|
34
|
+
screenshot.pageName = pageName;
|
|
35
|
+
screenshot.parentPath = parentPath;
|
|
36
|
+
const pageId = `page_${index}`;
|
|
37
|
+
hierarchy[pageId] = {
|
|
38
|
+
page: screenshot,
|
|
39
|
+
children: [],
|
|
40
|
+
parent: undefined,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
// Second pass: establish parent-child relationships
|
|
44
|
+
Object.entries(hierarchy).forEach(([pageId, entry]) => {
|
|
45
|
+
const parentPath = entry.page.parentPath;
|
|
46
|
+
if (parentPath) {
|
|
47
|
+
// Find parent by matching the full path
|
|
48
|
+
const parent = Object.values(hierarchy).find((h) => h.page.pageName === parentPath.split(delimiter).pop());
|
|
49
|
+
if (parent) {
|
|
50
|
+
entry.parent = Object.keys(hierarchy).find((key) => hierarchy[key] === parent);
|
|
51
|
+
parent.children.push(pageId);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return hierarchy;
|
|
56
|
+
}
|
|
57
|
+
static parseOCRText(ocrResult) {
|
|
58
|
+
const lines = ocrResult.split('\n').filter((line) => line.trim());
|
|
59
|
+
const cells = [];
|
|
60
|
+
const categories = new Set();
|
|
61
|
+
// Skip header metadata
|
|
62
|
+
const contentStart = lines.findIndex((line) => line.includes('ich möchte') && !line.includes('ich möchte ich'));
|
|
63
|
+
if (contentStart === -1) {
|
|
64
|
+
// Try another approach if the first pattern doesn't match
|
|
65
|
+
const gridStart = lines.findIndex((line) => line.includes('ich möchte') && line.split(/\s+/).length > 2);
|
|
66
|
+
if (gridStart === -1)
|
|
67
|
+
return { rows: 6, cols: 11, cells: [], categories: [] };
|
|
68
|
+
}
|
|
69
|
+
// Find the line with the grid content (usually has tab-separated values)
|
|
70
|
+
const gridLineIndex = lines.findIndex((line) => line.includes('ich möchte') && line.includes('\t') && line.split(/\s+/).length > 5);
|
|
71
|
+
let rows = 6;
|
|
72
|
+
let cols = 11;
|
|
73
|
+
// If we found a properly formatted grid line
|
|
74
|
+
if (gridLineIndex !== -1) {
|
|
75
|
+
const gridLine = lines[gridLineIndex];
|
|
76
|
+
// Split by tabs to get individual cell values
|
|
77
|
+
const tokens = gridLine
|
|
78
|
+
.split('\t')
|
|
79
|
+
.map((t) => t.trim())
|
|
80
|
+
.filter((t) => t);
|
|
81
|
+
cols = Math.max(tokens.length, cols);
|
|
82
|
+
// Create first row from the main grid line
|
|
83
|
+
tokens.forEach((token, col) => {
|
|
84
|
+
const isCategory = this.isCategoryToken(token);
|
|
85
|
+
const isNavigation = this.isNavigationToken(token);
|
|
86
|
+
const isEmpty = !token || token === '...' || token === '';
|
|
87
|
+
if (isCategory)
|
|
88
|
+
categories.add(token);
|
|
89
|
+
if (!isEmpty) {
|
|
90
|
+
cells.push({
|
|
91
|
+
text: token,
|
|
92
|
+
row: 0,
|
|
93
|
+
col,
|
|
94
|
+
isCategory,
|
|
95
|
+
isNavigation,
|
|
96
|
+
isEmpty,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
// Process subsequent lines
|
|
101
|
+
let currentRow = 1;
|
|
102
|
+
for (let i = gridLineIndex + 1; i < lines.length; i++) {
|
|
103
|
+
const line = lines[i].trim();
|
|
104
|
+
if (!line)
|
|
105
|
+
continue;
|
|
106
|
+
// Skip lines that look like headers or metadata
|
|
107
|
+
if (line.match(/^\d+:\d+/) || line.match(/[A-Z][a-z]{2},\s+\d+/) || line.includes('%'))
|
|
108
|
+
continue;
|
|
109
|
+
// Skip duplicate "ich möchte" at start
|
|
110
|
+
if (line === 'ich möchte' && currentRow === 1) {
|
|
111
|
+
currentRow = 0;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const tokens = line
|
|
115
|
+
.split('\t')
|
|
116
|
+
.map((t) => t.trim())
|
|
117
|
+
.filter((t) => t);
|
|
118
|
+
tokens.forEach((token, col) => {
|
|
119
|
+
const isCategory = this.isCategoryToken(token);
|
|
120
|
+
const isNavigation = this.isNavigationToken(token);
|
|
121
|
+
const isEmpty = !token || token === '...' || token === '';
|
|
122
|
+
if (isCategory)
|
|
123
|
+
categories.add(token);
|
|
124
|
+
if (!isEmpty) {
|
|
125
|
+
cells.push({
|
|
126
|
+
text: token,
|
|
127
|
+
row: currentRow,
|
|
128
|
+
col,
|
|
129
|
+
isCategory,
|
|
130
|
+
isNavigation,
|
|
131
|
+
isEmpty,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
currentRow++;
|
|
136
|
+
if (currentRow >= rows)
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// Fallback: simple whitespace parsing for unstructured OCR
|
|
142
|
+
let currentRow = 0;
|
|
143
|
+
lines.forEach((line, _lineIndex) => {
|
|
144
|
+
if (!line.trim())
|
|
145
|
+
return;
|
|
146
|
+
// Skip metadata
|
|
147
|
+
if (line.includes('%') || line.match(/\d+:\d+/) || line.match(/[A-Z][a-z]{2},\s+\d+/))
|
|
148
|
+
return;
|
|
149
|
+
const tokens = line.trim().split(/\s+/);
|
|
150
|
+
tokens.forEach((token, tokenIndex) => {
|
|
151
|
+
if (tokenIndex >= cols)
|
|
152
|
+
return; // Skip if beyond expected columns
|
|
153
|
+
const isCategory = this.isCategoryToken(token);
|
|
154
|
+
const isNavigation = this.isNavigationToken(token);
|
|
155
|
+
const isEmpty = !token || token.trim() === '' || token === '...';
|
|
156
|
+
if (isCategory)
|
|
157
|
+
categories.add(token);
|
|
158
|
+
if (!isEmpty) {
|
|
159
|
+
cells.push({
|
|
160
|
+
text: token,
|
|
161
|
+
row: currentRow,
|
|
162
|
+
col: tokenIndex,
|
|
163
|
+
isCategory,
|
|
164
|
+
isNavigation,
|
|
165
|
+
isEmpty,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
currentRow++;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// Auto-detect actual grid dimensions
|
|
173
|
+
if (cells.length > 0) {
|
|
174
|
+
rows = Math.max(...cells.map((c) => c.row)) + 1;
|
|
175
|
+
cols = Math.max(...cells.map((c) => c.col)) + 1;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
rows,
|
|
179
|
+
cols,
|
|
180
|
+
cells,
|
|
181
|
+
categories: Array.from(categories),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
static isCategoryToken(token) {
|
|
185
|
+
const knownCategories = [
|
|
186
|
+
// English categories
|
|
187
|
+
'Questions',
|
|
188
|
+
'Meetings',
|
|
189
|
+
'Praise',
|
|
190
|
+
'Complaints',
|
|
191
|
+
'Phrases',
|
|
192
|
+
'Conversations',
|
|
193
|
+
'Verbs',
|
|
194
|
+
'People',
|
|
195
|
+
'Messages',
|
|
196
|
+
'Properties',
|
|
197
|
+
'Feelings',
|
|
198
|
+
'Actions',
|
|
199
|
+
'Activities',
|
|
200
|
+
'Food',
|
|
201
|
+
'Drink',
|
|
202
|
+
'Colors',
|
|
203
|
+
'Shapes',
|
|
204
|
+
'Settings',
|
|
205
|
+
'Home',
|
|
206
|
+
'Back',
|
|
207
|
+
'Next',
|
|
208
|
+
'Menu',
|
|
209
|
+
// German categories
|
|
210
|
+
'Fragen',
|
|
211
|
+
'Treffen',
|
|
212
|
+
'Lob',
|
|
213
|
+
'Beschwerde',
|
|
214
|
+
'Sprüche',
|
|
215
|
+
'Gespräche',
|
|
216
|
+
'Verben',
|
|
217
|
+
'Leute',
|
|
218
|
+
'Mitteilungen',
|
|
219
|
+
'Eigenschaften',
|
|
220
|
+
'Gefühle',
|
|
221
|
+
'Spielen',
|
|
222
|
+
'Multimedia',
|
|
223
|
+
'Essen',
|
|
224
|
+
'Trinken',
|
|
225
|
+
'Farben/Formen',
|
|
226
|
+
];
|
|
227
|
+
// Check for known categories
|
|
228
|
+
if (knownCategories.includes(token)) {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
// Check for common category patterns
|
|
232
|
+
const categoryPatterns = [
|
|
233
|
+
/.*Questions?$/,
|
|
234
|
+
/.*Category.*/,
|
|
235
|
+
/.*Menu.*/,
|
|
236
|
+
/.*Settings?$/,
|
|
237
|
+
/.*Options?$/,
|
|
238
|
+
/谈话/i, // Chinese
|
|
239
|
+
/質問/i, // Japanese
|
|
240
|
+
/preguntas/i, // Spanish
|
|
241
|
+
];
|
|
242
|
+
return categoryPatterns.some((pattern) => pattern.test(token));
|
|
243
|
+
}
|
|
244
|
+
static isNavigationToken(token) {
|
|
245
|
+
const navTokens = [
|
|
246
|
+
// English
|
|
247
|
+
'Home',
|
|
248
|
+
'Back',
|
|
249
|
+
'Next',
|
|
250
|
+
'Previous',
|
|
251
|
+
'Menu',
|
|
252
|
+
'Settings',
|
|
253
|
+
'Exit',
|
|
254
|
+
'Close',
|
|
255
|
+
'OK',
|
|
256
|
+
'Cancel',
|
|
257
|
+
'Yes',
|
|
258
|
+
'No',
|
|
259
|
+
'Help',
|
|
260
|
+
'Search',
|
|
261
|
+
// German
|
|
262
|
+
'Home',
|
|
263
|
+
'Zurück',
|
|
264
|
+
'Weiter',
|
|
265
|
+
'Menü',
|
|
266
|
+
'Einstellungen',
|
|
267
|
+
'Beenden',
|
|
268
|
+
'Schließen',
|
|
269
|
+
'Hilfe',
|
|
270
|
+
'Suche',
|
|
271
|
+
// Navigation indicators
|
|
272
|
+
'←',
|
|
273
|
+
'→',
|
|
274
|
+
'↑',
|
|
275
|
+
'↓',
|
|
276
|
+
'◀',
|
|
277
|
+
'▶',
|
|
278
|
+
'▲',
|
|
279
|
+
'▼',
|
|
280
|
+
];
|
|
281
|
+
return (navTokens.includes(token) || token === '←' || token === '→' || token === '↑' || token === '↓');
|
|
282
|
+
}
|
|
283
|
+
static convertToAACPage(screenshotPage, pageHierarchy, options) {
|
|
284
|
+
const opts = {
|
|
285
|
+
...this.defaultOptions,
|
|
286
|
+
...options,
|
|
287
|
+
};
|
|
288
|
+
const buttons = [];
|
|
289
|
+
// Convert cells to AAC buttons
|
|
290
|
+
screenshotPage.grid.cells.forEach((cell) => {
|
|
291
|
+
if (cell.isEmpty && !opts.includeEmptyCells)
|
|
292
|
+
return;
|
|
293
|
+
const button = new treeStructure_1.AACButton({
|
|
294
|
+
id: `cell_${cell.row}_${cell.col}`,
|
|
295
|
+
label: cell.text,
|
|
296
|
+
message: cell.text,
|
|
297
|
+
style: {
|
|
298
|
+
backgroundColor: cell.isCategory ? '#4CAF50' : cell.isNavigation ? '#2196F3' : '#FFFFFF',
|
|
299
|
+
fontColor: cell.isCategory || cell.isNavigation ? '#FFFFFF' : '#000000',
|
|
300
|
+
borderColor: '#CCCCCC',
|
|
301
|
+
borderWidth: 1,
|
|
302
|
+
},
|
|
303
|
+
semanticAction: this.createSemanticAction(cell, screenshotPage, pageHierarchy, opts),
|
|
304
|
+
x: cell.col,
|
|
305
|
+
y: cell.row,
|
|
306
|
+
});
|
|
307
|
+
buttons.push(button);
|
|
308
|
+
});
|
|
309
|
+
return new treeStructure_1.AACPage({
|
|
310
|
+
id: screenshotPage.pageName || 'screenshot_page',
|
|
311
|
+
name: screenshotPage.pageTitle || screenshotPage.pageName || 'Screenshot Page',
|
|
312
|
+
buttons,
|
|
313
|
+
grid: {
|
|
314
|
+
columns: screenshotPage.grid.cols,
|
|
315
|
+
rows: screenshotPage.grid.rows,
|
|
316
|
+
},
|
|
317
|
+
style: {
|
|
318
|
+
backgroundColor: '#F5F5F5',
|
|
319
|
+
},
|
|
320
|
+
parentId: null,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
static createSemanticAction(cell, screenshotPage, pageHierarchy, options) {
|
|
324
|
+
if (cell.isEmpty)
|
|
325
|
+
return undefined;
|
|
326
|
+
const _opts = {
|
|
327
|
+
...this.defaultOptions,
|
|
328
|
+
...options,
|
|
329
|
+
};
|
|
330
|
+
if (cell.isCategory) {
|
|
331
|
+
// Try to find target page in hierarchy based on category name
|
|
332
|
+
let targetId = `category_${cell.text.toLowerCase().replace(/\s+/g, '_')}`;
|
|
333
|
+
if (pageHierarchy) {
|
|
334
|
+
// Look for a page that matches this category
|
|
335
|
+
const matchingPage = Object.values(pageHierarchy).find((h) => h.page.pageName?.toLowerCase() === cell.text.toLowerCase());
|
|
336
|
+
if (matchingPage) {
|
|
337
|
+
targetId = matchingPage.page.pageName || targetId;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
category: treeStructure_1.AACSemanticCategory.NAVIGATION,
|
|
342
|
+
intent: treeStructure_1.AACSemanticIntent.NAVIGATE_TO,
|
|
343
|
+
targetId,
|
|
344
|
+
parameters: { category: cell.text },
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (cell.isNavigation) {
|
|
348
|
+
const text = cell.text.toLowerCase();
|
|
349
|
+
// Home navigation
|
|
350
|
+
if (text === 'home' || text === '⌂') {
|
|
351
|
+
return {
|
|
352
|
+
category: treeStructure_1.AACSemanticCategory.NAVIGATION,
|
|
353
|
+
intent: treeStructure_1.AACSemanticIntent.GO_HOME,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
// Back navigation
|
|
357
|
+
if (text === 'back' || text === 'zurück' || text === '←' || text === '◀') {
|
|
358
|
+
return {
|
|
359
|
+
category: treeStructure_1.AACSemanticCategory.NAVIGATION,
|
|
360
|
+
intent: treeStructure_1.AACSemanticIntent.GO_BACK,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
// Next/forward navigation
|
|
364
|
+
if (text === 'next' || text === 'weiter' || text === '→' || text === '▶') {
|
|
365
|
+
// If we have hierarchy, navigate to parent
|
|
366
|
+
if (pageHierarchy && screenshotPage.parentPath) {
|
|
367
|
+
const parentId = Object.keys(pageHierarchy).find((key) => pageHierarchy[key].page.pageName === screenshotPage.parentPath?.split('->').pop());
|
|
368
|
+
if (parentId) {
|
|
369
|
+
return {
|
|
370
|
+
category: treeStructure_1.AACSemanticCategory.NAVIGATION,
|
|
371
|
+
intent: treeStructure_1.AACSemanticIntent.NAVIGATE_TO,
|
|
372
|
+
targetId: parentId,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
category: treeStructure_1.AACSemanticCategory.NAVIGATION,
|
|
378
|
+
intent: treeStructure_1.AACSemanticIntent.NAVIGATE_TO,
|
|
379
|
+
targetId: 'next_page',
|
|
380
|
+
parameters: { direction: 'next' },
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
// Menu navigation
|
|
384
|
+
if (text === 'menu' || text === 'menü') {
|
|
385
|
+
return {
|
|
386
|
+
category: treeStructure_1.AACSemanticCategory.NAVIGATION,
|
|
387
|
+
intent: treeStructure_1.AACSemanticIntent.NAVIGATE_TO,
|
|
388
|
+
targetId: 'main_menu',
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Default to speaking the text
|
|
393
|
+
return {
|
|
394
|
+
category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
|
|
395
|
+
intent: treeStructure_1.AACSemanticIntent.SPEAK_IMMEDIATE,
|
|
396
|
+
text: cell.text,
|
|
397
|
+
richText: {
|
|
398
|
+
text: cell.text,
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
static convertToAACTree(screenshotPages, options) {
|
|
403
|
+
const opts = { ...this.defaultOptions, ...options };
|
|
404
|
+
const tree = new treeStructure_1.AACTree();
|
|
405
|
+
// Build page hierarchy
|
|
406
|
+
const pageHierarchy = this.buildPageHierarchy(screenshotPages);
|
|
407
|
+
// Set metadata on tree
|
|
408
|
+
tree.version = '1.0';
|
|
409
|
+
tree.metadata = {
|
|
410
|
+
name: 'Screenshot Conversion',
|
|
411
|
+
author: 'AAC Processors',
|
|
412
|
+
description: 'Converted from screenshot images',
|
|
413
|
+
language: opts.language,
|
|
414
|
+
};
|
|
415
|
+
// Convert each screenshot page to AAC page
|
|
416
|
+
screenshotPages.forEach((screenshotPage, index) => {
|
|
417
|
+
const page = this.convertToAACPage(screenshotPage, pageHierarchy, opts);
|
|
418
|
+
// Ensure unique ID by using page name if available
|
|
419
|
+
if (screenshotPage.pageName) {
|
|
420
|
+
page.id = this.sanitizePageId(screenshotPage.pageName);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
page.id = `screenshot_page_${index}`;
|
|
424
|
+
}
|
|
425
|
+
tree.addPage(page);
|
|
426
|
+
});
|
|
427
|
+
// Set root page to the one with no parent
|
|
428
|
+
const rootPage = Object.entries(pageHierarchy).find(([_, entry]) => !entry.parent);
|
|
429
|
+
if (rootPage) {
|
|
430
|
+
const rootPageId = this.sanitizePageId(rootPage[1].page.pageName || 'home');
|
|
431
|
+
if (tree.pages[rootPageId]) {
|
|
432
|
+
tree.rootId = rootPageId;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return tree;
|
|
436
|
+
}
|
|
437
|
+
static sanitizePageId(pageName) {
|
|
438
|
+
return pageName
|
|
439
|
+
.toLowerCase()
|
|
440
|
+
.replace(/[^a-z0-9]/g, '_')
|
|
441
|
+
.replace(/_+/g, '_')
|
|
442
|
+
.replace(/^_|_$/g, '');
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
exports.ScreenshotConverter = ScreenshotConverter;
|
|
446
|
+
ScreenshotConverter.defaultOptions = {
|
|
447
|
+
includeEmptyCells: false,
|
|
448
|
+
generateIds: true,
|
|
449
|
+
targetPlatform: 'grid3',
|
|
450
|
+
language: 'en',
|
|
451
|
+
fallbackCategory: 'General',
|
|
452
|
+
filenameDelimiter: '->',
|
|
453
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ValidationResult, ValidationCheck, ValidationOptions } from './validationTypes';
|
|
2
|
+
/**
|
|
3
|
+
* Base class for all format validators
|
|
4
|
+
* Provides the check-based validation system
|
|
5
|
+
*/
|
|
6
|
+
export declare abstract class BaseValidator {
|
|
7
|
+
protected _errors: number;
|
|
8
|
+
protected _warnings: number;
|
|
9
|
+
protected _checks: ValidationCheck[];
|
|
10
|
+
protected _sub_checks: ValidationResult[];
|
|
11
|
+
protected _blocked: boolean;
|
|
12
|
+
protected _options: ValidationOptions;
|
|
13
|
+
constructor(options?: ValidationOptions);
|
|
14
|
+
/**
|
|
15
|
+
* Reset validator state
|
|
16
|
+
*/
|
|
17
|
+
protected reset(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Add a validation check that will be executed
|
|
20
|
+
* @param type - Category of the check
|
|
21
|
+
* @param description - Human-readable description
|
|
22
|
+
* @param checkFn - Async function that performs the check
|
|
23
|
+
*/
|
|
24
|
+
protected add_check(type: string, description: string, checkFn: () => Promise<void>): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Add a synchronous validation check
|
|
27
|
+
*/
|
|
28
|
+
protected add_check_sync(type: string, description: string, checkFn: () => void): void;
|
|
29
|
+
/**
|
|
30
|
+
* Throw a validation error
|
|
31
|
+
* @param message - Error message
|
|
32
|
+
* @param blocker - If true, stop further validation
|
|
33
|
+
*/
|
|
34
|
+
protected err(message: string, blocker?: boolean): never;
|
|
35
|
+
/**
|
|
36
|
+
* Add a warning to the last check
|
|
37
|
+
* @param message - Warning message
|
|
38
|
+
*/
|
|
39
|
+
protected warn(message: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Get the current error count
|
|
42
|
+
*/
|
|
43
|
+
get errors(): number;
|
|
44
|
+
/**
|
|
45
|
+
* Get the current warning count
|
|
46
|
+
*/
|
|
47
|
+
get warnings(): number;
|
|
48
|
+
/**
|
|
49
|
+
* Get all checks performed so far
|
|
50
|
+
*/
|
|
51
|
+
get checks(): ValidationCheck[];
|
|
52
|
+
/**
|
|
53
|
+
* Get sub-validation results
|
|
54
|
+
*/
|
|
55
|
+
get sub_checks(): ValidationResult[];
|
|
56
|
+
/**
|
|
57
|
+
* Check if validation has been blocked
|
|
58
|
+
*/
|
|
59
|
+
get isBlocked(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Build the final validation result
|
|
62
|
+
*/
|
|
63
|
+
protected buildResult(filename: string, filesize: number, format: string): ValidationResult;
|
|
64
|
+
/**
|
|
65
|
+
* Abstract method - each validator must implement this
|
|
66
|
+
* @param content - The content to validate (can be string, buffer, object, etc.)
|
|
67
|
+
* @param filename - Name of the file being validated
|
|
68
|
+
* @param filesize - Size of the file in bytes
|
|
69
|
+
*/
|
|
70
|
+
abstract validate(content: any, filename: string, filesize: number): Promise<ValidationResult>;
|
|
71
|
+
/**
|
|
72
|
+
* Static helper to validate from file path
|
|
73
|
+
* Must be implemented by subclasses if they support file-based validation
|
|
74
|
+
*/
|
|
75
|
+
static validateFile(_filePath: string): Promise<ValidationResult>;
|
|
76
|
+
/**
|
|
77
|
+
* Static helper to identify if content is this validator's format
|
|
78
|
+
*/
|
|
79
|
+
static identifyFormat(_content: any, _filename: string): Promise<boolean>;
|
|
80
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseValidator = void 0;
|
|
4
|
+
const validationTypes_1 = require("./validationTypes");
|
|
5
|
+
/**
|
|
6
|
+
* Base class for all format validators
|
|
7
|
+
* Provides the check-based validation system
|
|
8
|
+
*/
|
|
9
|
+
class BaseValidator {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this._errors = 0;
|
|
12
|
+
this._warnings = 0;
|
|
13
|
+
this._checks = [];
|
|
14
|
+
this._sub_checks = [];
|
|
15
|
+
this._blocked = false;
|
|
16
|
+
this._options = {
|
|
17
|
+
includeWarnings: options.includeWarnings ?? true,
|
|
18
|
+
stopOnBlocker: options.stopOnBlocker ?? true,
|
|
19
|
+
customRules: options.customRules || [],
|
|
20
|
+
};
|
|
21
|
+
this.reset();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Reset validator state
|
|
25
|
+
*/
|
|
26
|
+
reset() {
|
|
27
|
+
this._errors = 0;
|
|
28
|
+
this._warnings = 0;
|
|
29
|
+
this._checks = [];
|
|
30
|
+
this._sub_checks = [];
|
|
31
|
+
this._blocked = false;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Add a validation check that will be executed
|
|
35
|
+
* @param type - Category of the check
|
|
36
|
+
* @param description - Human-readable description
|
|
37
|
+
* @param checkFn - Async function that performs the check
|
|
38
|
+
*/
|
|
39
|
+
async add_check(type, description, checkFn) {
|
|
40
|
+
// Skip if blocked by a previous error
|
|
41
|
+
if (this._blocked && this._options.stopOnBlocker) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const checkObj = {
|
|
45
|
+
type,
|
|
46
|
+
description,
|
|
47
|
+
valid: true,
|
|
48
|
+
};
|
|
49
|
+
this._checks.push(checkObj);
|
|
50
|
+
try {
|
|
51
|
+
await checkFn();
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
if (e instanceof validationTypes_1.ValidationError) {
|
|
55
|
+
this._errors++;
|
|
56
|
+
checkObj.valid = false;
|
|
57
|
+
checkObj.error = e.message;
|
|
58
|
+
if (e.blocker) {
|
|
59
|
+
this._blocked = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Re-throw non-ValidationError exceptions
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Add a synchronous validation check
|
|
70
|
+
*/
|
|
71
|
+
add_check_sync(type, description, checkFn) {
|
|
72
|
+
// Convert sync to async for consistency
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
74
|
+
void this.add_check(type, description, async () => checkFn());
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Throw a validation error
|
|
78
|
+
* @param message - Error message
|
|
79
|
+
* @param blocker - If true, stop further validation
|
|
80
|
+
*/
|
|
81
|
+
err(message, blocker = false) {
|
|
82
|
+
throw new validationTypes_1.ValidationError(message, blocker);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Add a warning to the last check
|
|
86
|
+
* @param message - Warning message
|
|
87
|
+
*/
|
|
88
|
+
warn(message) {
|
|
89
|
+
if (!this._options.includeWarnings) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this._warnings++;
|
|
93
|
+
const lastCheck = this._checks[this._checks.length - 1];
|
|
94
|
+
if (lastCheck) {
|
|
95
|
+
lastCheck.warnings = lastCheck.warnings || [];
|
|
96
|
+
lastCheck.warnings.push(message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get the current error count
|
|
101
|
+
*/
|
|
102
|
+
get errors() {
|
|
103
|
+
return this._errors;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get the current warning count
|
|
107
|
+
*/
|
|
108
|
+
get warnings() {
|
|
109
|
+
return this._warnings;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get all checks performed so far
|
|
113
|
+
*/
|
|
114
|
+
get checks() {
|
|
115
|
+
return this._checks;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get sub-validation results
|
|
119
|
+
*/
|
|
120
|
+
get sub_checks() {
|
|
121
|
+
return this._sub_checks;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check if validation has been blocked
|
|
125
|
+
*/
|
|
126
|
+
get isBlocked() {
|
|
127
|
+
return this._blocked;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Build the final validation result
|
|
131
|
+
*/
|
|
132
|
+
buildResult(filename, filesize, format) {
|
|
133
|
+
return {
|
|
134
|
+
filename,
|
|
135
|
+
filesize,
|
|
136
|
+
format,
|
|
137
|
+
valid: this._errors === 0,
|
|
138
|
+
errors: this._errors,
|
|
139
|
+
warnings: this._warnings,
|
|
140
|
+
results: this._checks,
|
|
141
|
+
sub_results: this._sub_checks.length > 0 ? this._sub_checks : undefined,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Static helper to validate from file path
|
|
146
|
+
* Must be implemented by subclasses if they support file-based validation
|
|
147
|
+
*/
|
|
148
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
149
|
+
static async validateFile(_filePath) {
|
|
150
|
+
throw new Error('validateFile must be implemented by subclass');
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Static helper to identify if content is this validator's format
|
|
154
|
+
*/
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
156
|
+
static async identifyFormat(_content, _filename) {
|
|
157
|
+
throw new Error('identifyFormat must be implemented by subclass');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
exports.BaseValidator = BaseValidator;
|