Skip to content

Commit 9c25a40

Browse files
authored
Merge pull request #21 from sonicbaume/custom-adapters
Add option to provide custom zip adapter
2 parents 5cfbdab + b3be0e7 commit 9c25a40

10 files changed

Lines changed: 79 additions & 40 deletions

File tree

src/core/baseProcessor.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { AACTree, AACButton, AACSemanticCategory } from './treeStructure';
4444
import { StringCasing, detectCasing, isNumericOrEmpty } from './stringCasing';
4545
import { ValidationResult } from '../validation/validationTypes';
4646
import { BinaryOutput, ProcessorInput } from '../utils/io';
47+
import type { ZipAdapter } from '../utils/zip';
4748

4849
// Configuration options for processors
4950
export interface ProcessorOptions {
@@ -71,6 +72,9 @@ export interface ProcessorOptions {
7172
// Locale for symbol libraries (e.g., 'en-GB', 'en-US')
7273
// Defaults to 'en-GB' if not specified
7374
grid3Locale?: string;
75+
76+
// Optionally provide your own adapter for unzipping the input
77+
zipAdapter?: (input: ProcessorInput) => Promise<{ zip: ZipAdapter }>;
7478
}
7579

7680
// Types for aac-tools-platform compatibility

src/index.browser.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export { AstericsGridProcessor } from './processors/astericsGridProcessor';
3838
// Metrics namespace (pageset analytics)
3939
export * as Metrics from './metrics';
4040

41-
import { BaseProcessor } from './core/baseProcessor';
41+
import { BaseProcessor, ProcessorOptions } from './core/baseProcessor';
4242
import { DotProcessor } from './processors/dotProcessor';
4343
import { OpmlProcessor } from './processors/opmlProcessor';
4444
import { ObfProcessor } from './processors/obfProcessor';
@@ -55,30 +55,33 @@ export { configureSqlJs } from './utils/sqlite';
5555
* @returns The appropriate processor instance
5656
* @throws Error if the file extension is not supported
5757
*/
58-
export function getProcessor(filePathOrExtension: string): BaseProcessor {
58+
export function getProcessor(
59+
filePathOrExtension: string,
60+
options?: ProcessorOptions
61+
): BaseProcessor {
5962
const extension = filePathOrExtension.includes('.')
6063
? filePathOrExtension.substring(filePathOrExtension.lastIndexOf('.'))
6164
: filePathOrExtension;
6265

6366
switch (extension.toLowerCase()) {
6467
case '.dot':
65-
return new DotProcessor();
68+
return new DotProcessor(options);
6669
case '.opml':
67-
return new OpmlProcessor();
70+
return new OpmlProcessor(options);
6871
case '.obf':
6972
case '.obz':
70-
return new ObfProcessor();
73+
return new ObfProcessor(options);
7174
case '.gridset':
72-
return new GridsetProcessor();
75+
return new GridsetProcessor(options);
7376
case '.spb':
7477
case '.sps':
75-
return new SnapProcessor();
78+
return new SnapProcessor(options);
7679
case '.ce':
77-
return new TouchChatProcessor();
80+
return new TouchChatProcessor(options);
7881
case '.plist':
79-
return new ApplePanelsProcessor();
82+
return new ApplePanelsProcessor(options);
8083
case '.grd':
81-
return new AstericsGridProcessor();
84+
return new AstericsGridProcessor(options);
8285
default:
8386
throw new Error(`Unsupported file extension: ${extension}`);
8487
}

src/index.node.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export * as Translation from './translation';
4848
// UTILITY FUNCTIONS
4949
// ===================================================================
5050

51-
import { BaseProcessor } from './core/baseProcessor';
51+
import { BaseProcessor, ProcessorOptions } from './core/baseProcessor';
5252
import { DotProcessor } from './processors/dotProcessor';
5353
import { ExcelProcessor } from './processors/excelProcessor';
5454
import { OpmlProcessor } from './processors/opmlProcessor';
@@ -70,36 +70,39 @@ import { ObfsetProcessor } from './processors/obfsetProcessor';
7070
* const processor = getProcessor('/path/to/file.gridset');
7171
* const tree = processor.loadIntoTree('/path/to/file.gridset');
7272
*/
73-
export function getProcessor(filePathOrExtension: string): BaseProcessor {
73+
export function getProcessor(
74+
filePathOrExtension: string,
75+
options?: ProcessorOptions
76+
): BaseProcessor {
7477
// Extract extension from file path
7578
const extension = filePathOrExtension.includes('.')
7679
? filePathOrExtension.substring(filePathOrExtension.lastIndexOf('.'))
7780
: filePathOrExtension;
7881

7982
switch (extension.toLowerCase()) {
8083
case '.dot':
81-
return new DotProcessor();
84+
return new DotProcessor(options);
8285
case '.xlsx':
83-
return new ExcelProcessor();
86+
return new ExcelProcessor(options);
8487
case '.opml':
85-
return new OpmlProcessor();
88+
return new OpmlProcessor(options);
8689
case '.obf':
8790
case '.obz':
88-
return new ObfProcessor();
91+
return new ObfProcessor(options);
8992
case '.obfset':
90-
return new ObfsetProcessor();
93+
return new ObfsetProcessor(options);
9194
case '.gridset':
9295
case '.gridsetx':
93-
return new GridsetProcessor();
96+
return new GridsetProcessor(options);
9497
case '.spb':
9598
case '.sps':
96-
return new SnapProcessor();
99+
return new SnapProcessor(options);
97100
case '.ce':
98-
return new TouchChatProcessor();
101+
return new TouchChatProcessor(options);
99102
case '.plist':
100-
return new ApplePanelsProcessor();
103+
return new ApplePanelsProcessor(options);
101104
case '.grd':
102-
return new AstericsGridProcessor();
105+
return new AstericsGridProcessor(options);
103106
default:
104107
throw new Error(`Unsupported file extension: ${extension}`);
105108
}

src/processors/gridset/helpers.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import { execSync } from 'child_process';
1212
import Database from 'better-sqlite3';
1313
import { dotNetTicksToDate } from '../../utils/dotnetTicks';
1414
import { getZipEntriesFromAdapter, resolveGridsetPasswordFromEnv } from './password';
15-
import { openZipFromInput } from '../../utils/zip';
15+
import { openZipFromInput, type ZipAdapter } from '../../utils/zip';
16+
import type { ProcessorInput } from '../../utils/io';
1617

1718
function normalizeZipPath(p: string): string {
1819
const unified = p.replace(/\\/g, '/');
@@ -62,10 +63,13 @@ export function getAllowedImageEntries(tree: AACTree): Set<string> {
6263
export async function openImage(
6364
gridsetBuffer: Uint8Array,
6465
entryPath: string,
65-
password = resolveGridsetPasswordFromEnv()
66+
password = resolveGridsetPasswordFromEnv(),
67+
zipAdapter?: (input: ProcessorInput) => Promise<{ zip: ZipAdapter }>
6668
): Promise<Uint8Array | null> {
6769
try {
68-
const { zip } = await openZipFromInput(gridsetBuffer);
70+
const { zip } = zipAdapter
71+
? await zipAdapter(gridsetBuffer)
72+
: await openZipFromInput(gridsetBuffer);
6973
const entries = getZipEntriesFromAdapter(zip, password);
7074
const want = normalizeZipPath(entryPath);
7175
const entry = entries.find((e) => normalizeZipPath(e.entryName) === want);

src/processors/gridset/imageDebug.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
* correctly in Grid3 gridsets.
66
*/
77

8-
import { openZipFromInput } from '../../utils/zip';
8+
import { openZipFromInput, type ZipAdapter } from '../../utils/zip';
99
import { getZipEntriesFromAdapter } from './password';
1010
import { resolveGridsetPasswordFromEnv } from './password';
1111
import { XMLParser } from 'fast-xml-parser';
12-
import { decodeText } from '../../utils/io';
12+
import { decodeText, type ProcessorInput } from '../../utils/io';
1313

1414
export interface ImageIssue {
1515
gridName: string;
@@ -45,7 +45,8 @@ export interface ImageAuditResult {
4545
*/
4646
export async function auditGridsetImages(
4747
gridsetBuffer: Uint8Array,
48-
password = resolveGridsetPasswordFromEnv()
48+
password = resolveGridsetPasswordFromEnv(),
49+
zipAdapter?: (input: ProcessorInput) => Promise<{ zip: ZipAdapter }>
4950
): Promise<ImageAuditResult> {
5051
const issues: ImageIssue[] = [];
5152
const availableImages = new Set<string>();
@@ -55,7 +56,10 @@ export async function auditGridsetImages(
5556
let unresolvedImages = 0;
5657

5758
try {
58-
const { zip } = await openZipFromInput(gridsetBuffer);
59+
const { zip } = zipAdapter
60+
? await zipAdapter(gridsetBuffer)
61+
: await openZipFromInput(gridsetBuffer);
62+
5963
const entries = getZipEntriesFromAdapter(zip, password);
6064
const parser = new XMLParser();
6165

src/processors/gridset/wordlistHelpers.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
1313
import { getZipEntriesFromAdapter, resolveGridsetPasswordFromEnv } from './password';
14-
import { openZipFromInput } from '../../utils/zip';
15-
import { getNodeRequire, isNodeRuntime } from '../../utils/io';
14+
import { openZipFromInput, type ZipAdapter } from '../../utils/zip';
15+
import { getNodeRequire, isNodeRuntime, type ProcessorInput } from '../../utils/io';
1616
import { decodeText } from '../../utils/io';
1717

1818
/**
@@ -130,13 +130,16 @@ export function wordlistToXml(wordlist: WordList): string {
130130
*/
131131
export async function extractWordlists(
132132
gridsetBuffer: Uint8Array,
133-
password = resolveGridsetPasswordFromEnv()
133+
password = resolveGridsetPasswordFromEnv(),
134+
zipAdapter?: (input: ProcessorInput) => Promise<{ zip: ZipAdapter }>
134135
): Promise<Map<string, WordList>> {
135136
const wordlists = new Map<string, WordList>();
136137
const parser = new XMLParser();
137138

138139
try {
139-
const { zip } = await openZipFromInput(gridsetBuffer);
140+
const { zip } = zipAdapter
141+
? await zipAdapter(gridsetBuffer)
142+
: await openZipFromInput(gridsetBuffer);
140143
const entries = getZipEntriesFromAdapter(zip, password);
141144

142145
// Process each grid file

src/processors/gridsetProcessor.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,10 @@ class GridsetProcessor extends BaseProcessor {
450450

451451
let zipResult: Awaited<ReturnType<typeof openZipFromInput>>;
452452
try {
453-
zipResult = await openZipFromInput(readBinaryFromInput(filePathOrBuffer));
453+
const zipInput = readBinaryFromInput(filePathOrBuffer);
454+
zipResult = this.options.zipAdapter
455+
? await this.options.zipAdapter(zipInput)
456+
: await openZipFromInput(zipInput);
454457
} catch (error: any) {
455458
throw new Error(`Invalid ZIP file format: ${error.message}`);
456459
}

src/processors/obfProcessor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,9 @@ class ObfProcessor extends BaseProcessor {
497497
}
498498

499499
try {
500-
const zipResult = await openZipFromInput(filePathOrBuffer);
500+
const zipResult = this.options.zipAdapter
501+
? await this.options.zipAdapter(filePathOrBuffer)
502+
: await openZipFromInput(filePathOrBuffer);
501503
this.zipFile = zipResult.zip;
502504
} catch (err) {
503505
console.error('[OBF] Error loading ZIP:', err);

src/processors/touchchatProcessor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,9 @@ class TouchChatProcessor extends BaseProcessor {
153153

154154
// Step 1: Unzip
155155
const zipInput = readBinaryFromInput(filePathOrBuffer);
156-
const { zip } = await openZipFromInput(zipInput);
156+
const { zip } = this.options.zipAdapter
157+
? await this.options.zipAdapter(zipInput)
158+
: await openZipFromInput(zipInput);
157159
const vocabEntry = zip.listFiles().find((name) => name.endsWith('.c4v'));
158160
if (!vocabEntry) {
159161
throw new Error('No .c4v vocab DB found in TouchChat export');

src/validation/touchChatValidator.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@
44
import * as xml2js from 'xml2js';
55
import { BaseValidator } from './baseValidator';
66
import { ValidationResult } from './validationTypes';
7-
import { decodeText, getBasename, getFs, readBinaryFromInput, toUint8Array } from '../utils/io';
8-
import { openZipFromInput } from '../utils/zip';
7+
import {
8+
decodeText,
9+
getBasename,
10+
getFs,
11+
type ProcessorInput,
12+
readBinaryFromInput,
13+
toUint8Array,
14+
} from '../utils/io';
15+
import { openZipFromInput, type ZipAdapter } from '../utils/zip';
916
import { openSqliteDatabase } from '../utils/sqlite';
1017

1118
/**
@@ -31,15 +38,19 @@ export class TouchChatValidator extends BaseValidator {
3138
/**
3239
* Check if content is TouchChat format
3340
*/
34-
static async identifyFormat(content: any, filename: string): Promise<boolean> {
41+
static async identifyFormat(
42+
content: any,
43+
filename: string,
44+
zipAdapter?: (input: ProcessorInput) => Promise<{ zip: ZipAdapter }>
45+
): Promise<boolean> {
3546
const name = filename.toLowerCase();
3647
if (name.endsWith('.ce')) {
3748
return true;
3849
}
3950

4051
// Try to parse as ZIP and check for .c4v database
4152
try {
42-
const { zip } = await openZipFromInput(content);
53+
const { zip } = zipAdapter ? await zipAdapter(content) : await openZipFromInput(content);
4354
const entries = zip.listFiles();
4455
if (entries.some((entry) => entry.toLowerCase().endsWith('.c4v'))) {
4556
return true;

0 commit comments

Comments
 (0)