@willwade/aac-processors 0.1.3 → 0.1.4
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/gridset/crypto.js +15 -8
- package/dist/browser/processors/gridsetProcessor.js +3 -5
- package/dist/browser/processors/obfProcessor.js +6 -2
- package/dist/browser/utils/io.js +25 -4
- package/dist/cli/index.js +161 -0
- package/dist/processors/gridset/crypto.js +15 -8
- package/dist/processors/gridsetProcessor.js +2 -4
- package/dist/processors/obfProcessor.js +6 -2
- package/dist/processors/snapProcessor.js +16 -8
- package/dist/utilities/analytics/history.d.ts +25 -0
- package/dist/utilities/analytics/history.js +56 -0
- package/dist/utils/io.js +25 -4
- package/package.json +2 -2
|
@@ -12,11 +12,15 @@
|
|
|
12
12
|
* @returns Decrypted and inflated buffer
|
|
13
13
|
*/
|
|
14
14
|
export function decryptGridsetEntry(buffer, password) {
|
|
15
|
+
const nodeRequire = typeof require === 'function' ? require : undefined;
|
|
16
|
+
if (!nodeRequire) {
|
|
17
|
+
throw new Error('Crypto utilities are not available in this environment.');
|
|
18
|
+
}
|
|
15
19
|
// Dynamic require to avoid breaking in browser environments
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
const zlib =
|
|
20
|
+
const cryptoModule = 'crypto';
|
|
21
|
+
const zlibModule = 'zlib';
|
|
22
|
+
const crypto = nodeRequire(cryptoModule);
|
|
23
|
+
const zlib = nodeRequire(zlibModule);
|
|
20
24
|
const pwd = (password || 'Chocolate').padEnd(32, ' ');
|
|
21
25
|
const key = Buffer.from(pwd.slice(0, 32), 'utf8');
|
|
22
26
|
const iv = Buffer.from(pwd.slice(0, 16), 'utf8');
|
|
@@ -41,10 +45,13 @@ export function decryptGridsetEntry(buffer, password) {
|
|
|
41
45
|
*/
|
|
42
46
|
export function isCryptoAvailable() {
|
|
43
47
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
const nodeRequire = typeof require === 'function' ? require : undefined;
|
|
49
|
+
if (!nodeRequire)
|
|
50
|
+
return false;
|
|
51
|
+
const cryptoModule = 'crypto';
|
|
52
|
+
const zlibModule = 'zlib';
|
|
53
|
+
nodeRequire(cryptoModule);
|
|
54
|
+
nodeRequire(zlibModule);
|
|
48
55
|
return true;
|
|
49
56
|
}
|
|
50
57
|
catch {
|
|
@@ -13,7 +13,7 @@ import { parseSymbolReference } from './gridset/symbols';
|
|
|
13
13
|
import { isSymbolLibraryReference } from './gridset/resolver';
|
|
14
14
|
import { generateCloneId } from '../utilities/analytics/utils/idGenerator';
|
|
15
15
|
import { translateWithSymbols, extractSymbolsFromButton } from './gridset/symbolAlignment';
|
|
16
|
-
import { readBinaryFromInput, decodeText } from '../utils/io';
|
|
16
|
+
import { readBinaryFromInput, decodeText, writeBinaryToPath } from '../utils/io';
|
|
17
17
|
let JSZipModule;
|
|
18
18
|
async function getJSZip() {
|
|
19
19
|
if (!JSZipModule) {
|
|
@@ -1615,8 +1615,7 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
1615
1615
|
if (Object.keys(tree.pages).length === 0) {
|
|
1616
1616
|
// Create empty zip for empty tree
|
|
1617
1617
|
const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
|
|
1618
|
-
|
|
1619
|
-
require('fs').writeFileSync(outputPath, zipBuffer);
|
|
1618
|
+
writeBinaryToPath(outputPath, zipBuffer);
|
|
1620
1619
|
return;
|
|
1621
1620
|
}
|
|
1622
1621
|
// Collect all unique styles from pages and buttons
|
|
@@ -1911,8 +1910,7 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
1911
1910
|
zip.file('FileMap.xml', fileMapXmlContent, { binary: false });
|
|
1912
1911
|
// Write the zip file
|
|
1913
1912
|
const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
|
|
1914
|
-
|
|
1915
|
-
require('fs').writeFileSync(outputPath, zipBuffer);
|
|
1913
|
+
writeBinaryToPath(outputPath, zipBuffer);
|
|
1916
1914
|
}
|
|
1917
1915
|
// Helper method to calculate column definitions based on page layout
|
|
1918
1916
|
calculateColumnDefinitions(page) {
|
|
@@ -146,11 +146,15 @@ class ObfProcessor extends BaseProcessor {
|
|
|
146
146
|
async processBoard(boardData, _boardPath) {
|
|
147
147
|
const sourceButtons = boardData.buttons || [];
|
|
148
148
|
// Calculate page ID first (used to make button IDs unique)
|
|
149
|
-
const
|
|
149
|
+
const isZipEntry = _boardPath &&
|
|
150
|
+
_boardPath.endsWith('.obf') &&
|
|
151
|
+
!_boardPath.includes('/') &&
|
|
152
|
+
!_boardPath.includes('\\');
|
|
153
|
+
const pageId = isZipEntry
|
|
150
154
|
? _boardPath // Zip entry - use filename to match navigation paths
|
|
151
155
|
: boardData?.id
|
|
152
156
|
? String(boardData.id)
|
|
153
|
-
: _boardPath?.split(
|
|
157
|
+
: _boardPath?.split(/[/\\]/).pop() || '';
|
|
154
158
|
const buttons = await Promise.all(sourceButtons.map(async (btn) => {
|
|
155
159
|
const semanticAction = btn.load_board
|
|
156
160
|
? {
|
package/dist/browser/utils/io.js
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
let cachedFs = null;
|
|
2
2
|
let cachedPath = null;
|
|
3
|
+
let cachedRequire = undefined;
|
|
4
|
+
function getNodeRequire() {
|
|
5
|
+
if (cachedRequire === undefined) {
|
|
6
|
+
if (typeof require === 'function') {
|
|
7
|
+
cachedRequire = require;
|
|
8
|
+
}
|
|
9
|
+
else if (typeof globalThis !== 'undefined') {
|
|
10
|
+
const maybeRequire = globalThis.require;
|
|
11
|
+
cachedRequire = typeof maybeRequire === 'function' ? maybeRequire : null;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
cachedRequire = null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (!cachedRequire) {
|
|
18
|
+
throw new Error('File system access is not available in this environment.');
|
|
19
|
+
}
|
|
20
|
+
return cachedRequire;
|
|
21
|
+
}
|
|
3
22
|
export function getFs() {
|
|
4
23
|
if (!cachedFs) {
|
|
5
24
|
try {
|
|
6
|
-
|
|
7
|
-
|
|
25
|
+
const nodeRequire = getNodeRequire();
|
|
26
|
+
const fsModule = 'fs';
|
|
27
|
+
cachedFs = nodeRequire(fsModule);
|
|
8
28
|
}
|
|
9
29
|
catch {
|
|
10
30
|
throw new Error('File system access is not available in this environment.');
|
|
@@ -18,8 +38,9 @@ export function getFs() {
|
|
|
18
38
|
export function getPath() {
|
|
19
39
|
if (!cachedPath) {
|
|
20
40
|
try {
|
|
21
|
-
|
|
22
|
-
|
|
41
|
+
const nodeRequire = getNodeRequire();
|
|
42
|
+
const pathModule = 'path';
|
|
43
|
+
cachedPath = nodeRequire(pathModule);
|
|
23
44
|
}
|
|
24
45
|
catch {
|
|
25
46
|
throw new Error('Path utilities are not available in this environment.');
|
package/dist/cli/index.js
CHANGED
|
@@ -7,6 +7,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
const commander_1 = require("commander");
|
|
8
8
|
const prettyPrint_1 = require("./prettyPrint");
|
|
9
9
|
const analyze_1 = require("../core/analyze");
|
|
10
|
+
const history_1 = require("../utilities/analytics/history");
|
|
11
|
+
const analytics_1 = require("../utilities/analytics");
|
|
12
|
+
const aac_1 = require("../types/aac");
|
|
10
13
|
const path_1 = __importDefault(require("path"));
|
|
11
14
|
const fs_1 = __importDefault(require("fs"));
|
|
12
15
|
// Helper function to detect format from file/folder path
|
|
@@ -276,6 +279,164 @@ commander_1.program
|
|
|
276
279
|
process.exit(1);
|
|
277
280
|
}
|
|
278
281
|
});
|
|
282
|
+
commander_1.program
|
|
283
|
+
.command('history <input>')
|
|
284
|
+
.option('--format <format>', 'Output format: raw or baton', 'raw')
|
|
285
|
+
.option('--out <path>', 'Write output to a file instead of stdout')
|
|
286
|
+
.option('--source <source>', 'History source: auto, grid3, snap', 'auto')
|
|
287
|
+
.option('--anonymous-uuid <uuid>', 'Anonymous UUID for baton export')
|
|
288
|
+
.option('--export-date <iso>', 'Export date for baton export (ISO string)')
|
|
289
|
+
.option('--encryption <mode>', 'Encryption label for baton export', 'none')
|
|
290
|
+
.option('--version <version>', 'Baton export version', '1.0')
|
|
291
|
+
.action((input, options) => {
|
|
292
|
+
try {
|
|
293
|
+
if (!fs_1.default.existsSync(input)) {
|
|
294
|
+
throw new Error(`File not found: ${input}`);
|
|
295
|
+
}
|
|
296
|
+
const normalizedSource = (options.source || 'auto').toLowerCase();
|
|
297
|
+
const ext = path_1.default.extname(input).toLowerCase();
|
|
298
|
+
const isGrid3Db = ext === '.sqlite' || path_1.default.basename(input).toLowerCase() === 'history.sqlite';
|
|
299
|
+
const isSnap = ext === '.sps' || ext === '.spb';
|
|
300
|
+
let entries;
|
|
301
|
+
if (normalizedSource === 'grid3' || (normalizedSource === 'auto' && isGrid3Db)) {
|
|
302
|
+
entries = (0, history_1.readGrid3History)(input);
|
|
303
|
+
}
|
|
304
|
+
else if (normalizedSource === 'snap' || (normalizedSource === 'auto' && isSnap)) {
|
|
305
|
+
entries = (0, history_1.readSnapUsage)(input);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
throw new Error('Unable to detect history source. Use --source grid3 or --source snap.');
|
|
309
|
+
}
|
|
310
|
+
const format = (options.format || 'raw').toLowerCase();
|
|
311
|
+
let payload = entries;
|
|
312
|
+
if (format === 'baton') {
|
|
313
|
+
payload = (0, history_1.exportHistoryToBaton)(entries, {
|
|
314
|
+
version: options.version,
|
|
315
|
+
exportDate: options.exportDate,
|
|
316
|
+
encryption: options.encryption,
|
|
317
|
+
anonymousUUID: options.anonymousUuid,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
else if (format !== 'raw') {
|
|
321
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
322
|
+
}
|
|
323
|
+
const output = JSON.stringify(payload, null, 2);
|
|
324
|
+
if (options.out) {
|
|
325
|
+
fs_1.default.writeFileSync(options.out, output);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
console.log(output);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
console.error('Error exporting history:', error instanceof Error ? error.message : String(error));
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
commander_1.program
|
|
337
|
+
.command('metrics <file>')
|
|
338
|
+
.option('--format <format>', 'Format type (auto-detected if not specified)')
|
|
339
|
+
.option('--pretty', 'Pretty print JSON output')
|
|
340
|
+
.option('--out <path>', 'Write output to a file instead of stdout')
|
|
341
|
+
.option('--preserve-all-buttons', 'Preserve all buttons including navigation/system buttons')
|
|
342
|
+
.option('--no-exclude-navigation', "Don't exclude navigation buttons (Home, Back)")
|
|
343
|
+
.option('--no-exclude-system', "Don't exclude system buttons (Delete, Clear, etc.)")
|
|
344
|
+
.option('--exclude-buttons <list>', 'Comma-separated list of button labels/terms to exclude')
|
|
345
|
+
.option('--gridset-password <password>', 'Password for encrypted Grid3 archives (.gridsetx)')
|
|
346
|
+
.option('--access-method <method>', 'direct or scanning', 'direct')
|
|
347
|
+
.option('--scanning-pattern <pattern>', 'linear, row-column, or block', 'row-column')
|
|
348
|
+
.option('--selection-method <method>', 'auto-1-switch, step-1-switch, or step-2-switch', 'auto-1-switch')
|
|
349
|
+
.option('--error-correction', 'Enable scanning error correction', false)
|
|
350
|
+
.option('--use-prediction', 'Enable prediction in CARE scoring', false)
|
|
351
|
+
.option('--no-smart-grammar', 'Disable smart grammar word forms')
|
|
352
|
+
.option('--care', 'Include CARE comparison output', false)
|
|
353
|
+
.action(async (file, options) => {
|
|
354
|
+
try {
|
|
355
|
+
const filteringOptions = parseFilteringOptions(options);
|
|
356
|
+
const format = options.format || detectFormat(file);
|
|
357
|
+
const processor = (0, analyze_1.getProcessor)(format, filteringOptions);
|
|
358
|
+
const tree = await processor.loadIntoTree(file);
|
|
359
|
+
const accessMethod = (options.accessMethod || 'direct').toLowerCase();
|
|
360
|
+
const scanningPattern = (options.scanningPattern || 'row-column').toLowerCase();
|
|
361
|
+
const selectionMethodParam = (options.selectionMethod || 'auto-1-switch').toLowerCase();
|
|
362
|
+
const errorCorrection = !!options.errorCorrection;
|
|
363
|
+
let scanningConfig = undefined;
|
|
364
|
+
if (accessMethod === 'scanning') {
|
|
365
|
+
let cellScanningOrder = aac_1.CellScanningOrder.SimpleScan;
|
|
366
|
+
let blockScanEnabled = false;
|
|
367
|
+
switch (scanningPattern) {
|
|
368
|
+
case 'linear':
|
|
369
|
+
cellScanningOrder = aac_1.CellScanningOrder.SimpleScan;
|
|
370
|
+
break;
|
|
371
|
+
case 'row-column':
|
|
372
|
+
cellScanningOrder = aac_1.CellScanningOrder.RowColumnScan;
|
|
373
|
+
break;
|
|
374
|
+
case 'block':
|
|
375
|
+
cellScanningOrder = aac_1.CellScanningOrder.RowColumnScan;
|
|
376
|
+
blockScanEnabled = true;
|
|
377
|
+
break;
|
|
378
|
+
default:
|
|
379
|
+
throw new Error(`Unsupported scanning pattern: ${scanningPattern}`);
|
|
380
|
+
}
|
|
381
|
+
let selectionMethod = aac_1.ScanningSelectionMethod.AutoScan;
|
|
382
|
+
switch (selectionMethodParam) {
|
|
383
|
+
case 'auto-1-switch':
|
|
384
|
+
selectionMethod = aac_1.ScanningSelectionMethod.AutoScan;
|
|
385
|
+
break;
|
|
386
|
+
case 'step-1-switch':
|
|
387
|
+
selectionMethod = aac_1.ScanningSelectionMethod.StepScan1Switch;
|
|
388
|
+
break;
|
|
389
|
+
case 'step-2-switch':
|
|
390
|
+
selectionMethod = aac_1.ScanningSelectionMethod.StepScan2Switch;
|
|
391
|
+
break;
|
|
392
|
+
default:
|
|
393
|
+
throw new Error(`Unsupported selection method: ${selectionMethodParam}`);
|
|
394
|
+
}
|
|
395
|
+
scanningConfig = {
|
|
396
|
+
cellScanningOrder,
|
|
397
|
+
blockScanEnabled,
|
|
398
|
+
selectionMethod,
|
|
399
|
+
errorCorrectionEnabled: errorCorrection,
|
|
400
|
+
errorRate: errorCorrection ? 0.1 : undefined,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
const calculator = new analytics_1.MetricsCalculator();
|
|
404
|
+
const metrics = calculator.analyze(tree, {
|
|
405
|
+
scanningConfig,
|
|
406
|
+
useSmartGrammar: options.smartGrammar,
|
|
407
|
+
});
|
|
408
|
+
let care = undefined;
|
|
409
|
+
if (options.care) {
|
|
410
|
+
const comparison = new analytics_1.ComparisonAnalyzer();
|
|
411
|
+
care = comparison.compare(metrics, metrics, {
|
|
412
|
+
includeSentences: true,
|
|
413
|
+
usePrediction: !!options.usePrediction,
|
|
414
|
+
scanningConfig,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
const result = {
|
|
418
|
+
format,
|
|
419
|
+
filtering: filteringOptions,
|
|
420
|
+
accessMethod,
|
|
421
|
+
scanningPattern,
|
|
422
|
+
selectionMethod: selectionMethodParam,
|
|
423
|
+
errorCorrection,
|
|
424
|
+
metrics,
|
|
425
|
+
care,
|
|
426
|
+
};
|
|
427
|
+
const output = options.pretty ? JSON.stringify(result, null, 2) : JSON.stringify(result);
|
|
428
|
+
if (options.out) {
|
|
429
|
+
fs_1.default.writeFileSync(options.out, output);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
console.log(output);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
console.error('Error calculating metrics:', error instanceof Error ? error.message : String(error));
|
|
437
|
+
process.exit(1);
|
|
438
|
+
}
|
|
439
|
+
});
|
|
279
440
|
// Show help if no command provided
|
|
280
441
|
if (process.argv.length <= 2) {
|
|
281
442
|
commander_1.program.help();
|
|
@@ -16,11 +16,15 @@ exports.isCryptoAvailable = isCryptoAvailable;
|
|
|
16
16
|
* @returns Decrypted and inflated buffer
|
|
17
17
|
*/
|
|
18
18
|
function decryptGridsetEntry(buffer, password) {
|
|
19
|
+
const nodeRequire = typeof require === 'function' ? require : undefined;
|
|
20
|
+
if (!nodeRequire) {
|
|
21
|
+
throw new Error('Crypto utilities are not available in this environment.');
|
|
22
|
+
}
|
|
19
23
|
// Dynamic require to avoid breaking in browser environments
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const zlib =
|
|
24
|
+
const cryptoModule = 'crypto';
|
|
25
|
+
const zlibModule = 'zlib';
|
|
26
|
+
const crypto = nodeRequire(cryptoModule);
|
|
27
|
+
const zlib = nodeRequire(zlibModule);
|
|
24
28
|
const pwd = (password || 'Chocolate').padEnd(32, ' ');
|
|
25
29
|
const key = Buffer.from(pwd.slice(0, 32), 'utf8');
|
|
26
30
|
const iv = Buffer.from(pwd.slice(0, 16), 'utf8');
|
|
@@ -45,10 +49,13 @@ function decryptGridsetEntry(buffer, password) {
|
|
|
45
49
|
*/
|
|
46
50
|
function isCryptoAvailable() {
|
|
47
51
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
const nodeRequire = typeof require === 'function' ? require : undefined;
|
|
53
|
+
if (!nodeRequire)
|
|
54
|
+
return false;
|
|
55
|
+
const cryptoModule = 'crypto';
|
|
56
|
+
const zlibModule = 'zlib';
|
|
57
|
+
nodeRequire(cryptoModule);
|
|
58
|
+
nodeRequire(zlibModule);
|
|
52
59
|
return true;
|
|
53
60
|
}
|
|
54
61
|
catch {
|
|
@@ -1641,8 +1641,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1641
1641
|
if (Object.keys(tree.pages).length === 0) {
|
|
1642
1642
|
// Create empty zip for empty tree
|
|
1643
1643
|
const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
|
|
1644
|
-
|
|
1645
|
-
require('fs').writeFileSync(outputPath, zipBuffer);
|
|
1644
|
+
(0, io_1.writeBinaryToPath)(outputPath, zipBuffer);
|
|
1646
1645
|
return;
|
|
1647
1646
|
}
|
|
1648
1647
|
// Collect all unique styles from pages and buttons
|
|
@@ -1937,8 +1936,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1937
1936
|
zip.file('FileMap.xml', fileMapXmlContent, { binary: false });
|
|
1938
1937
|
// Write the zip file
|
|
1939
1938
|
const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
|
|
1940
|
-
|
|
1941
|
-
require('fs').writeFileSync(outputPath, zipBuffer);
|
|
1939
|
+
(0, io_1.writeBinaryToPath)(outputPath, zipBuffer);
|
|
1942
1940
|
}
|
|
1943
1941
|
// Helper method to calculate column definitions based on page layout
|
|
1944
1942
|
calculateColumnDefinitions(page) {
|
|
@@ -172,11 +172,15 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
172
172
|
async processBoard(boardData, _boardPath) {
|
|
173
173
|
const sourceButtons = boardData.buttons || [];
|
|
174
174
|
// Calculate page ID first (used to make button IDs unique)
|
|
175
|
-
const
|
|
175
|
+
const isZipEntry = _boardPath &&
|
|
176
|
+
_boardPath.endsWith('.obf') &&
|
|
177
|
+
!_boardPath.includes('/') &&
|
|
178
|
+
!_boardPath.includes('\\');
|
|
179
|
+
const pageId = isZipEntry
|
|
176
180
|
? _boardPath // Zip entry - use filename to match navigation paths
|
|
177
181
|
: boardData?.id
|
|
178
182
|
? String(boardData.id)
|
|
179
|
-
: _boardPath?.split(
|
|
183
|
+
: _boardPath?.split(/[/\\]/).pop() || '';
|
|
180
184
|
const buttons = await Promise.all(sourceButtons.map(async (btn) => {
|
|
181
185
|
const semanticAction = btn.load_board
|
|
182
186
|
? {
|
|
@@ -11,6 +11,7 @@ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
13
13
|
const crypto_1 = __importDefault(require("crypto"));
|
|
14
|
+
const os_1 = __importDefault(require("os"));
|
|
14
15
|
const snapValidator_1 = require("../validation/snapValidator");
|
|
15
16
|
/**
|
|
16
17
|
* Map Snap Visible value to AAC standard visibility
|
|
@@ -55,11 +56,18 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
55
56
|
async loadIntoTree(filePathOrBuffer) {
|
|
56
57
|
await Promise.resolve();
|
|
57
58
|
const tree = new treeStructure_1.AACTree();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
let tempDir = null;
|
|
60
|
+
const filePath = typeof filePathOrBuffer !== 'string'
|
|
61
|
+
? (() => {
|
|
62
|
+
tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'snap-'));
|
|
63
|
+
return path_1.default.join(tempDir, 'input.spb');
|
|
64
|
+
})()
|
|
65
|
+
: filePathOrBuffer;
|
|
66
|
+
if (typeof filePathOrBuffer !== 'string') {
|
|
67
|
+
const buffer = Buffer.isBuffer(filePathOrBuffer)
|
|
68
|
+
? filePathOrBuffer
|
|
69
|
+
: Buffer.from(filePathOrBuffer);
|
|
70
|
+
fs_1.default.writeFileSync(filePath, buffer);
|
|
63
71
|
}
|
|
64
72
|
let db = null;
|
|
65
73
|
try {
|
|
@@ -603,12 +611,12 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
603
611
|
db.close();
|
|
604
612
|
}
|
|
605
613
|
// Clean up temporary file if created from buffer
|
|
606
|
-
if (
|
|
614
|
+
if (tempDir && fs_1.default.existsSync(tempDir)) {
|
|
607
615
|
try {
|
|
608
|
-
fs_1.default.
|
|
616
|
+
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
609
617
|
}
|
|
610
618
|
catch (e) {
|
|
611
|
-
console.warn('Failed to clean up temporary
|
|
619
|
+
console.warn('Failed to clean up temporary files:', e);
|
|
612
620
|
}
|
|
613
621
|
}
|
|
614
622
|
}
|
|
@@ -36,6 +36,31 @@ export interface HistoryEntry {
|
|
|
36
36
|
platform?: HistoryPlatformExtras;
|
|
37
37
|
}
|
|
38
38
|
export { dotNetTicksToDate };
|
|
39
|
+
export interface BatonExportMetadata {
|
|
40
|
+
timestamp: string;
|
|
41
|
+
latitude?: number | null;
|
|
42
|
+
longitude?: number | null;
|
|
43
|
+
}
|
|
44
|
+
export interface BatonExportSentence {
|
|
45
|
+
uuid: string;
|
|
46
|
+
anonymousUUID: string;
|
|
47
|
+
content: string;
|
|
48
|
+
metadata: BatonExportMetadata[];
|
|
49
|
+
source: HistorySource;
|
|
50
|
+
}
|
|
51
|
+
export interface BatonExport {
|
|
52
|
+
version: string;
|
|
53
|
+
exportDate: string;
|
|
54
|
+
encryption: string;
|
|
55
|
+
sentenceCount: number;
|
|
56
|
+
sentences: BatonExportSentence[];
|
|
57
|
+
}
|
|
58
|
+
export declare function exportHistoryToBaton(entries: HistoryEntry[], options?: {
|
|
59
|
+
version?: string;
|
|
60
|
+
exportDate?: string | Date;
|
|
61
|
+
encryption?: string;
|
|
62
|
+
anonymousUUID?: string;
|
|
63
|
+
}): BatonExport;
|
|
39
64
|
/**
|
|
40
65
|
* Read Grid 3 phrase history from a history.sqlite database and tag entries with their source.
|
|
41
66
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.dotNetTicksToDate = void 0;
|
|
4
|
+
exports.exportHistoryToBaton = exportHistoryToBaton;
|
|
4
5
|
exports.readGrid3History = readGrid3History;
|
|
5
6
|
exports.readGrid3HistoryForUser = readGrid3HistoryForUser;
|
|
6
7
|
exports.readAllGrid3History = readAllGrid3History;
|
|
@@ -13,6 +14,61 @@ const dotnetTicks_1 = require("../../utils/dotnetTicks");
|
|
|
13
14
|
Object.defineProperty(exports, "dotNetTicksToDate", { enumerable: true, get: function () { return dotnetTicks_1.dotNetTicksToDate; } });
|
|
14
15
|
const helpers_1 = require("../../processors/gridset/helpers");
|
|
15
16
|
const helpers_2 = require("../../processors/snap/helpers");
|
|
17
|
+
const generateUuid = () => {
|
|
18
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
|
19
|
+
return globalThis.crypto.randomUUID();
|
|
20
|
+
}
|
|
21
|
+
// RFC4122-ish fallback for Node without crypto.randomUUID
|
|
22
|
+
const hex = '0123456789abcdef';
|
|
23
|
+
const bytes = Array.from({ length: 16 }, () => Math.floor(Math.random() * 256));
|
|
24
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
25
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
26
|
+
const toHex = (b) => hex[(b >> 4) & 0x0f] + hex[b & 0x0f];
|
|
27
|
+
return (toHex(bytes[0]) +
|
|
28
|
+
toHex(bytes[1]) +
|
|
29
|
+
toHex(bytes[2]) +
|
|
30
|
+
toHex(bytes[3]) +
|
|
31
|
+
'-' +
|
|
32
|
+
toHex(bytes[4]) +
|
|
33
|
+
toHex(bytes[5]) +
|
|
34
|
+
'-' +
|
|
35
|
+
toHex(bytes[6]) +
|
|
36
|
+
toHex(bytes[7]) +
|
|
37
|
+
'-' +
|
|
38
|
+
toHex(bytes[8]) +
|
|
39
|
+
toHex(bytes[9]) +
|
|
40
|
+
'-' +
|
|
41
|
+
toHex(bytes[10]) +
|
|
42
|
+
toHex(bytes[11]) +
|
|
43
|
+
toHex(bytes[12]) +
|
|
44
|
+
toHex(bytes[13]) +
|
|
45
|
+
toHex(bytes[14]) +
|
|
46
|
+
toHex(bytes[15]));
|
|
47
|
+
};
|
|
48
|
+
function exportHistoryToBaton(entries, options) {
|
|
49
|
+
const exportDate = options?.exportDate instanceof Date
|
|
50
|
+
? options.exportDate.toISOString()
|
|
51
|
+
: options?.exportDate || new Date().toISOString();
|
|
52
|
+
const anonymousUUID = options?.anonymousUUID || generateUuid();
|
|
53
|
+
const sentences = entries.map((entry) => ({
|
|
54
|
+
uuid: generateUuid(),
|
|
55
|
+
anonymousUUID,
|
|
56
|
+
content: entry.content,
|
|
57
|
+
metadata: entry.occurrences.map((occ) => ({
|
|
58
|
+
timestamp: occ.timestamp.toISOString(),
|
|
59
|
+
latitude: occ.latitude ?? null,
|
|
60
|
+
longitude: occ.longitude ?? null,
|
|
61
|
+
})),
|
|
62
|
+
source: entry.source,
|
|
63
|
+
}));
|
|
64
|
+
return {
|
|
65
|
+
version: options?.version || '1.0',
|
|
66
|
+
exportDate,
|
|
67
|
+
encryption: options?.encryption || 'none',
|
|
68
|
+
sentenceCount: sentences.length,
|
|
69
|
+
sentences,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
16
72
|
/**
|
|
17
73
|
* Read Grid 3 phrase history from a history.sqlite database and tag entries with their source.
|
|
18
74
|
*/
|
package/dist/utils/io.js
CHANGED
|
@@ -12,11 +12,31 @@ exports.writeBinaryToPath = writeBinaryToPath;
|
|
|
12
12
|
exports.writeTextToPath = writeTextToPath;
|
|
13
13
|
let cachedFs = null;
|
|
14
14
|
let cachedPath = null;
|
|
15
|
+
let cachedRequire = undefined;
|
|
16
|
+
function getNodeRequire() {
|
|
17
|
+
if (cachedRequire === undefined) {
|
|
18
|
+
if (typeof require === 'function') {
|
|
19
|
+
cachedRequire = require;
|
|
20
|
+
}
|
|
21
|
+
else if (typeof globalThis !== 'undefined') {
|
|
22
|
+
const maybeRequire = globalThis.require;
|
|
23
|
+
cachedRequire = typeof maybeRequire === 'function' ? maybeRequire : null;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
cachedRequire = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (!cachedRequire) {
|
|
30
|
+
throw new Error('File system access is not available in this environment.');
|
|
31
|
+
}
|
|
32
|
+
return cachedRequire;
|
|
33
|
+
}
|
|
15
34
|
function getFs() {
|
|
16
35
|
if (!cachedFs) {
|
|
17
36
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
37
|
+
const nodeRequire = getNodeRequire();
|
|
38
|
+
const fsModule = 'fs';
|
|
39
|
+
cachedFs = nodeRequire(fsModule);
|
|
20
40
|
}
|
|
21
41
|
catch {
|
|
22
42
|
throw new Error('File system access is not available in this environment.');
|
|
@@ -30,8 +50,9 @@ function getFs() {
|
|
|
30
50
|
function getPath() {
|
|
31
51
|
if (!cachedPath) {
|
|
32
52
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
53
|
+
const nodeRequire = getNodeRequire();
|
|
54
|
+
const pathModule = 'path';
|
|
55
|
+
cachedPath = nodeRequire(pathModule);
|
|
35
56
|
}
|
|
36
57
|
catch {
|
|
37
58
|
throw new Error('Path utilities are not available in this environment.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@willwade/aac-processors",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "A comprehensive TypeScript library for processing AAC (Augmentative and Alternative Communication) file formats with translation support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"browser": "dist/browser/index.browser.js",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"build:all": "npm run build:node && npm run build:browser",
|
|
89
89
|
"build:watch": "tsc -p tsconfig.node.json --watch",
|
|
90
90
|
"clean": "rimraf dist coverage",
|
|
91
|
-
"copy:assets": "
|
|
91
|
+
"copy:assets": "node scripts/copy-assets.js",
|
|
92
92
|
"lint": "eslint \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\"",
|
|
93
93
|
"lint:fix": "eslint \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" --fix",
|
|
94
94
|
"format": "prettier --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" \"*.{js,ts,json,md}\"",
|