Skip to content

Commit c469c9c

Browse files
committed
Added zipAdapter option to allow custom unzipping
1 parent edd2308 commit c469c9c

7 files changed

Lines changed: 40 additions & 17 deletions

File tree

src/core/baseProcessor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export interface ProcessorOptions {
7373
// Defaults to 'en-GB' if not specified
7474
grid3Locale?: string;
7575

76+
// Optionally provide your own adapter for unzipping the input
7677
zipAdapter?: (input: ProcessorInput) => Promise<{ zip: ZipAdapter }>
7778
}
7879

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/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: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
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 { decodeText, getBasename, getFs, type ProcessorInput, readBinaryFromInput, toUint8Array } from '../utils/io';
8+
import { openZipFromInput, type ZipAdapter } from '../utils/zip';
99
import { openSqliteDatabase } from '../utils/sqlite';
1010

1111
/**
@@ -31,15 +31,21 @@ export class TouchChatValidator extends BaseValidator {
3131
/**
3232
* Check if content is TouchChat format
3333
*/
34-
static async identifyFormat(content: any, filename: string): Promise<boolean> {
34+
static async identifyFormat(
35+
content: any,
36+
filename: string,
37+
zipAdapter?: (input: ProcessorInput) => Promise<{ zip: ZipAdapter }>
38+
): Promise<boolean> {
3539
const name = filename.toLowerCase();
3640
if (name.endsWith('.ce')) {
3741
return true;
3842
}
3943

4044
// Try to parse as ZIP and check for .c4v database
4145
try {
42-
const { zip } = await openZipFromInput(content);
46+
const { zip } = zipAdapter
47+
? await zipAdapter(content)
48+
: await openZipFromInput(content);
4349
const entries = zip.listFiles();
4450
if (entries.some((entry) => entry.toLowerCase().endsWith('.c4v'))) {
4551
return true;

0 commit comments

Comments
 (0)