@willwade/aac-processors 0.1.21 → 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/dist/browser/processors/applePanelsProcessor.js +24 -24
- package/dist/browser/processors/astericsGridProcessor.js +22 -24
- package/dist/browser/processors/dotProcessor.js +6 -10
- package/dist/browser/processors/gridset/helpers.js +33 -30
- package/dist/browser/processors/gridset/symbolExtractor.js +2 -2
- package/dist/browser/processors/gridset/symbolSearch.js +22 -22
- package/dist/browser/processors/gridset/symbols.js +14 -14
- package/dist/browser/processors/gridsetProcessor.js +7 -7
- package/dist/browser/processors/obfProcessor.js +24 -25
- package/dist/browser/processors/opmlProcessor.js +6 -10
- package/dist/browser/processors/snap/helpers.js +34 -30
- package/dist/browser/processors/snapProcessor.js +28 -28
- package/dist/browser/processors/touchchatProcessor.js +24 -25
- package/dist/browser/utilities/analytics/history.js +24 -18
- package/dist/browser/utilities/analytics/metrics/comparison.js +16 -16
- package/dist/browser/utilities/analytics/metrics/vocabulary.js +2 -2
- package/dist/browser/utilities/analytics/reference/browser.js +16 -16
- package/dist/browser/utilities/analytics/reference/index.js +25 -24
- package/dist/browser/utils/io.js +29 -25
- package/dist/browser/utils/sqlite.js +5 -5
- package/dist/browser/utils/zip.js +2 -4
- package/dist/browser/validation/gridsetValidator.js +2 -2
- package/dist/browser/validation/obfValidator.js +2 -2
- package/dist/browser/validation/snapValidator.js +3 -3
- package/dist/browser/validation/touchChatValidator.js +3 -3
- package/dist/cli/index.js +19 -16
- package/dist/core/baseProcessor.d.ts +1 -1
- package/dist/processors/applePanelsProcessor.js +24 -24
- package/dist/processors/astericsGridProcessor.d.ts +4 -4
- package/dist/processors/astericsGridProcessor.js +22 -24
- package/dist/processors/dotProcessor.js +6 -10
- package/dist/processors/excelProcessor.d.ts +3 -3
- package/dist/processors/excelProcessor.js +10 -13
- package/dist/processors/gridset/helpers.d.ts +9 -9
- package/dist/processors/gridset/helpers.js +33 -30
- package/dist/processors/gridset/symbolExtractor.d.ts +1 -1
- package/dist/processors/gridset/symbolExtractor.js +2 -2
- package/dist/processors/gridset/symbolSearch.d.ts +10 -10
- package/dist/processors/gridset/symbolSearch.js +22 -22
- package/dist/processors/gridset/symbols.d.ts +3 -3
- package/dist/processors/gridset/symbols.js +14 -14
- package/dist/processors/gridsetProcessor.d.ts +2 -2
- package/dist/processors/gridsetProcessor.js +7 -7
- package/dist/processors/obfProcessor.d.ts +2 -2
- package/dist/processors/obfProcessor.js +24 -25
- package/dist/processors/obfsetProcessor.js +1 -2
- package/dist/processors/opmlProcessor.js +6 -10
- package/dist/processors/snap/helpers.d.ts +8 -8
- package/dist/processors/snap/helpers.js +34 -30
- package/dist/processors/snapProcessor.d.ts +2 -2
- package/dist/processors/snapProcessor.js +28 -28
- package/dist/processors/touchchatProcessor.d.ts +2 -2
- package/dist/processors/touchchatProcessor.js +24 -25
- package/dist/types/aac.d.ts +2 -2
- package/dist/utilities/analytics/history.d.ts +8 -8
- package/dist/utilities/analytics/history.js +24 -18
- package/dist/utilities/analytics/index.d.ts +1 -1
- package/dist/utilities/analytics/index.js +3 -2
- package/dist/utilities/analytics/metrics/comparison.d.ts +1 -1
- package/dist/utilities/analytics/metrics/comparison.js +16 -16
- package/dist/utilities/analytics/metrics/vocabulary.d.ts +1 -1
- package/dist/utilities/analytics/metrics/vocabulary.js +2 -2
- package/dist/utilities/analytics/reference/browser.d.ts +9 -9
- package/dist/utilities/analytics/reference/browser.js +16 -16
- package/dist/utilities/analytics/reference/index.d.ts +21 -21
- package/dist/utilities/analytics/reference/index.js +25 -24
- package/dist/utilities/symbolTools.d.ts +5 -5
- package/dist/utilities/symbolTools.js +10 -8
- package/dist/utils/io.d.ts +11 -11
- package/dist/utils/io.js +29 -25
- package/dist/utils/sqlite.d.ts +1 -1
- package/dist/utils/sqlite.js +5 -5
- package/dist/utils/zip.js +2 -4
- package/dist/validation/applePanelsValidator.js +7 -6
- package/dist/validation/astericsValidator.js +2 -2
- package/dist/validation/dotValidator.js +2 -2
- package/dist/validation/excelValidator.js +2 -2
- package/dist/validation/gridsetValidator.js +2 -2
- package/dist/validation/index.js +2 -2
- package/dist/validation/obfValidator.js +2 -2
- package/dist/validation/obfsetValidator.js +2 -2
- package/dist/validation/opmlValidator.js +2 -2
- package/dist/validation/snapValidator.js +3 -3
- package/dist/validation/touchChatValidator.js +3 -3
- package/package.json +1 -1
|
@@ -93,12 +93,12 @@ export function isSymbolReference(reference) {
|
|
|
93
93
|
* Get the default Grid 3 installation path for the current platform
|
|
94
94
|
* @returns Default Grid 3 path or empty string if not found
|
|
95
95
|
*/
|
|
96
|
-
export function getDefaultGrid3Path(fileAdapter) {
|
|
96
|
+
export async function getDefaultGrid3Path(fileAdapter) {
|
|
97
97
|
const { pathExists } = fileAdapter ?? defaultFileAdapter;
|
|
98
98
|
const platform = (typeof process !== 'undefined' && process.platform ? process.platform : 'unknown');
|
|
99
99
|
const defaultPath = DEFAULT_GRID3_PATHS[platform] || '';
|
|
100
100
|
try {
|
|
101
|
-
if (defaultPath && pathExists(defaultPath)) {
|
|
101
|
+
if (defaultPath && (await pathExists(defaultPath))) {
|
|
102
102
|
return defaultPath;
|
|
103
103
|
}
|
|
104
104
|
// Try to find Grid 3 in common locations
|
|
@@ -110,7 +110,7 @@ export function getDefaultGrid3Path(fileAdapter) {
|
|
|
110
110
|
'/opt/smartbox/grid3',
|
|
111
111
|
];
|
|
112
112
|
for (const testPath of commonPaths) {
|
|
113
|
-
if (pathExists(testPath)) {
|
|
113
|
+
if (await pathExists(testPath)) {
|
|
114
114
|
return testPath;
|
|
115
115
|
}
|
|
116
116
|
}
|
|
@@ -146,22 +146,22 @@ export function getSymbolSearchIndexesDir(grid3Path, locale = DEFAULT_LOCALE, fi
|
|
|
146
146
|
* @param options - Resolution options
|
|
147
147
|
* @returns Array of symbol library information
|
|
148
148
|
*/
|
|
149
|
-
export function getAvailableSymbolLibraries(options = {}, fileAdapter) {
|
|
149
|
+
export async function getAvailableSymbolLibraries(options = {}, fileAdapter) {
|
|
150
150
|
const { pathExists, getFileSize, listDir, join, basename } = fileAdapter ?? defaultFileAdapter;
|
|
151
|
-
const grid3Path = options.grid3Path || options.symbolDir || getDefaultGrid3Path();
|
|
151
|
+
const grid3Path = options.grid3Path || options.symbolDir || (await getDefaultGrid3Path());
|
|
152
152
|
if (!grid3Path) {
|
|
153
153
|
return [];
|
|
154
154
|
}
|
|
155
155
|
const symbolsDir = getSymbolLibrariesDir(grid3Path, fileAdapter);
|
|
156
|
-
if (!pathExists(symbolsDir)) {
|
|
156
|
+
if (!(await pathExists(symbolsDir))) {
|
|
157
157
|
return [];
|
|
158
158
|
}
|
|
159
159
|
const libraries = [];
|
|
160
|
-
const files = listDir(symbolsDir);
|
|
160
|
+
const files = await listDir(symbolsDir);
|
|
161
161
|
for (const file of files) {
|
|
162
162
|
if (file.endsWith('.symbols')) {
|
|
163
163
|
const fullPath = join(symbolsDir, file);
|
|
164
|
-
const size = getFileSize(fullPath);
|
|
164
|
+
const size = await getFileSize(fullPath);
|
|
165
165
|
const libraryName = basename(file, '.symbols');
|
|
166
166
|
libraries.push({
|
|
167
167
|
name: libraryName,
|
|
@@ -180,9 +180,9 @@ export function getAvailableSymbolLibraries(options = {}, fileAdapter) {
|
|
|
180
180
|
* @param options - Resolution options
|
|
181
181
|
* @returns Symbol library info or undefined if not found
|
|
182
182
|
*/
|
|
183
|
-
export function getSymbolLibraryInfo(libraryName, options = {}, fileAdapter) {
|
|
183
|
+
export async function getSymbolLibraryInfo(libraryName, options = {}, fileAdapter) {
|
|
184
184
|
const { pathExists, getFileSize, join } = fileAdapter ?? defaultFileAdapter;
|
|
185
|
-
const grid3Path = options.grid3Path || options.symbolDir || getDefaultGrid3Path();
|
|
185
|
+
const grid3Path = options.grid3Path || options.symbolDir || (await getDefaultGrid3Path());
|
|
186
186
|
if (!grid3Path) {
|
|
187
187
|
return undefined;
|
|
188
188
|
}
|
|
@@ -196,8 +196,8 @@ export function getSymbolLibraryInfo(libraryName, options = {}, fileAdapter) {
|
|
|
196
196
|
];
|
|
197
197
|
for (const file of variations) {
|
|
198
198
|
const fullPath = join(symbolsDir, file);
|
|
199
|
-
if (pathExists(fullPath)) {
|
|
200
|
-
const size = getFileSize(fullPath);
|
|
199
|
+
if (await pathExists(fullPath)) {
|
|
200
|
+
const size = await getFileSize(fullPath);
|
|
201
201
|
return {
|
|
202
202
|
name: libraryName,
|
|
203
203
|
pixFile: fullPath,
|
|
@@ -224,7 +224,7 @@ export async function resolveSymbolReference(reference, options = {}, fileAdapte
|
|
|
224
224
|
error: 'Invalid symbol reference format',
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
|
-
const grid3Path = options.grid3Path || getDefaultGrid3Path();
|
|
227
|
+
const grid3Path = options.grid3Path || (await getDefaultGrid3Path());
|
|
228
228
|
if (!grid3Path) {
|
|
229
229
|
return {
|
|
230
230
|
reference: parsed,
|
|
@@ -232,7 +232,7 @@ export async function resolveSymbolReference(reference, options = {}, fileAdapte
|
|
|
232
232
|
error: 'Grid 3 installation not found. Please specify grid3Path.',
|
|
233
233
|
};
|
|
234
234
|
}
|
|
235
|
-
const libraryInfo = getSymbolLibraryInfo(parsed.library, { grid3Path });
|
|
235
|
+
const libraryInfo = await getSymbolLibraryInfo(parsed.library, { grid3Path });
|
|
236
236
|
if (!libraryInfo || !libraryInfo.exists) {
|
|
237
237
|
return {
|
|
238
238
|
reference: parsed,
|
|
@@ -398,7 +398,7 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
398
398
|
const tree = new AACTree();
|
|
399
399
|
let zipResult;
|
|
400
400
|
try {
|
|
401
|
-
const zipInput = readBinaryFromInput(filePathOrBuffer);
|
|
401
|
+
const zipInput = await readBinaryFromInput(filePathOrBuffer);
|
|
402
402
|
zipResult = await this.options.zipAdapter(zipInput);
|
|
403
403
|
}
|
|
404
404
|
catch (error) {
|
|
@@ -1742,7 +1742,7 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
1742
1742
|
});
|
|
1743
1743
|
// Save the translated tree and return its content
|
|
1744
1744
|
await this.saveFromTree(tree, outputPath);
|
|
1745
|
-
return readBinaryFromInput(outputPath);
|
|
1745
|
+
return await readBinaryFromInput(outputPath);
|
|
1746
1746
|
}
|
|
1747
1747
|
/**
|
|
1748
1748
|
* Extract symbol information from a gridset for LLM-based translation.
|
|
@@ -1751,7 +1751,7 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
1751
1751
|
* This method uses shared translation utilities that work across all AAC formats.
|
|
1752
1752
|
*
|
|
1753
1753
|
* @param filePathOrBuffer - Path to gridset file or buffer
|
|
1754
|
-
* @returns
|
|
1754
|
+
* @returns Promise resolving to symbol information for LLM processing
|
|
1755
1755
|
*/
|
|
1756
1756
|
async extractSymbolsForLLM(filePathOrBuffer) {
|
|
1757
1757
|
const tree = await this.loadIntoTree(filePathOrBuffer);
|
|
@@ -1781,7 +1781,7 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
1781
1781
|
* @param llmTranslations - Array of LLM translations with symbol info
|
|
1782
1782
|
* @param outputPath - Where to save the translated gridset
|
|
1783
1783
|
* @param options - Translation options (e.g., allowPartial for testing)
|
|
1784
|
-
* @returns
|
|
1784
|
+
* @returns Promise resolving to a buffer of the translated gridset
|
|
1785
1785
|
*/
|
|
1786
1786
|
async processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
|
|
1787
1787
|
const { readBinaryFromInput } = this.options.fileAdapter;
|
|
@@ -1823,7 +1823,7 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
1823
1823
|
});
|
|
1824
1824
|
// Save and return
|
|
1825
1825
|
await this.saveFromTree(tree, outputPath);
|
|
1826
|
-
return readBinaryFromInput(outputPath);
|
|
1826
|
+
return await readBinaryFromInput(outputPath);
|
|
1827
1827
|
}
|
|
1828
1828
|
async saveFromTree(tree, outputPath) {
|
|
1829
1829
|
const files = [];
|
|
@@ -1832,7 +1832,7 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
1832
1832
|
if (Object.keys(tree.pages).length === 0) {
|
|
1833
1833
|
// Create empty zip for empty tree
|
|
1834
1834
|
const zipBuffer = await zip.writeFiles([]);
|
|
1835
|
-
writeBinaryToPath(outputPath, zipBuffer);
|
|
1835
|
+
await writeBinaryToPath(outputPath, zipBuffer);
|
|
1836
1836
|
return;
|
|
1837
1837
|
}
|
|
1838
1838
|
// Collect all unique styles from pages and buttons
|
|
@@ -2142,7 +2142,7 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
2142
2142
|
});
|
|
2143
2143
|
// Write the zip file
|
|
2144
2144
|
const zipBuffer = await zip.writeFiles(files);
|
|
2145
|
-
writeBinaryToPath(outputPath, zipBuffer);
|
|
2145
|
+
await writeBinaryToPath(outputPath, zipBuffer);
|
|
2146
2146
|
}
|
|
2147
2147
|
// Helper method to calculate column definitions based on page layout
|
|
2148
2148
|
calculateColumnDefinitions(page) {
|
|
@@ -3,7 +3,6 @@ import { AACTree, AACPage, AACButton, AACSemanticCategory, AACSemanticIntent, }
|
|
|
3
3
|
import { generateCloneId } from '../utilities/analytics/utils/idGenerator';
|
|
4
4
|
import { extractAllButtonsForTranslation, validateTranslationResults, } from '../utilities/translation/translationProcessor';
|
|
5
5
|
import { encodeBase64, decodeText } from '../utils/io';
|
|
6
|
-
import { getZipAdapter } from '../utils/zip';
|
|
7
6
|
const OBF_FORMAT_VERSION = 'open-board-0.1';
|
|
8
7
|
/**
|
|
9
8
|
* Map OBF hidden value to AAC standard visibility
|
|
@@ -167,8 +166,7 @@ class ObfProcessor extends BaseProcessor {
|
|
|
167
166
|
buttonParameters.image_id = btn.image_id;
|
|
168
167
|
}
|
|
169
168
|
return new AACButton({
|
|
170
|
-
|
|
171
|
-
id: `${pageId}::${btn?.id || ''}`,
|
|
169
|
+
id: String(btn.id),
|
|
172
170
|
label: String(btn?.label || ''),
|
|
173
171
|
message: String(btn?.vocalization || btn?.label || ''),
|
|
174
172
|
visibility: mapObfVisibility(btn.hidden),
|
|
@@ -217,7 +215,7 @@ class ObfProcessor extends BaseProcessor {
|
|
|
217
215
|
return;
|
|
218
216
|
if (rowIndex >= rows || colIndex >= cols)
|
|
219
217
|
return;
|
|
220
|
-
const aacBtn = buttonMap.get(
|
|
218
|
+
const aacBtn = buttonMap.get(String(cellId));
|
|
221
219
|
if (aacBtn) {
|
|
222
220
|
grid[rowIndex][colIndex] = aacBtn;
|
|
223
221
|
}
|
|
@@ -230,7 +228,7 @@ class ObfProcessor extends BaseProcessor {
|
|
|
230
228
|
const row = Math.floor(btn.box_id / cols);
|
|
231
229
|
const col = btn.box_id % cols;
|
|
232
230
|
if (row < rows && col < cols) {
|
|
233
|
-
const aacBtn = buttonMap.get(
|
|
231
|
+
const aacBtn = buttonMap.get(String(btn.id));
|
|
234
232
|
if (aacBtn) {
|
|
235
233
|
grid[row][col] = aacBtn;
|
|
236
234
|
}
|
|
@@ -287,7 +285,7 @@ class ObfProcessor extends BaseProcessor {
|
|
|
287
285
|
// Detailed logging for debugging input
|
|
288
286
|
const bufferLength = typeof filePathOrBuffer === 'string'
|
|
289
287
|
? null
|
|
290
|
-
: readBinaryFromInput(filePathOrBuffer).byteLength;
|
|
288
|
+
: (await readBinaryFromInput(filePathOrBuffer)).byteLength;
|
|
291
289
|
console.log('[OBF] loadIntoTree called with:', {
|
|
292
290
|
type: typeof filePathOrBuffer,
|
|
293
291
|
isBuffer: typeof Buffer !== 'undefined' && Buffer.isBuffer(filePathOrBuffer),
|
|
@@ -297,9 +295,9 @@ class ObfProcessor extends BaseProcessor {
|
|
|
297
295
|
});
|
|
298
296
|
const tree = new AACTree();
|
|
299
297
|
// Helper: try to parse JSON OBF
|
|
300
|
-
function tryParseObfJson(data) {
|
|
298
|
+
async function tryParseObfJson(data) {
|
|
301
299
|
try {
|
|
302
|
-
const str = typeof data === 'string' ? data : readTextFromInput(data);
|
|
300
|
+
const str = typeof data === 'string' ? data : await readTextFromInput(data);
|
|
303
301
|
// Check for empty or whitespace-only content
|
|
304
302
|
if (!str.trim()) {
|
|
305
303
|
return null;
|
|
@@ -321,8 +319,8 @@ class ObfProcessor extends BaseProcessor {
|
|
|
321
319
|
// If input is a string path and ends with .obf, treat as JSON
|
|
322
320
|
if (typeof filePathOrBuffer === 'string' && filePathOrBuffer.toLowerCase().endsWith('.obf')) {
|
|
323
321
|
try {
|
|
324
|
-
const content = readTextFromInput(filePathOrBuffer);
|
|
325
|
-
const boardData = tryParseObfJson(content);
|
|
322
|
+
const content = await readTextFromInput(filePathOrBuffer);
|
|
323
|
+
const boardData = await tryParseObfJson(content);
|
|
326
324
|
if (boardData) {
|
|
327
325
|
console.log('[OBF] Detected .obf file, parsed as JSON');
|
|
328
326
|
const page = await this.processBoard(boardData, filePathOrBuffer, false);
|
|
@@ -350,17 +348,17 @@ class ObfProcessor extends BaseProcessor {
|
|
|
350
348
|
}
|
|
351
349
|
}
|
|
352
350
|
// Detect likely zip signature first
|
|
353
|
-
function isLikelyZip(input) {
|
|
351
|
+
async function isLikelyZip(input) {
|
|
354
352
|
if (typeof input === 'string') {
|
|
355
353
|
const lowered = input.toLowerCase();
|
|
356
354
|
return lowered.endsWith('.zip') || lowered.endsWith('.obz');
|
|
357
355
|
}
|
|
358
|
-
const bytes = readBinaryFromInput(input);
|
|
356
|
+
const bytes = await readBinaryFromInput(input);
|
|
359
357
|
return bytes.length >= 2 && bytes[0] === 0x50 && bytes[1] === 0x4b;
|
|
360
358
|
}
|
|
361
359
|
// Check if input is a buffer or string that parses as OBF JSON; throw if neither JSON nor ZIP
|
|
362
|
-
if (!isLikelyZip(filePathOrBuffer)) {
|
|
363
|
-
const asJson = tryParseObfJson(filePathOrBuffer);
|
|
360
|
+
if (!(await isLikelyZip(filePathOrBuffer))) {
|
|
361
|
+
const asJson = await tryParseObfJson(filePathOrBuffer);
|
|
364
362
|
if (!asJson)
|
|
365
363
|
throw new Error('Invalid OBF content: not JSON and not ZIP');
|
|
366
364
|
console.log('[OBF] Detected buffer/string as OBF JSON');
|
|
@@ -399,7 +397,7 @@ class ObfProcessor extends BaseProcessor {
|
|
|
399
397
|
try {
|
|
400
398
|
const content = await this.zipFile.readFile(manifestFile[0]);
|
|
401
399
|
const data = decodeText(content);
|
|
402
|
-
const str = typeof data === 'string' ? data : readTextFromInput(data);
|
|
400
|
+
const str = typeof data === 'string' ? data : await readTextFromInput(data);
|
|
403
401
|
if (!str.trim())
|
|
404
402
|
throw new Error('Manifest object missing');
|
|
405
403
|
const manifestObject = JSON.parse(str);
|
|
@@ -423,7 +421,7 @@ class ObfProcessor extends BaseProcessor {
|
|
|
423
421
|
for (const entryName of obfEntries) {
|
|
424
422
|
try {
|
|
425
423
|
const content = await this.zipFile.readFile(entryName);
|
|
426
|
-
const boardData = tryParseObfJson(decodeText(content));
|
|
424
|
+
const boardData = await tryParseObfJson(decodeText(content));
|
|
427
425
|
if (boardData) {
|
|
428
426
|
const page = await this.processBoard(boardData, entryName, true);
|
|
429
427
|
tree.addPage(page);
|
|
@@ -567,10 +565,10 @@ class ObfProcessor extends BaseProcessor {
|
|
|
567
565
|
});
|
|
568
566
|
// Save the translated tree and return its content
|
|
569
567
|
await this.saveFromTree(tree, outputPath);
|
|
570
|
-
return readBinaryFromInput(outputPath);
|
|
568
|
+
return await readBinaryFromInput(outputPath);
|
|
571
569
|
}
|
|
572
570
|
async saveFromTree(tree, outputPath) {
|
|
573
|
-
const { writeTextToPath, writeBinaryToPath } = this.options.fileAdapter;
|
|
571
|
+
const { writeTextToPath, writeBinaryToPath, pathExists } = this.options.fileAdapter;
|
|
574
572
|
if (outputPath.endsWith('.obf')) {
|
|
575
573
|
// Save as single OBF JSON file
|
|
576
574
|
const rootPage = tree.rootId ? tree.getPage(tree.rootId) : Object.values(tree.pages)[0];
|
|
@@ -578,7 +576,7 @@ class ObfProcessor extends BaseProcessor {
|
|
|
578
576
|
throw new Error('No pages to save');
|
|
579
577
|
}
|
|
580
578
|
const obfBoard = this.createObfBoardFromPage(rootPage, 'Exported Board', tree.metadata);
|
|
581
|
-
writeTextToPath(outputPath, JSON.stringify(obfBoard, null, 2));
|
|
579
|
+
await writeTextToPath(outputPath, JSON.stringify(obfBoard, null, 2));
|
|
582
580
|
}
|
|
583
581
|
else {
|
|
584
582
|
const files = Object.values(tree.pages).map((page) => {
|
|
@@ -590,9 +588,10 @@ class ObfProcessor extends BaseProcessor {
|
|
|
590
588
|
data: new TextEncoder().encode(obfContent),
|
|
591
589
|
};
|
|
592
590
|
});
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
591
|
+
const fileExists = await pathExists(outputPath);
|
|
592
|
+
this.zipFile = await this.options.zipAdapter(fileExists ? outputPath : undefined, this.options.fileAdapter);
|
|
593
|
+
const zipData = await this.zipFile.writeFiles(files);
|
|
594
|
+
await writeBinaryToPath(outputPath, zipData);
|
|
596
595
|
}
|
|
597
596
|
}
|
|
598
597
|
/**
|
|
@@ -625,7 +624,7 @@ class ObfProcessor extends BaseProcessor {
|
|
|
625
624
|
* This method uses shared translation utilities that work across all AAC formats.
|
|
626
625
|
*
|
|
627
626
|
* @param filePathOrBuffer - Path to OBF/OBZ file or buffer
|
|
628
|
-
* @returns
|
|
627
|
+
* @returns Promise resolving to symbol information for LLM processing
|
|
629
628
|
*/
|
|
630
629
|
async extractSymbolsForLLM(filePathOrBuffer) {
|
|
631
630
|
const tree = await this.loadIntoTree(filePathOrBuffer);
|
|
@@ -655,7 +654,7 @@ class ObfProcessor extends BaseProcessor {
|
|
|
655
654
|
* @param llmTranslations - Array of LLM translations with symbol info
|
|
656
655
|
* @param outputPath - Where to save the translated OBF/OBZ file
|
|
657
656
|
* @param options - Translation options (e.g., allowPartial for testing)
|
|
658
|
-
* @returns
|
|
657
|
+
* @returns Promise resolving to a buffer of the translated OBF/OBZ file
|
|
659
658
|
*/
|
|
660
659
|
async processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
|
|
661
660
|
const { readBinaryFromInput } = this.options.fileAdapter;
|
|
@@ -697,7 +696,7 @@ class ObfProcessor extends BaseProcessor {
|
|
|
697
696
|
});
|
|
698
697
|
// Save and return
|
|
699
698
|
await this.saveFromTree(tree, outputPath);
|
|
700
|
-
return readBinaryFromInput(outputPath);
|
|
699
|
+
return await readBinaryFromInput(outputPath);
|
|
701
700
|
}
|
|
702
701
|
getObfValidator() {
|
|
703
702
|
try {
|
|
@@ -50,8 +50,7 @@ class OpmlProcessor extends BaseProcessor {
|
|
|
50
50
|
}
|
|
51
51
|
async extractTexts(filePathOrBuffer) {
|
|
52
52
|
const { readTextFromInput } = this.options.fileAdapter;
|
|
53
|
-
await
|
|
54
|
-
const content = readTextFromInput(filePathOrBuffer);
|
|
53
|
+
const content = await readTextFromInput(filePathOrBuffer);
|
|
55
54
|
const parser = new XMLParser({ ignoreAttributes: false });
|
|
56
55
|
const data = parser.parse(content);
|
|
57
56
|
const texts = [];
|
|
@@ -83,10 +82,9 @@ class OpmlProcessor extends BaseProcessor {
|
|
|
83
82
|
}
|
|
84
83
|
async loadIntoTree(filePathOrBuffer) {
|
|
85
84
|
const { readBinaryFromInput, readTextFromInput } = this.options.fileAdapter;
|
|
86
|
-
await Promise.resolve();
|
|
87
85
|
const filename = typeof filePathOrBuffer === 'string' ? getBasename(filePathOrBuffer) : 'upload.opml';
|
|
88
|
-
const buffer = readBinaryFromInput(filePathOrBuffer);
|
|
89
|
-
const content = readTextFromInput(buffer);
|
|
86
|
+
const buffer = await readBinaryFromInput(filePathOrBuffer);
|
|
87
|
+
const content = await readTextFromInput(buffer);
|
|
90
88
|
try {
|
|
91
89
|
if (!content || !content.trim()) {
|
|
92
90
|
const validationResult = buildValidationResultFromMessage({
|
|
@@ -170,8 +168,7 @@ class OpmlProcessor extends BaseProcessor {
|
|
|
170
168
|
}
|
|
171
169
|
async processTexts(filePathOrBuffer, translations, outputPath) {
|
|
172
170
|
const { writeBinaryToPath, readTextFromInput } = this.options.fileAdapter;
|
|
173
|
-
await
|
|
174
|
-
const content = readTextFromInput(filePathOrBuffer);
|
|
171
|
+
const content = await readTextFromInput(filePathOrBuffer);
|
|
175
172
|
let translatedContent = content;
|
|
176
173
|
// Apply translations to text attributes in OPML outline elements
|
|
177
174
|
translations.forEach((translation, originalText) => {
|
|
@@ -182,12 +179,11 @@ class OpmlProcessor extends BaseProcessor {
|
|
|
182
179
|
}
|
|
183
180
|
});
|
|
184
181
|
const resultBuffer = encodeText(translatedContent);
|
|
185
|
-
writeBinaryToPath(outputPath, resultBuffer);
|
|
182
|
+
await writeBinaryToPath(outputPath, resultBuffer);
|
|
186
183
|
return resultBuffer;
|
|
187
184
|
}
|
|
188
185
|
async saveFromTree(tree, outputPath) {
|
|
189
186
|
const { writeTextToPath } = this.options.fileAdapter;
|
|
190
|
-
await Promise.resolve();
|
|
191
187
|
// Helper to recursively build outline nodes with cycle detection
|
|
192
188
|
function buildOutline(page, visited = new Set()) {
|
|
193
189
|
// Prevent infinite recursion by tracking visited pages
|
|
@@ -257,7 +253,7 @@ class OpmlProcessor extends BaseProcessor {
|
|
|
257
253
|
attributeNamePrefix: '@_',
|
|
258
254
|
});
|
|
259
255
|
const xml = '<?xml version="1.0" encoding="UTF-8"?>\n' + builder.build(opmlObj);
|
|
260
|
-
writeTextToPath(outputPath, xml);
|
|
256
|
+
await writeTextToPath(outputPath, xml);
|
|
261
257
|
}
|
|
262
258
|
/**
|
|
263
259
|
* Extract strings with metadata for aac-tools-platform compatibility
|
|
@@ -10,7 +10,7 @@ import { requireBetterSqlite3 } from '../../utils/sqlite';
|
|
|
10
10
|
// We extract PNG/JPEG images but skip vector graphics (requires renderer).
|
|
11
11
|
// NOTE: Snap buttons currently do not populate resolvedImageEntry; these helpers
|
|
12
12
|
// therefore return empty collections until image resolution is implemented.
|
|
13
|
-
function collectFiles(root, matcher, maxDepth = 3, fileAdapter = defaultFileAdapter) {
|
|
13
|
+
async function collectFiles(root, matcher, maxDepth = 3, fileAdapter = defaultFileAdapter) {
|
|
14
14
|
const { listDir, join, isDirectory } = fileAdapter;
|
|
15
15
|
const results = new Set();
|
|
16
16
|
const stack = [{ dir: root, depth: 0 }];
|
|
@@ -22,14 +22,14 @@ function collectFiles(root, matcher, maxDepth = 3, fileAdapter = defaultFileAdap
|
|
|
22
22
|
continue;
|
|
23
23
|
let entries;
|
|
24
24
|
try {
|
|
25
|
-
entries = listDir(current.dir);
|
|
25
|
+
entries = await listDir(current.dir);
|
|
26
26
|
}
|
|
27
27
|
catch (error) {
|
|
28
28
|
continue;
|
|
29
29
|
}
|
|
30
30
|
for (const entry of entries) {
|
|
31
31
|
const fullPath = join(current.dir, entry);
|
|
32
|
-
if (isDirectory(entry)) {
|
|
32
|
+
if (await isDirectory(entry)) {
|
|
33
33
|
stack.push({ dir: fullPath, depth: current.depth + 1 });
|
|
34
34
|
}
|
|
35
35
|
else if (matcher(fullPath)) {
|
|
@@ -83,15 +83,15 @@ export function getAllowedImageEntries(tree) {
|
|
|
83
83
|
* @param entryPath Symbol identifier (e.g., "SYM:12345")
|
|
84
84
|
* @returns Image data buffer or null if not found
|
|
85
85
|
*/
|
|
86
|
-
export function openImage(dbOrFile, entryPath, fileAdapter = defaultFileAdapter) {
|
|
86
|
+
export async function openImage(dbOrFile, entryPath, fileAdapter = defaultFileAdapter) {
|
|
87
87
|
const { mkTempDir, join, writeBinaryToPath, removePath, dirname } = fileAdapter;
|
|
88
88
|
let dbPath;
|
|
89
89
|
let cleanupNeeded = false;
|
|
90
90
|
// Handle Buffer input by writing to temp file
|
|
91
91
|
if (Buffer.isBuffer(dbOrFile)) {
|
|
92
|
-
const tempDir = mkTempDir(join(process.cwd(), 'snap-'));
|
|
92
|
+
const tempDir = await mkTempDir(join(process.cwd(), 'snap-'));
|
|
93
93
|
dbPath = join(tempDir, 'temp.sps');
|
|
94
|
-
writeBinaryToPath(dbPath, dbOrFile);
|
|
94
|
+
await writeBinaryToPath(dbPath, dbOrFile);
|
|
95
95
|
cleanupNeeded = true;
|
|
96
96
|
}
|
|
97
97
|
else if (typeof dbOrFile === 'string') {
|
|
@@ -126,9 +126,9 @@ export function openImage(dbOrFile, entryPath, fileAdapter = defaultFileAdapter)
|
|
|
126
126
|
}
|
|
127
127
|
if (cleanupNeeded && dbPath) {
|
|
128
128
|
try {
|
|
129
|
-
removePath(dbPath);
|
|
129
|
+
await removePath(dbPath);
|
|
130
130
|
const dir = dirname(dbPath);
|
|
131
|
-
removePath(dir);
|
|
131
|
+
await removePath(dir);
|
|
132
132
|
}
|
|
133
133
|
catch (e) {
|
|
134
134
|
// Ignore cleanup errors
|
|
@@ -142,7 +142,7 @@ export function openImage(dbOrFile, entryPath, fileAdapter = defaultFileAdapter)
|
|
|
142
142
|
* @param packageNamePattern Optional pattern to filter package names (default: 'TobiiDynavox')
|
|
143
143
|
* @returns Array of Snap package path information
|
|
144
144
|
*/
|
|
145
|
-
export function findSnapPackages(packageNamePattern = 'TobiiDynavox', fileAdapter = defaultFileAdapter) {
|
|
145
|
+
export async function findSnapPackages(packageNamePattern = 'TobiiDynavox', fileAdapter = defaultFileAdapter) {
|
|
146
146
|
const { join, listDir, isDirectory, pathExists } = fileAdapter;
|
|
147
147
|
const results = [];
|
|
148
148
|
// Only works on Windows
|
|
@@ -156,13 +156,13 @@ export function findSnapPackages(packageNamePattern = 'TobiiDynavox', fileAdapte
|
|
|
156
156
|
}
|
|
157
157
|
const packagesPath = join(localAppData, 'Packages');
|
|
158
158
|
// Check if Packages directory exists
|
|
159
|
-
if (!pathExists(packagesPath)) {
|
|
159
|
+
if (!(await pathExists(packagesPath))) {
|
|
160
160
|
return results;
|
|
161
161
|
}
|
|
162
162
|
// Enumerate packages
|
|
163
|
-
const packages = listDir(packagesPath);
|
|
163
|
+
const packages = await listDir(packagesPath);
|
|
164
164
|
for (const packageDir of packages) {
|
|
165
|
-
if (!isDirectory(packageDir))
|
|
165
|
+
if (!(await isDirectory(packageDir)))
|
|
166
166
|
continue;
|
|
167
167
|
const packageName = packageDir;
|
|
168
168
|
// Filter by pattern
|
|
@@ -185,8 +185,8 @@ export function findSnapPackages(packageNamePattern = 'TobiiDynavox', fileAdapte
|
|
|
185
185
|
* @param packageNamePattern Optional pattern to filter package names (default: 'TobiiDynavox')
|
|
186
186
|
* @returns Path to the first matching Snap package, or null if not found
|
|
187
187
|
*/
|
|
188
|
-
export function findSnapPackagePath(packageNamePattern = 'TobiiDynavox', fileAdapter) {
|
|
189
|
-
const packages = findSnapPackages(packageNamePattern, fileAdapter);
|
|
188
|
+
export async function findSnapPackagePath(packageNamePattern = 'TobiiDynavox', fileAdapter) {
|
|
189
|
+
const packages = await findSnapPackages(packageNamePattern, fileAdapter);
|
|
190
190
|
return packages.length > 0 ? packages[0].packagePath : null;
|
|
191
191
|
}
|
|
192
192
|
/**
|
|
@@ -196,28 +196,28 @@ export function findSnapPackagePath(packageNamePattern = 'TobiiDynavox', fileAda
|
|
|
196
196
|
* @param packageNamePattern Optional package filter (default TobiiDynavox)
|
|
197
197
|
* @returns Array of user info with vocab paths
|
|
198
198
|
*/
|
|
199
|
-
export function findSnapUsers(packageNamePattern = 'TobiiDynavox', fileAdapter = defaultFileAdapter) {
|
|
199
|
+
export async function findSnapUsers(packageNamePattern = 'TobiiDynavox', fileAdapter = defaultFileAdapter) {
|
|
200
200
|
const { join, listDir, isDirectory, pathExists } = fileAdapter;
|
|
201
201
|
const results = [];
|
|
202
202
|
if (process.platform !== 'win32') {
|
|
203
203
|
return results;
|
|
204
204
|
}
|
|
205
|
-
const packagePath = findSnapPackagePath(packageNamePattern, fileAdapter);
|
|
205
|
+
const packagePath = await findSnapPackagePath(packageNamePattern, fileAdapter);
|
|
206
206
|
if (!packagePath) {
|
|
207
207
|
return results;
|
|
208
208
|
}
|
|
209
209
|
const usersRoot = join(packagePath, 'LocalState', 'Users');
|
|
210
|
-
if (!pathExists(usersRoot)) {
|
|
210
|
+
if (!(await pathExists(usersRoot))) {
|
|
211
211
|
return results;
|
|
212
212
|
}
|
|
213
|
-
const entries = listDir(usersRoot);
|
|
213
|
+
const entries = await listDir(usersRoot);
|
|
214
214
|
for (const entry of entries) {
|
|
215
|
-
if (!isDirectory(entry))
|
|
215
|
+
if (!(await isDirectory(entry)))
|
|
216
216
|
continue;
|
|
217
217
|
if (entry.toLowerCase().startsWith('swiftkey'))
|
|
218
218
|
continue;
|
|
219
219
|
const userPath = join(usersRoot, entry);
|
|
220
|
-
const vocabPaths = collectFiles(userPath, (full) => {
|
|
220
|
+
const vocabPaths = await collectFiles(userPath, (full) => {
|
|
221
221
|
const ext = extname(full).toLowerCase();
|
|
222
222
|
return ext === '.sps' || ext === '.spb';
|
|
223
223
|
}, 2, fileAdapter);
|
|
@@ -235,8 +235,9 @@ export function findSnapUsers(packageNamePattern = 'TobiiDynavox', fileAdapter =
|
|
|
235
235
|
* @param packageNamePattern Optional package filter
|
|
236
236
|
* @returns Array of vocab file paths
|
|
237
237
|
*/
|
|
238
|
-
export function findSnapUserVocabularies(userId, packageNamePattern = 'TobiiDynavox', fileAdapter) {
|
|
239
|
-
const
|
|
238
|
+
export async function findSnapUserVocabularies(userId, packageNamePattern = 'TobiiDynavox', fileAdapter) {
|
|
239
|
+
const allUsers = await findSnapUsers(packageNamePattern, fileAdapter);
|
|
240
|
+
const users = allUsers.filter((u) => !userId || u.userId === userId);
|
|
240
241
|
return users.flatMap((u) => u.vocabPaths);
|
|
241
242
|
}
|
|
242
243
|
/**
|
|
@@ -246,12 +247,13 @@ export function findSnapUserVocabularies(userId, packageNamePattern = 'TobiiDyna
|
|
|
246
247
|
* @param packageNamePattern Optional package filter
|
|
247
248
|
* @returns Array of history file paths (may be empty if not found)
|
|
248
249
|
*/
|
|
249
|
-
export function findSnapUserHistory(userId, packageNamePattern = 'TobiiDynavox', fileAdapter = defaultFileAdapter) {
|
|
250
|
+
export async function findSnapUserHistory(userId, packageNamePattern = 'TobiiDynavox', fileAdapter = defaultFileAdapter) {
|
|
250
251
|
const { basename } = fileAdapter;
|
|
251
|
-
const
|
|
252
|
+
const allUsers = await findSnapUsers(packageNamePattern, fileAdapter);
|
|
253
|
+
const user = allUsers.find((u) => u.userId === userId);
|
|
252
254
|
if (!user)
|
|
253
255
|
return [];
|
|
254
|
-
return collectFiles(user.userPath, (full) => basename(full).toLowerCase().includes('history'), 2, fileAdapter);
|
|
256
|
+
return await collectFiles(user.userPath, (full) => basename(full).toLowerCase().includes('history'), 2, fileAdapter);
|
|
255
257
|
}
|
|
256
258
|
/**
|
|
257
259
|
* Check whether TD Snap appears to be installed (Windows only)
|
|
@@ -264,9 +266,9 @@ export function isSnapInstalled(packageNamePattern = 'TobiiDynavox') {
|
|
|
264
266
|
/**
|
|
265
267
|
* Read Snap usage history from a pageset file (.sps/.spb)
|
|
266
268
|
*/
|
|
267
|
-
export function readSnapUsage(pagesetPath, fileAdapter = defaultFileAdapter) {
|
|
269
|
+
export async function readSnapUsage(pagesetPath, fileAdapter = defaultFileAdapter) {
|
|
268
270
|
const { pathExists } = fileAdapter;
|
|
269
|
-
if (!pathExists(pagesetPath))
|
|
271
|
+
if (!(await pathExists(pagesetPath)))
|
|
270
272
|
return [];
|
|
271
273
|
const Database = requireBetterSqlite3();
|
|
272
274
|
const db = new Database(pagesetPath, { readonly: true });
|
|
@@ -323,8 +325,10 @@ export function readSnapUsage(pagesetPath, fileAdapter = defaultFileAdapter) {
|
|
|
323
325
|
/**
|
|
324
326
|
* Read Snap usage history for a user (all pagesets)
|
|
325
327
|
*/
|
|
326
|
-
export function readSnapUsageForUser(userId, packageNamePattern = 'TobiiDynavox') {
|
|
327
|
-
const
|
|
328
|
+
export async function readSnapUsageForUser(userId, packageNamePattern = 'TobiiDynavox') {
|
|
329
|
+
const allUsers = await findSnapUsers(packageNamePattern);
|
|
330
|
+
const users = allUsers.filter((u) => !userId || u.userId === userId);
|
|
328
331
|
const pagesets = users.flatMap((u) => u.vocabPaths);
|
|
329
|
-
|
|
332
|
+
const usage = await Promise.all(pagesets.map(async (p) => await readSnapUsage(p)));
|
|
333
|
+
return usage.flat();
|
|
330
334
|
}
|