@willwade/aac-processors 0.1.17 → 0.1.19
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/index.browser.js +9 -9
- package/dist/browser/processors/gridset/helpers.js +4 -2
- package/dist/browser/processors/gridsetProcessor.js +4 -1
- package/dist/browser/processors/obfProcessor.js +17 -16
- package/dist/browser/processors/snap/helpers.js +8 -0
- package/dist/browser/processors/snapProcessor.js +43 -43
- package/dist/browser/processors/touchchatProcessor.js +3 -1
- package/dist/browser/validation/touchChatValidator.js +3 -3
- package/dist/core/baseProcessor.d.ts +4 -0
- package/dist/index.browser.d.ts +2 -2
- package/dist/index.browser.js +9 -9
- package/dist/index.node.d.ts +2 -2
- package/dist/index.node.js +11 -11
- package/dist/processors/gridset/helpers.d.ts +5 -1
- package/dist/processors/gridset/helpers.js +4 -2
- package/dist/processors/gridset/imageDebug.d.ts +5 -1
- package/dist/processors/gridset/imageDebug.js +4 -2
- package/dist/processors/gridset/wordlistHelpers.d.ts +5 -1
- package/dist/processors/gridset/wordlistHelpers.js +4 -2
- package/dist/processors/gridsetProcessor.js +4 -1
- package/dist/processors/obfProcessor.js +17 -16
- package/dist/processors/snap/helpers.js +8 -0
- package/dist/processors/snapProcessor.js +43 -43
- package/dist/processors/touchchatProcessor.js +3 -1
- package/dist/validation/touchChatValidator.d.ts +5 -1
- package/dist/validation/touchChatValidator.js +2 -2
- package/package.json +1 -1
|
@@ -48,29 +48,29 @@ export { configureSqlJs } from './utils/sqlite';
|
|
|
48
48
|
* @returns The appropriate processor instance
|
|
49
49
|
* @throws Error if the file extension is not supported
|
|
50
50
|
*/
|
|
51
|
-
export function getProcessor(filePathOrExtension) {
|
|
51
|
+
export function getProcessor(filePathOrExtension, options) {
|
|
52
52
|
const extension = filePathOrExtension.includes('.')
|
|
53
53
|
? filePathOrExtension.substring(filePathOrExtension.lastIndexOf('.'))
|
|
54
54
|
: filePathOrExtension;
|
|
55
55
|
switch (extension.toLowerCase()) {
|
|
56
56
|
case '.dot':
|
|
57
|
-
return new DotProcessor();
|
|
57
|
+
return new DotProcessor(options);
|
|
58
58
|
case '.opml':
|
|
59
|
-
return new OpmlProcessor();
|
|
59
|
+
return new OpmlProcessor(options);
|
|
60
60
|
case '.obf':
|
|
61
61
|
case '.obz':
|
|
62
|
-
return new ObfProcessor();
|
|
62
|
+
return new ObfProcessor(options);
|
|
63
63
|
case '.gridset':
|
|
64
|
-
return new GridsetProcessor();
|
|
64
|
+
return new GridsetProcessor(options);
|
|
65
65
|
case '.spb':
|
|
66
66
|
case '.sps':
|
|
67
|
-
return new SnapProcessor();
|
|
67
|
+
return new SnapProcessor(options);
|
|
68
68
|
case '.ce':
|
|
69
|
-
return new TouchChatProcessor();
|
|
69
|
+
return new TouchChatProcessor(options);
|
|
70
70
|
case '.plist':
|
|
71
|
-
return new ApplePanelsProcessor();
|
|
71
|
+
return new ApplePanelsProcessor(options);
|
|
72
72
|
case '.grd':
|
|
73
|
-
return new AstericsGridProcessor();
|
|
73
|
+
return new AstericsGridProcessor(options);
|
|
74
74
|
default:
|
|
75
75
|
throw new Error(`Unsupported file extension: ${extension}`);
|
|
76
76
|
}
|
|
@@ -52,9 +52,11 @@ export function getAllowedImageEntries(tree) {
|
|
|
52
52
|
* @param entryPath Entry name inside the zip
|
|
53
53
|
* @returns Image data buffer or null if not found
|
|
54
54
|
*/
|
|
55
|
-
export async function openImage(gridsetBuffer, entryPath, password = resolveGridsetPasswordFromEnv()) {
|
|
55
|
+
export async function openImage(gridsetBuffer, entryPath, password = resolveGridsetPasswordFromEnv(), zipAdapter) {
|
|
56
56
|
try {
|
|
57
|
-
const { zip } =
|
|
57
|
+
const { zip } = zipAdapter
|
|
58
|
+
? await zipAdapter(gridsetBuffer)
|
|
59
|
+
: await openZipFromInput(gridsetBuffer);
|
|
58
60
|
const entries = getZipEntriesFromAdapter(zip, password);
|
|
59
61
|
const want = normalizeZipPath(entryPath);
|
|
60
62
|
const entry = entries.find((e) => normalizeZipPath(e.entryName) === want);
|
|
@@ -398,7 +398,10 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
398
398
|
const tree = new AACTree();
|
|
399
399
|
let zipResult;
|
|
400
400
|
try {
|
|
401
|
-
|
|
401
|
+
const zipInput = readBinaryFromInput(filePathOrBuffer);
|
|
402
|
+
zipResult = this.options.zipAdapter
|
|
403
|
+
? await this.options.zipAdapter(zipInput)
|
|
404
|
+
: await openZipFromInput(zipInput);
|
|
402
405
|
}
|
|
403
406
|
catch (error) {
|
|
404
407
|
throw new Error(`Invalid ZIP file format: ${error.message}`);
|
|
@@ -352,9 +352,20 @@ class ObfProcessor extends BaseProcessor {
|
|
|
352
352
|
throw err;
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
|
-
//
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
// Detect likely zip signature first
|
|
356
|
+
function isLikelyZip(input) {
|
|
357
|
+
if (typeof input === 'string') {
|
|
358
|
+
const lowered = input.toLowerCase();
|
|
359
|
+
return lowered.endsWith('.zip') || lowered.endsWith('.obz');
|
|
360
|
+
}
|
|
361
|
+
const bytes = readBinaryFromInput(input);
|
|
362
|
+
return bytes.length >= 2 && bytes[0] === 0x50 && bytes[1] === 0x4b;
|
|
363
|
+
}
|
|
364
|
+
// Check if input is a buffer or string that parses as OBF JSON; throw if neither JSON nor ZIP
|
|
365
|
+
if (!isLikelyZip(filePathOrBuffer)) {
|
|
366
|
+
const asJson = tryParseObfJson(filePathOrBuffer);
|
|
367
|
+
if (!asJson)
|
|
368
|
+
throw new Error('Invalid OBF content: not JSON and not ZIP');
|
|
358
369
|
console.log('[OBF] Detected buffer/string as OBF JSON');
|
|
359
370
|
const page = await this.processBoard(asJson, '[bufferOrString]');
|
|
360
371
|
tree.addPage(page);
|
|
@@ -372,20 +383,10 @@ class ObfProcessor extends BaseProcessor {
|
|
|
372
383
|
tree.rootId = page.id;
|
|
373
384
|
return tree;
|
|
374
385
|
}
|
|
375
|
-
// Otherwise, try as ZIP (.obz). Detect likely zip signature first; throw if neither JSON nor ZIP
|
|
376
|
-
function isLikelyZip(input) {
|
|
377
|
-
if (typeof input === 'string') {
|
|
378
|
-
const lowered = input.toLowerCase();
|
|
379
|
-
return lowered.endsWith('.zip') || lowered.endsWith('.obz');
|
|
380
|
-
}
|
|
381
|
-
const bytes = readBinaryFromInput(input);
|
|
382
|
-
return bytes.length >= 2 && bytes[0] === 0x50 && bytes[1] === 0x4b;
|
|
383
|
-
}
|
|
384
|
-
if (!isLikelyZip(filePathOrBuffer)) {
|
|
385
|
-
throw new Error('Invalid OBF content: not JSON and not ZIP');
|
|
386
|
-
}
|
|
387
386
|
try {
|
|
388
|
-
const zipResult =
|
|
387
|
+
const zipResult = this.options.zipAdapter
|
|
388
|
+
? await this.options.zipAdapter(filePathOrBuffer)
|
|
389
|
+
: await openZipFromInput(filePathOrBuffer);
|
|
389
390
|
this.zipFile = zipResult.zip;
|
|
390
391
|
}
|
|
391
392
|
catch (err) {
|
|
@@ -4,6 +4,11 @@ import * as path from 'path';
|
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
import { dotNetTicksToDate } from '../../utils/dotnetTicks';
|
|
6
6
|
// Minimal Snap helpers (stubs) to align with processors/<engine>/helpers pattern
|
|
7
|
+
// NOTE: Snap files can store different types of image data in PageSetData:
|
|
8
|
+
// - PNG/JPEG binaries: Actual images that can be displayed
|
|
9
|
+
// - Vector graphics: Custom format (d7 cd c6 9a) requiring rendering engine
|
|
10
|
+
//
|
|
11
|
+
// We extract PNG/JPEG images but skip vector graphics (requires renderer).
|
|
7
12
|
// NOTE: Snap buttons currently do not populate resolvedImageEntry; these helpers
|
|
8
13
|
// therefore return empty collections until image resolution is implemented.
|
|
9
14
|
function collectFiles(root, matcher, maxDepth = 3) {
|
|
@@ -105,6 +110,9 @@ export function openImage(dbOrFile, entryPath) {
|
|
|
105
110
|
.prepare('SELECT Id, Identifier, Data FROM PageSetData WHERE Identifier = ?')
|
|
106
111
|
.get(entryPath);
|
|
107
112
|
if (row && row.Data && row.Data.length > 0) {
|
|
113
|
+
// Snap files can store different types of image data:
|
|
114
|
+
// 1. PNG/JPEG binaries (actual images) - return as-is
|
|
115
|
+
// 2. Vector graphics (custom format d7 cd c6 9a) - return but may not be displayable
|
|
108
116
|
return row.Data;
|
|
109
117
|
}
|
|
110
118
|
return null;
|
|
@@ -4,6 +4,25 @@ import { generateCloneId } from '../utilities/analytics/utils/idGenerator';
|
|
|
4
4
|
import { SnapValidator } from '../validation/snapValidator';
|
|
5
5
|
import { getFs, getNodeRequire, getPath, isNodeRuntime } from '../utils/io';
|
|
6
6
|
import { openSqliteDatabase, requireBetterSqlite3 } from '../utils/sqlite';
|
|
7
|
+
/**
|
|
8
|
+
* Convert a Buffer or Uint8Array to base64 string (browser and Node compatible)
|
|
9
|
+
* Node.js Buffers support toString('base64'), but Uint8Arrays in browser do not.
|
|
10
|
+
* This function works in both environments.
|
|
11
|
+
*/
|
|
12
|
+
function arrayBufferToBase64(data) {
|
|
13
|
+
// Node.js environment - Buffer has built-in base64 encoding
|
|
14
|
+
if (typeof Buffer !== 'undefined' && data instanceof Buffer) {
|
|
15
|
+
return data.toString('base64');
|
|
16
|
+
}
|
|
17
|
+
// Browser environment - use btoa with binary string conversion
|
|
18
|
+
const bytes = new Uint8Array(data);
|
|
19
|
+
let binary = '';
|
|
20
|
+
const len = bytes.byteLength;
|
|
21
|
+
for (let i = 0; i < len; i++) {
|
|
22
|
+
binary += String.fromCharCode(bytes[i]);
|
|
23
|
+
}
|
|
24
|
+
return btoa(binary);
|
|
25
|
+
}
|
|
7
26
|
/**
|
|
8
27
|
* Map Snap Visible value to AAC standard visibility
|
|
9
28
|
* Snap: 0 = hidden, 1 (or non-zero) = visible
|
|
@@ -15,41 +34,6 @@ function mapSnapVisibility(visible) {
|
|
|
15
34
|
}
|
|
16
35
|
return visible === 0 ? 'Hidden' : 'Visible';
|
|
17
36
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Detect image MIME type from binary data using magic bytes
|
|
20
|
-
* @param buffer Image data buffer
|
|
21
|
-
* @returns MIME type string (defaults to 'image/png' if unknown)
|
|
22
|
-
*/
|
|
23
|
-
function detectImageMimeType(buffer) {
|
|
24
|
-
if (!buffer || buffer.length < 8) {
|
|
25
|
-
return 'image/png';
|
|
26
|
-
}
|
|
27
|
-
// Check for PNG: 89 50 4E 47 0D 0A 1A 0A
|
|
28
|
-
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) {
|
|
29
|
-
return 'image/png';
|
|
30
|
-
}
|
|
31
|
-
// Check for JPEG: FF D8 FF
|
|
32
|
-
if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
|
|
33
|
-
return 'image/jpeg';
|
|
34
|
-
}
|
|
35
|
-
// Check for GIF: 47 49 46 38 (GIF8)
|
|
36
|
-
if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
|
|
37
|
-
return 'image/gif';
|
|
38
|
-
}
|
|
39
|
-
// Check for WebP: 52 49 46 46 ... 57 45 42 50 (RIFF...WEBP)
|
|
40
|
-
if (buffer[0] === 0x52 &&
|
|
41
|
-
buffer[1] === 0x49 &&
|
|
42
|
-
buffer[2] === 0x46 &&
|
|
43
|
-
buffer[3] === 0x46 &&
|
|
44
|
-
buffer[8] === 0x57 &&
|
|
45
|
-
buffer[9] === 0x45 &&
|
|
46
|
-
buffer[10] === 0x42 &&
|
|
47
|
-
buffer[11] === 0x50) {
|
|
48
|
-
return 'image/webp';
|
|
49
|
-
}
|
|
50
|
-
// Default to PNG
|
|
51
|
-
return 'image/png';
|
|
52
|
-
}
|
|
53
37
|
class SnapProcessor extends BaseProcessor {
|
|
54
38
|
constructor(symbolResolver = null, options = {}) {
|
|
55
39
|
super(options);
|
|
@@ -465,14 +449,30 @@ class SnapProcessor extends BaseProcessor {
|
|
|
465
449
|
`)
|
|
466
450
|
.get(btnRow.PageSetImageId);
|
|
467
451
|
if (imageData && imageData.Data && imageData.Data.length > 0) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
//
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
452
|
+
// Snap files can store different types of image data:
|
|
453
|
+
// 1. PNG/JPEG binaries (actual images) - extract and display
|
|
454
|
+
// 2. Vector graphics (custom format d7 cd c6 9a) - skip (requires renderer)
|
|
455
|
+
const data = imageData.Data;
|
|
456
|
+
// Check for PNG: 89 50 4E 47
|
|
457
|
+
const isPng = data.length > 4 &&
|
|
458
|
+
data[0] === 0x89 &&
|
|
459
|
+
data[1] === 0x50 &&
|
|
460
|
+
data[2] === 0x4e &&
|
|
461
|
+
data[3] === 0x47;
|
|
462
|
+
// Check for JPEG: FF D8 FF
|
|
463
|
+
const isJpeg = data.length > 3 && data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff;
|
|
464
|
+
if (isPng || isJpeg) {
|
|
465
|
+
// Actual PNG/JPEG image - can be displayed
|
|
466
|
+
const mimeType = isPng ? 'image/png' : 'image/jpeg';
|
|
467
|
+
const base64 = arrayBufferToBase64(data);
|
|
468
|
+
buttonImage = `data:${mimeType};base64,${base64}`;
|
|
469
|
+
buttonParameters.image_id = imageData.Identifier;
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
// Vector graphics or other format - skip rendering
|
|
473
|
+
// Store identifier but don't create image URL
|
|
474
|
+
buttonParameters.image_id = imageData.Identifier;
|
|
475
|
+
}
|
|
476
476
|
}
|
|
477
477
|
}
|
|
478
478
|
catch (e) {
|
|
@@ -64,7 +64,9 @@ class TouchChatProcessor extends BaseProcessor {
|
|
|
64
64
|
this.sourceFile = filePathOrBuffer;
|
|
65
65
|
// Step 1: Unzip
|
|
66
66
|
const zipInput = readBinaryFromInput(filePathOrBuffer);
|
|
67
|
-
const { zip } =
|
|
67
|
+
const { zip } = this.options.zipAdapter
|
|
68
|
+
? await this.options.zipAdapter(zipInput)
|
|
69
|
+
: await openZipFromInput(zipInput);
|
|
68
70
|
const vocabEntry = zip.listFiles().find((name) => name.endsWith('.c4v'));
|
|
69
71
|
if (!vocabEntry) {
|
|
70
72
|
throw new Error('No .c4v vocab DB found in TouchChat export');
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
4
4
|
import * as xml2js from 'xml2js';
|
|
5
5
|
import { BaseValidator } from './baseValidator';
|
|
6
|
-
import { decodeText, getBasename, getFs, readBinaryFromInput, toUint8Array } from '../utils/io';
|
|
6
|
+
import { decodeText, getBasename, getFs, readBinaryFromInput, toUint8Array, } from '../utils/io';
|
|
7
7
|
import { openZipFromInput } from '../utils/zip';
|
|
8
8
|
import { openSqliteDatabase } from '../utils/sqlite';
|
|
9
9
|
/**
|
|
@@ -27,14 +27,14 @@ export class TouchChatValidator extends BaseValidator {
|
|
|
27
27
|
/**
|
|
28
28
|
* Check if content is TouchChat format
|
|
29
29
|
*/
|
|
30
|
-
static async identifyFormat(content, filename) {
|
|
30
|
+
static async identifyFormat(content, filename, zipAdapter) {
|
|
31
31
|
const name = filename.toLowerCase();
|
|
32
32
|
if (name.endsWith('.ce')) {
|
|
33
33
|
return true;
|
|
34
34
|
}
|
|
35
35
|
// Try to parse as ZIP and check for .c4v database
|
|
36
36
|
try {
|
|
37
|
-
const { zip } = await openZipFromInput(content);
|
|
37
|
+
const { zip } = zipAdapter ? await zipAdapter(content) : await openZipFromInput(content);
|
|
38
38
|
const entries = zip.listFiles();
|
|
39
39
|
if (entries.some((entry) => entry.toLowerCase().endsWith('.c4v'))) {
|
|
40
40
|
return true;
|
|
@@ -43,6 +43,7 @@ import { AACTree, AACButton } from './treeStructure';
|
|
|
43
43
|
import { StringCasing } from './stringCasing';
|
|
44
44
|
import { ValidationResult } from '../validation/validationTypes';
|
|
45
45
|
import { BinaryOutput, ProcessorInput } from '../utils/io';
|
|
46
|
+
import type { ZipAdapter } from '../utils/zip';
|
|
46
47
|
export interface ProcessorOptions {
|
|
47
48
|
excludeNavigationButtons?: boolean;
|
|
48
49
|
excludeSystemButtons?: boolean;
|
|
@@ -52,6 +53,9 @@ export interface ProcessorOptions {
|
|
|
52
53
|
grid3SymbolDir?: string;
|
|
53
54
|
grid3Path?: string;
|
|
54
55
|
grid3Locale?: string;
|
|
56
|
+
zipAdapter?: (input: ProcessorInput) => Promise<{
|
|
57
|
+
zip: ZipAdapter;
|
|
58
|
+
}>;
|
|
55
59
|
}
|
|
56
60
|
export interface ExtractedString {
|
|
57
61
|
string: string;
|
package/dist/index.browser.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export { TouchChatProcessor } from './processors/touchchatProcessor';
|
|
|
23
23
|
export { ApplePanelsProcessor } from './processors/applePanelsProcessor';
|
|
24
24
|
export { AstericsGridProcessor } from './processors/astericsGridProcessor';
|
|
25
25
|
export * as Metrics from './metrics';
|
|
26
|
-
import { BaseProcessor } from './core/baseProcessor';
|
|
26
|
+
import { BaseProcessor, ProcessorOptions } from './core/baseProcessor';
|
|
27
27
|
export { configureSqlJs } from './utils/sqlite';
|
|
28
28
|
/**
|
|
29
29
|
* Factory function to get the appropriate processor for a file extension
|
|
@@ -31,7 +31,7 @@ export { configureSqlJs } from './utils/sqlite';
|
|
|
31
31
|
* @returns The appropriate processor instance
|
|
32
32
|
* @throws Error if the file extension is not supported
|
|
33
33
|
*/
|
|
34
|
-
export declare function getProcessor(filePathOrExtension: string): BaseProcessor;
|
|
34
|
+
export declare function getProcessor(filePathOrExtension: string, options?: ProcessorOptions): BaseProcessor;
|
|
35
35
|
/**
|
|
36
36
|
* Get all supported file extensions
|
|
37
37
|
* @returns Array of supported file extensions
|
package/dist/index.browser.js
CHANGED
|
@@ -89,29 +89,29 @@ Object.defineProperty(exports, "configureSqlJs", { enumerable: true, get: functi
|
|
|
89
89
|
* @returns The appropriate processor instance
|
|
90
90
|
* @throws Error if the file extension is not supported
|
|
91
91
|
*/
|
|
92
|
-
function getProcessor(filePathOrExtension) {
|
|
92
|
+
function getProcessor(filePathOrExtension, options) {
|
|
93
93
|
const extension = filePathOrExtension.includes('.')
|
|
94
94
|
? filePathOrExtension.substring(filePathOrExtension.lastIndexOf('.'))
|
|
95
95
|
: filePathOrExtension;
|
|
96
96
|
switch (extension.toLowerCase()) {
|
|
97
97
|
case '.dot':
|
|
98
|
-
return new dotProcessor_2.DotProcessor();
|
|
98
|
+
return new dotProcessor_2.DotProcessor(options);
|
|
99
99
|
case '.opml':
|
|
100
|
-
return new opmlProcessor_2.OpmlProcessor();
|
|
100
|
+
return new opmlProcessor_2.OpmlProcessor(options);
|
|
101
101
|
case '.obf':
|
|
102
102
|
case '.obz':
|
|
103
|
-
return new obfProcessor_2.ObfProcessor();
|
|
103
|
+
return new obfProcessor_2.ObfProcessor(options);
|
|
104
104
|
case '.gridset':
|
|
105
|
-
return new gridsetProcessor_2.GridsetProcessor();
|
|
105
|
+
return new gridsetProcessor_2.GridsetProcessor(options);
|
|
106
106
|
case '.spb':
|
|
107
107
|
case '.sps':
|
|
108
|
-
return new snapProcessor_2.SnapProcessor();
|
|
108
|
+
return new snapProcessor_2.SnapProcessor(options);
|
|
109
109
|
case '.ce':
|
|
110
|
-
return new touchchatProcessor_2.TouchChatProcessor();
|
|
110
|
+
return new touchchatProcessor_2.TouchChatProcessor(options);
|
|
111
111
|
case '.plist':
|
|
112
|
-
return new applePanelsProcessor_2.ApplePanelsProcessor();
|
|
112
|
+
return new applePanelsProcessor_2.ApplePanelsProcessor(options);
|
|
113
113
|
case '.grd':
|
|
114
|
-
return new astericsGridProcessor_2.AstericsGridProcessor();
|
|
114
|
+
return new astericsGridProcessor_2.AstericsGridProcessor(options);
|
|
115
115
|
default:
|
|
116
116
|
throw new Error(`Unsupported file extension: ${extension}`);
|
|
117
117
|
}
|
package/dist/index.node.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export * as Opml from './opml';
|
|
|
23
23
|
export * as ApplePanels from './applePanels';
|
|
24
24
|
export * as AstericsGrid from './astericsGrid';
|
|
25
25
|
export * as Translation from './translation';
|
|
26
|
-
import { BaseProcessor } from './core/baseProcessor';
|
|
26
|
+
import { BaseProcessor, ProcessorOptions } from './core/baseProcessor';
|
|
27
27
|
/**
|
|
28
28
|
* Factory function to get the appropriate processor for a file extension
|
|
29
29
|
* @param filePathOrExtension - File path or extension (e.g., '.dot', '/path/to/file.obf')
|
|
@@ -34,7 +34,7 @@ import { BaseProcessor } from './core/baseProcessor';
|
|
|
34
34
|
* const processor = getProcessor('/path/to/file.gridset');
|
|
35
35
|
* const tree = processor.loadIntoTree('/path/to/file.gridset');
|
|
36
36
|
*/
|
|
37
|
-
export declare function getProcessor(filePathOrExtension: string): BaseProcessor;
|
|
37
|
+
export declare function getProcessor(filePathOrExtension: string, options?: ProcessorOptions): BaseProcessor;
|
|
38
38
|
/**
|
|
39
39
|
* Get all supported file extensions
|
|
40
40
|
* @returns Array of supported file extensions
|
package/dist/index.node.js
CHANGED
|
@@ -88,35 +88,35 @@ const obfsetProcessor_1 = require("./processors/obfsetProcessor");
|
|
|
88
88
|
* const processor = getProcessor('/path/to/file.gridset');
|
|
89
89
|
* const tree = processor.loadIntoTree('/path/to/file.gridset');
|
|
90
90
|
*/
|
|
91
|
-
function getProcessor(filePathOrExtension) {
|
|
91
|
+
function getProcessor(filePathOrExtension, options) {
|
|
92
92
|
// Extract extension from file path
|
|
93
93
|
const extension = filePathOrExtension.includes('.')
|
|
94
94
|
? filePathOrExtension.substring(filePathOrExtension.lastIndexOf('.'))
|
|
95
95
|
: filePathOrExtension;
|
|
96
96
|
switch (extension.toLowerCase()) {
|
|
97
97
|
case '.dot':
|
|
98
|
-
return new dotProcessor_1.DotProcessor();
|
|
98
|
+
return new dotProcessor_1.DotProcessor(options);
|
|
99
99
|
case '.xlsx':
|
|
100
|
-
return new excelProcessor_1.ExcelProcessor();
|
|
100
|
+
return new excelProcessor_1.ExcelProcessor(options);
|
|
101
101
|
case '.opml':
|
|
102
|
-
return new opmlProcessor_1.OpmlProcessor();
|
|
102
|
+
return new opmlProcessor_1.OpmlProcessor(options);
|
|
103
103
|
case '.obf':
|
|
104
104
|
case '.obz':
|
|
105
|
-
return new obfProcessor_1.ObfProcessor();
|
|
105
|
+
return new obfProcessor_1.ObfProcessor(options);
|
|
106
106
|
case '.obfset':
|
|
107
|
-
return new obfsetProcessor_1.ObfsetProcessor();
|
|
107
|
+
return new obfsetProcessor_1.ObfsetProcessor(options);
|
|
108
108
|
case '.gridset':
|
|
109
109
|
case '.gridsetx':
|
|
110
|
-
return new gridsetProcessor_1.GridsetProcessor();
|
|
110
|
+
return new gridsetProcessor_1.GridsetProcessor(options);
|
|
111
111
|
case '.spb':
|
|
112
112
|
case '.sps':
|
|
113
|
-
return new snapProcessor_1.SnapProcessor();
|
|
113
|
+
return new snapProcessor_1.SnapProcessor(options);
|
|
114
114
|
case '.ce':
|
|
115
|
-
return new touchchatProcessor_1.TouchChatProcessor();
|
|
115
|
+
return new touchchatProcessor_1.TouchChatProcessor(options);
|
|
116
116
|
case '.plist':
|
|
117
|
-
return new applePanelsProcessor_1.ApplePanelsProcessor();
|
|
117
|
+
return new applePanelsProcessor_1.ApplePanelsProcessor(options);
|
|
118
118
|
case '.grd':
|
|
119
|
-
return new astericsGridProcessor_1.AstericsGridProcessor();
|
|
119
|
+
return new astericsGridProcessor_1.AstericsGridProcessor(options);
|
|
120
120
|
default:
|
|
121
121
|
throw new Error(`Unsupported file extension: ${extension}`);
|
|
122
122
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { AACTree, AACSemanticCategory, AACSemanticIntent } from '../../core/treeStructure';
|
|
2
|
+
import { type ZipAdapter } from '../../utils/zip';
|
|
3
|
+
import type { ProcessorInput } from '../../utils/io';
|
|
2
4
|
/**
|
|
3
5
|
* Build a map of button IDs to resolved image entry paths for a specific page.
|
|
4
6
|
* Helpful when rewriting zip entry names or validating images referenced in a grid.
|
|
@@ -15,7 +17,9 @@ export declare function getAllowedImageEntries(tree: AACTree): Set<string>;
|
|
|
15
17
|
* @param entryPath Entry name inside the zip
|
|
16
18
|
* @returns Image data buffer or null if not found
|
|
17
19
|
*/
|
|
18
|
-
export declare function openImage(gridsetBuffer: Uint8Array, entryPath: string, password?: string | undefined
|
|
20
|
+
export declare function openImage(gridsetBuffer: Uint8Array, entryPath: string, password?: string | undefined, zipAdapter?: (input: ProcessorInput) => Promise<{
|
|
21
|
+
zip: ZipAdapter;
|
|
22
|
+
}>): Promise<Uint8Array | null>;
|
|
19
23
|
/**
|
|
20
24
|
* Generate a random GUID for Grid3 elements
|
|
21
25
|
* Grid3 uses GUIDs for grid identification
|
|
@@ -96,9 +96,11 @@ function getAllowedImageEntries(tree) {
|
|
|
96
96
|
* @param entryPath Entry name inside the zip
|
|
97
97
|
* @returns Image data buffer or null if not found
|
|
98
98
|
*/
|
|
99
|
-
async function openImage(gridsetBuffer, entryPath, password = (0, password_1.resolveGridsetPasswordFromEnv)()) {
|
|
99
|
+
async function openImage(gridsetBuffer, entryPath, password = (0, password_1.resolveGridsetPasswordFromEnv)(), zipAdapter) {
|
|
100
100
|
try {
|
|
101
|
-
const { zip } =
|
|
101
|
+
const { zip } = zipAdapter
|
|
102
|
+
? await zipAdapter(gridsetBuffer)
|
|
103
|
+
: await (0, zip_1.openZipFromInput)(gridsetBuffer);
|
|
102
104
|
const entries = (0, password_1.getZipEntriesFromAdapter)(zip, password);
|
|
103
105
|
const want = normalizeZipPath(entryPath);
|
|
104
106
|
const entry = entries.find((e) => normalizeZipPath(e.entryName) === want);
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* These utilities help developers understand why images might not be resolving
|
|
5
5
|
* correctly in Grid3 gridsets.
|
|
6
6
|
*/
|
|
7
|
+
import { type ZipAdapter } from '../../utils/zip';
|
|
8
|
+
import { type ProcessorInput } from '../../utils/io';
|
|
7
9
|
export interface ImageIssue {
|
|
8
10
|
gridName: string;
|
|
9
11
|
cellX: number;
|
|
@@ -34,7 +36,9 @@ export interface ImageAuditResult {
|
|
|
34
36
|
* console.log(`Cell (${issue.cellX}, ${issue.cellY}): ${issue.suggestion}`);
|
|
35
37
|
* });
|
|
36
38
|
*/
|
|
37
|
-
export declare function auditGridsetImages(gridsetBuffer: Uint8Array, password?: string | undefined
|
|
39
|
+
export declare function auditGridsetImages(gridsetBuffer: Uint8Array, password?: string | undefined, zipAdapter?: (input: ProcessorInput) => Promise<{
|
|
40
|
+
zip: ZipAdapter;
|
|
41
|
+
}>): Promise<ImageAuditResult>;
|
|
38
42
|
/**
|
|
39
43
|
* Get a human-readable summary of image audit results
|
|
40
44
|
*/
|
|
@@ -26,7 +26,7 @@ const io_1 = require("../../utils/io");
|
|
|
26
26
|
* console.log(`Cell (${issue.cellX}, ${issue.cellY}): ${issue.suggestion}`);
|
|
27
27
|
* });
|
|
28
28
|
*/
|
|
29
|
-
async function auditGridsetImages(gridsetBuffer, password = (0, password_2.resolveGridsetPasswordFromEnv)()) {
|
|
29
|
+
async function auditGridsetImages(gridsetBuffer, password = (0, password_2.resolveGridsetPasswordFromEnv)(), zipAdapter) {
|
|
30
30
|
const issues = [];
|
|
31
31
|
const availableImages = new Set();
|
|
32
32
|
let totalCells = 0;
|
|
@@ -34,7 +34,9 @@ async function auditGridsetImages(gridsetBuffer, password = (0, password_2.resol
|
|
|
34
34
|
let resolvedImages = 0;
|
|
35
35
|
let unresolvedImages = 0;
|
|
36
36
|
try {
|
|
37
|
-
const { zip } =
|
|
37
|
+
const { zip } = zipAdapter
|
|
38
|
+
? await zipAdapter(gridsetBuffer)
|
|
39
|
+
: await (0, zip_1.openZipFromInput)(gridsetBuffer);
|
|
38
40
|
const entries = (0, password_1.getZipEntriesFromAdapter)(zip, password);
|
|
39
41
|
const parser = new fast_xml_parser_1.XMLParser();
|
|
40
42
|
// Collect all image files in the gridset
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* Note: Wordlists are only supported in Grid3 format. Other AAC formats
|
|
9
9
|
* do not have equivalent wordlist functionality.
|
|
10
10
|
*/
|
|
11
|
+
import { type ZipAdapter } from '../../utils/zip';
|
|
12
|
+
import { type ProcessorInput } from '../../utils/io';
|
|
11
13
|
/**
|
|
12
14
|
* Represents a single item in a wordlist
|
|
13
15
|
*/
|
|
@@ -64,7 +66,9 @@ export declare function wordlistToXml(wordlist: WordList): string;
|
|
|
64
66
|
* console.log(`Grid "${gridName}" has ${wordlist.items.length} items`);
|
|
65
67
|
* });
|
|
66
68
|
*/
|
|
67
|
-
export declare function extractWordlists(gridsetBuffer: Uint8Array, password?: string | undefined
|
|
69
|
+
export declare function extractWordlists(gridsetBuffer: Uint8Array, password?: string | undefined, zipAdapter?: (input: ProcessorInput) => Promise<{
|
|
70
|
+
zip: ZipAdapter;
|
|
71
|
+
}>): Promise<Map<string, WordList>>;
|
|
68
72
|
/**
|
|
69
73
|
* Updates or adds a wordlist to a specific grid in a gridset
|
|
70
74
|
*
|
|
@@ -127,11 +127,13 @@ function wordlistToXml(wordlist) {
|
|
|
127
127
|
* console.log(`Grid "${gridName}" has ${wordlist.items.length} items`);
|
|
128
128
|
* });
|
|
129
129
|
*/
|
|
130
|
-
async function extractWordlists(gridsetBuffer, password = (0, password_1.resolveGridsetPasswordFromEnv)()) {
|
|
130
|
+
async function extractWordlists(gridsetBuffer, password = (0, password_1.resolveGridsetPasswordFromEnv)(), zipAdapter) {
|
|
131
131
|
const wordlists = new Map();
|
|
132
132
|
const parser = new fast_xml_parser_1.XMLParser();
|
|
133
133
|
try {
|
|
134
|
-
const { zip } =
|
|
134
|
+
const { zip } = zipAdapter
|
|
135
|
+
? await zipAdapter(gridsetBuffer)
|
|
136
|
+
: await (0, zip_1.openZipFromInput)(gridsetBuffer);
|
|
135
137
|
const entries = (0, password_1.getZipEntriesFromAdapter)(zip, password);
|
|
136
138
|
// Process each grid file
|
|
137
139
|
for (const entry of entries) {
|
|
@@ -424,7 +424,10 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
424
424
|
const tree = new treeStructure_1.AACTree();
|
|
425
425
|
let zipResult;
|
|
426
426
|
try {
|
|
427
|
-
|
|
427
|
+
const zipInput = (0, io_1.readBinaryFromInput)(filePathOrBuffer);
|
|
428
|
+
zipResult = this.options.zipAdapter
|
|
429
|
+
? await this.options.zipAdapter(zipInput)
|
|
430
|
+
: await (0, zip_1.openZipFromInput)(zipInput);
|
|
428
431
|
}
|
|
429
432
|
catch (error) {
|
|
430
433
|
throw new Error(`Invalid ZIP file format: ${error.message}`);
|
|
@@ -378,9 +378,20 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
378
378
|
throw err;
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
|
-
//
|
|
382
|
-
|
|
383
|
-
|
|
381
|
+
// Detect likely zip signature first
|
|
382
|
+
function isLikelyZip(input) {
|
|
383
|
+
if (typeof input === 'string') {
|
|
384
|
+
const lowered = input.toLowerCase();
|
|
385
|
+
return lowered.endsWith('.zip') || lowered.endsWith('.obz');
|
|
386
|
+
}
|
|
387
|
+
const bytes = (0, io_1.readBinaryFromInput)(input);
|
|
388
|
+
return bytes.length >= 2 && bytes[0] === 0x50 && bytes[1] === 0x4b;
|
|
389
|
+
}
|
|
390
|
+
// Check if input is a buffer or string that parses as OBF JSON; throw if neither JSON nor ZIP
|
|
391
|
+
if (!isLikelyZip(filePathOrBuffer)) {
|
|
392
|
+
const asJson = tryParseObfJson(filePathOrBuffer);
|
|
393
|
+
if (!asJson)
|
|
394
|
+
throw new Error('Invalid OBF content: not JSON and not ZIP');
|
|
384
395
|
console.log('[OBF] Detected buffer/string as OBF JSON');
|
|
385
396
|
const page = await this.processBoard(asJson, '[bufferOrString]');
|
|
386
397
|
tree.addPage(page);
|
|
@@ -398,20 +409,10 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
398
409
|
tree.rootId = page.id;
|
|
399
410
|
return tree;
|
|
400
411
|
}
|
|
401
|
-
// Otherwise, try as ZIP (.obz). Detect likely zip signature first; throw if neither JSON nor ZIP
|
|
402
|
-
function isLikelyZip(input) {
|
|
403
|
-
if (typeof input === 'string') {
|
|
404
|
-
const lowered = input.toLowerCase();
|
|
405
|
-
return lowered.endsWith('.zip') || lowered.endsWith('.obz');
|
|
406
|
-
}
|
|
407
|
-
const bytes = (0, io_1.readBinaryFromInput)(input);
|
|
408
|
-
return bytes.length >= 2 && bytes[0] === 0x50 && bytes[1] === 0x4b;
|
|
409
|
-
}
|
|
410
|
-
if (!isLikelyZip(filePathOrBuffer)) {
|
|
411
|
-
throw new Error('Invalid OBF content: not JSON and not ZIP');
|
|
412
|
-
}
|
|
413
412
|
try {
|
|
414
|
-
const zipResult =
|
|
413
|
+
const zipResult = this.options.zipAdapter
|
|
414
|
+
? await this.options.zipAdapter(filePathOrBuffer)
|
|
415
|
+
: await (0, zip_1.openZipFromInput)(filePathOrBuffer);
|
|
415
416
|
this.zipFile = zipResult.zip;
|
|
416
417
|
}
|
|
417
418
|
catch (err) {
|
|
@@ -43,6 +43,11 @@ const path = __importStar(require("path"));
|
|
|
43
43
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
44
44
|
const dotnetTicks_1 = require("../../utils/dotnetTicks");
|
|
45
45
|
// Minimal Snap helpers (stubs) to align with processors/<engine>/helpers pattern
|
|
46
|
+
// NOTE: Snap files can store different types of image data in PageSetData:
|
|
47
|
+
// - PNG/JPEG binaries: Actual images that can be displayed
|
|
48
|
+
// - Vector graphics: Custom format (d7 cd c6 9a) requiring rendering engine
|
|
49
|
+
//
|
|
50
|
+
// We extract PNG/JPEG images but skip vector graphics (requires renderer).
|
|
46
51
|
// NOTE: Snap buttons currently do not populate resolvedImageEntry; these helpers
|
|
47
52
|
// therefore return empty collections until image resolution is implemented.
|
|
48
53
|
function collectFiles(root, matcher, maxDepth = 3) {
|
|
@@ -144,6 +149,9 @@ function openImage(dbOrFile, entryPath) {
|
|
|
144
149
|
.prepare('SELECT Id, Identifier, Data FROM PageSetData WHERE Identifier = ?')
|
|
145
150
|
.get(entryPath);
|
|
146
151
|
if (row && row.Data && row.Data.length > 0) {
|
|
152
|
+
// Snap files can store different types of image data:
|
|
153
|
+
// 1. PNG/JPEG binaries (actual images) - return as-is
|
|
154
|
+
// 2. Vector graphics (custom format d7 cd c6 9a) - return but may not be displayable
|
|
147
155
|
return row.Data;
|
|
148
156
|
}
|
|
149
157
|
return null;
|
|
@@ -7,6 +7,25 @@ const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
|
|
|
7
7
|
const snapValidator_1 = require("../validation/snapValidator");
|
|
8
8
|
const io_1 = require("../utils/io");
|
|
9
9
|
const sqlite_1 = require("../utils/sqlite");
|
|
10
|
+
/**
|
|
11
|
+
* Convert a Buffer or Uint8Array to base64 string (browser and Node compatible)
|
|
12
|
+
* Node.js Buffers support toString('base64'), but Uint8Arrays in browser do not.
|
|
13
|
+
* This function works in both environments.
|
|
14
|
+
*/
|
|
15
|
+
function arrayBufferToBase64(data) {
|
|
16
|
+
// Node.js environment - Buffer has built-in base64 encoding
|
|
17
|
+
if (typeof Buffer !== 'undefined' && data instanceof Buffer) {
|
|
18
|
+
return data.toString('base64');
|
|
19
|
+
}
|
|
20
|
+
// Browser environment - use btoa with binary string conversion
|
|
21
|
+
const bytes = new Uint8Array(data);
|
|
22
|
+
let binary = '';
|
|
23
|
+
const len = bytes.byteLength;
|
|
24
|
+
for (let i = 0; i < len; i++) {
|
|
25
|
+
binary += String.fromCharCode(bytes[i]);
|
|
26
|
+
}
|
|
27
|
+
return btoa(binary);
|
|
28
|
+
}
|
|
10
29
|
/**
|
|
11
30
|
* Map Snap Visible value to AAC standard visibility
|
|
12
31
|
* Snap: 0 = hidden, 1 (or non-zero) = visible
|
|
@@ -18,41 +37,6 @@ function mapSnapVisibility(visible) {
|
|
|
18
37
|
}
|
|
19
38
|
return visible === 0 ? 'Hidden' : 'Visible';
|
|
20
39
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Detect image MIME type from binary data using magic bytes
|
|
23
|
-
* @param buffer Image data buffer
|
|
24
|
-
* @returns MIME type string (defaults to 'image/png' if unknown)
|
|
25
|
-
*/
|
|
26
|
-
function detectImageMimeType(buffer) {
|
|
27
|
-
if (!buffer || buffer.length < 8) {
|
|
28
|
-
return 'image/png';
|
|
29
|
-
}
|
|
30
|
-
// Check for PNG: 89 50 4E 47 0D 0A 1A 0A
|
|
31
|
-
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47) {
|
|
32
|
-
return 'image/png';
|
|
33
|
-
}
|
|
34
|
-
// Check for JPEG: FF D8 FF
|
|
35
|
-
if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
|
|
36
|
-
return 'image/jpeg';
|
|
37
|
-
}
|
|
38
|
-
// Check for GIF: 47 49 46 38 (GIF8)
|
|
39
|
-
if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
|
|
40
|
-
return 'image/gif';
|
|
41
|
-
}
|
|
42
|
-
// Check for WebP: 52 49 46 46 ... 57 45 42 50 (RIFF...WEBP)
|
|
43
|
-
if (buffer[0] === 0x52 &&
|
|
44
|
-
buffer[1] === 0x49 &&
|
|
45
|
-
buffer[2] === 0x46 &&
|
|
46
|
-
buffer[3] === 0x46 &&
|
|
47
|
-
buffer[8] === 0x57 &&
|
|
48
|
-
buffer[9] === 0x45 &&
|
|
49
|
-
buffer[10] === 0x42 &&
|
|
50
|
-
buffer[11] === 0x50) {
|
|
51
|
-
return 'image/webp';
|
|
52
|
-
}
|
|
53
|
-
// Default to PNG
|
|
54
|
-
return 'image/png';
|
|
55
|
-
}
|
|
56
40
|
class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
57
41
|
constructor(symbolResolver = null, options = {}) {
|
|
58
42
|
super(options);
|
|
@@ -468,14 +452,30 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
468
452
|
`)
|
|
469
453
|
.get(btnRow.PageSetImageId);
|
|
470
454
|
if (imageData && imageData.Data && imageData.Data.length > 0) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
//
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
455
|
+
// Snap files can store different types of image data:
|
|
456
|
+
// 1. PNG/JPEG binaries (actual images) - extract and display
|
|
457
|
+
// 2. Vector graphics (custom format d7 cd c6 9a) - skip (requires renderer)
|
|
458
|
+
const data = imageData.Data;
|
|
459
|
+
// Check for PNG: 89 50 4E 47
|
|
460
|
+
const isPng = data.length > 4 &&
|
|
461
|
+
data[0] === 0x89 &&
|
|
462
|
+
data[1] === 0x50 &&
|
|
463
|
+
data[2] === 0x4e &&
|
|
464
|
+
data[3] === 0x47;
|
|
465
|
+
// Check for JPEG: FF D8 FF
|
|
466
|
+
const isJpeg = data.length > 3 && data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff;
|
|
467
|
+
if (isPng || isJpeg) {
|
|
468
|
+
// Actual PNG/JPEG image - can be displayed
|
|
469
|
+
const mimeType = isPng ? 'image/png' : 'image/jpeg';
|
|
470
|
+
const base64 = arrayBufferToBase64(data);
|
|
471
|
+
buttonImage = `data:${mimeType};base64,${base64}`;
|
|
472
|
+
buttonParameters.image_id = imageData.Identifier;
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
// Vector graphics or other format - skip rendering
|
|
476
|
+
// Store identifier but don't create image URL
|
|
477
|
+
buttonParameters.image_id = imageData.Identifier;
|
|
478
|
+
}
|
|
479
479
|
}
|
|
480
480
|
}
|
|
481
481
|
catch (e) {
|
|
@@ -67,7 +67,9 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
67
67
|
this.sourceFile = filePathOrBuffer;
|
|
68
68
|
// Step 1: Unzip
|
|
69
69
|
const zipInput = (0, io_1.readBinaryFromInput)(filePathOrBuffer);
|
|
70
|
-
const { zip } =
|
|
70
|
+
const { zip } = this.options.zipAdapter
|
|
71
|
+
? await this.options.zipAdapter(zipInput)
|
|
72
|
+
: await (0, zip_1.openZipFromInput)(zipInput);
|
|
71
73
|
const vocabEntry = zip.listFiles().find((name) => name.endsWith('.c4v'));
|
|
72
74
|
if (!vocabEntry) {
|
|
73
75
|
throw new Error('No .c4v vocab DB found in TouchChat export');
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { BaseValidator } from './baseValidator';
|
|
2
2
|
import { ValidationResult } from './validationTypes';
|
|
3
|
+
import { type ProcessorInput } from '../utils/io';
|
|
4
|
+
import { type ZipAdapter } from '../utils/zip';
|
|
3
5
|
/**
|
|
4
6
|
* Validator for TouchChat files (.ce)
|
|
5
7
|
* TouchChat files are ZIP archives that contain a .c4v SQLite database.
|
|
@@ -14,7 +16,9 @@ export declare class TouchChatValidator extends BaseValidator {
|
|
|
14
16
|
/**
|
|
15
17
|
* Check if content is TouchChat format
|
|
16
18
|
*/
|
|
17
|
-
static identifyFormat(content: any, filename: string
|
|
19
|
+
static identifyFormat(content: any, filename: string, zipAdapter?: (input: ProcessorInput) => Promise<{
|
|
20
|
+
zip: ZipAdapter;
|
|
21
|
+
}>): Promise<boolean>;
|
|
18
22
|
/**
|
|
19
23
|
* Main validation method
|
|
20
24
|
*/
|
|
@@ -53,14 +53,14 @@ class TouchChatValidator extends baseValidator_1.BaseValidator {
|
|
|
53
53
|
/**
|
|
54
54
|
* Check if content is TouchChat format
|
|
55
55
|
*/
|
|
56
|
-
static async identifyFormat(content, filename) {
|
|
56
|
+
static async identifyFormat(content, filename, zipAdapter) {
|
|
57
57
|
const name = filename.toLowerCase();
|
|
58
58
|
if (name.endsWith('.ce')) {
|
|
59
59
|
return true;
|
|
60
60
|
}
|
|
61
61
|
// Try to parse as ZIP and check for .c4v database
|
|
62
62
|
try {
|
|
63
|
-
const { zip } = await (0, zip_1.openZipFromInput)(content);
|
|
63
|
+
const { zip } = zipAdapter ? await zipAdapter(content) : await (0, zip_1.openZipFromInput)(content);
|
|
64
64
|
const entries = zip.listFiles();
|
|
65
65
|
if (entries.some((entry) => entry.toLowerCase().endsWith('.c4v'))) {
|
|
66
66
|
return true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@willwade/aac-processors",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
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",
|