@willwade/aac-processors 0.0.9 โ 0.0.10
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/README.md +85 -11
- package/dist/cli/index.js +87 -0
- package/dist/core/analyze.js +1 -0
- package/dist/core/baseProcessor.d.ts +3 -0
- package/dist/core/fileProcessor.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/optional/symbolTools.js +4 -2
- package/dist/processors/gridset/helpers.d.ts +1 -1
- package/dist/processors/gridset/helpers.js +5 -3
- package/dist/processors/gridset/password.d.ts +11 -0
- package/dist/processors/gridset/password.js +37 -0
- package/dist/processors/gridset/resolver.d.ts +1 -1
- package/dist/processors/gridset/resolver.js +8 -4
- package/dist/processors/gridset/wordlistHelpers.d.ts +2 -2
- package/dist/processors/gridset/wordlistHelpers.js +7 -4
- package/dist/processors/gridsetProcessor.d.ts +15 -1
- package/dist/processors/gridsetProcessor.js +64 -22
- package/dist/processors/index.d.ts +1 -0
- package/dist/processors/index.js +5 -2
- package/dist/processors/obfProcessor.d.ts +7 -0
- package/dist/processors/obfProcessor.js +9 -0
- package/dist/processors/snapProcessor.d.ts +7 -0
- package/dist/processors/snapProcessor.js +9 -0
- package/dist/processors/touchchatProcessor.d.ts +7 -0
- package/dist/processors/touchchatProcessor.js +9 -0
- package/dist/utilities/screenshotConverter.d.ts +69 -0
- package/dist/utilities/screenshotConverter.js +453 -0
- package/dist/validation/baseValidator.d.ts +80 -0
- package/dist/validation/baseValidator.js +160 -0
- package/dist/validation/gridsetValidator.d.ts +36 -0
- package/dist/validation/gridsetValidator.js +288 -0
- package/dist/validation/index.d.ts +13 -0
- package/dist/validation/index.js +69 -0
- package/dist/validation/obfValidator.d.ts +44 -0
- package/dist/validation/obfValidator.js +530 -0
- package/dist/validation/snapValidator.d.ts +33 -0
- package/dist/validation/snapValidator.js +237 -0
- package/dist/validation/touchChatValidator.d.ts +33 -0
- package/dist/validation/touchChatValidator.js +229 -0
- package/dist/validation/validationTypes.d.ts +64 -0
- package/dist/validation/validationTypes.js +15 -0
- package/examples/README.md +7 -0
- package/examples/demo.js +143 -0
- package/examples/obf/aboutme.json +376 -0
- package/examples/obf/array.json +6 -0
- package/examples/obf/hash.json +4 -0
- package/examples/obf/links.obz +0 -0
- package/examples/obf/simple.obf +53 -0
- package/examples/package-lock.json +1326 -0
- package/examples/package.json +10 -0
- package/examples/styling-example.ts +316 -0
- package/examples/translate.js +39 -0
- package/examples/translate_demo.js +254 -0
- package/examples/typescript-demo.ts +251 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ A comprehensive **TypeScript library** for processing AAC (Augmentative and Alte
|
|
|
27
27
|
- ๐ **Translation workflows** - Built-in i18n support with `processTexts()`
|
|
28
28
|
- ๐จ **Comprehensive styling support** - Preserve visual appearance across formats
|
|
29
29
|
- ๐งช **Property-based testing** - Robust validation with 140+ tests
|
|
30
|
+
- โ
**Format validation** - Spec-based validation for all supported formats
|
|
30
31
|
- โก **Performance optimized** - Memory-efficient processing of large files
|
|
31
32
|
- ๐ก๏ธ **Error recovery** - Graceful handling of corrupted data
|
|
32
33
|
- ๐ **Thread-safe** - Concurrent processing support
|
|
@@ -191,6 +192,78 @@ const translatedBuffer = processor.processTexts(
|
|
|
191
192
|
console.log("Translation complete!");
|
|
192
193
|
```
|
|
193
194
|
|
|
195
|
+
### Format Validation
|
|
196
|
+
|
|
197
|
+
Validate AAC files against format specifications to ensure data integrity:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { ObfProcessor, GridsetProcessor } from "aac-processors";
|
|
201
|
+
|
|
202
|
+
// Validate OBF/OBZ files
|
|
203
|
+
const obfProcessor = new ObfProcessor();
|
|
204
|
+
const result = await obfProcessor.validate("board.obf");
|
|
205
|
+
|
|
206
|
+
console.log(`Valid: ${result.valid}`);
|
|
207
|
+
console.log(`Errors: ${result.errors}`);
|
|
208
|
+
console.log(`Warnings: ${result.warnings}`);
|
|
209
|
+
|
|
210
|
+
// Detailed validation results
|
|
211
|
+
if (!result.valid) {
|
|
212
|
+
result.results
|
|
213
|
+
.filter((check) => !check.valid)
|
|
214
|
+
.forEach((check) => {
|
|
215
|
+
console.log(`โ ${check.description}: ${check.error}`);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Validate Gridset files (with optional password for encrypted files)
|
|
220
|
+
const gridsetProcessor = new GridsetProcessor({
|
|
221
|
+
gridsetPassword: "optional-password",
|
|
222
|
+
});
|
|
223
|
+
const gridsetResult = await gridsetProcessor.validate("vocab.gridsetx");
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### Using the CLI
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Validate a file
|
|
230
|
+
aacprocessors validate board.obf
|
|
231
|
+
|
|
232
|
+
# JSON output
|
|
233
|
+
aacprocessors validate board.obf --json
|
|
234
|
+
|
|
235
|
+
# Quiet mode (just valid/invalid)
|
|
236
|
+
aacprocessors validate board.gridset --quiet
|
|
237
|
+
|
|
238
|
+
# Validate encrypted Gridset file
|
|
239
|
+
aacprocessors validate board.gridsetx --gridset-password <password>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### What Gets Validated?
|
|
243
|
+
|
|
244
|
+
- **OBF/OBZ**: Spec compliance (Open Board Format)
|
|
245
|
+
- Required fields (format, id, locale, buttons, grid, images, sounds)
|
|
246
|
+
- Grid structure (rows, columns, order)
|
|
247
|
+
- Button references (image_id, sound_id, load_board paths)
|
|
248
|
+
- Color formats (RGB/RGBA)
|
|
249
|
+
- Cross-reference validation
|
|
250
|
+
|
|
251
|
+
- **Gridset**: XML structure
|
|
252
|
+
- Required elements (gridset, pages, cells)
|
|
253
|
+
- FixedCellSize configuration
|
|
254
|
+
- Page and cell attributes
|
|
255
|
+
- Image references
|
|
256
|
+
|
|
257
|
+
- **Snap**: Package structure
|
|
258
|
+
- ZIP package validity
|
|
259
|
+
- Settings file format
|
|
260
|
+
- Page/button configurations
|
|
261
|
+
|
|
262
|
+
- **TouchChat**: XML structure
|
|
263
|
+
- PageSet hierarchy
|
|
264
|
+
- Button definitions
|
|
265
|
+
- Navigation links
|
|
266
|
+
|
|
194
267
|
### Cross-Format Conversion
|
|
195
268
|
|
|
196
269
|
Convert between any supported AAC formats:
|
|
@@ -506,6 +579,7 @@ npx aac-processors analyze examples/example.gridset --pretty
|
|
|
506
579
|
- `--pretty` - Human-readable output (analyze command)
|
|
507
580
|
- `--verbose` - Detailed output (extract command)
|
|
508
581
|
- `--quiet` - Minimal output (extract command)
|
|
582
|
+
- `--gridset-password <password>` - Password for encrypted Grid 3 archives (`.gridsetx`)
|
|
509
583
|
|
|
510
584
|
**Button Filtering Options:**
|
|
511
585
|
|
|
@@ -594,17 +668,17 @@ interface AACButton {
|
|
|
594
668
|
|
|
595
669
|
### Supported Processors
|
|
596
670
|
|
|
597
|
-
| Processor | File Extensions
|
|
598
|
-
| ----------------------- |
|
|
599
|
-
| `DotProcessor` | `.dot`
|
|
600
|
-
| `OpmlProcessor` | `.opml`
|
|
601
|
-
| `ObfProcessor` | `.obf`, `.obz`
|
|
602
|
-
| `SnapProcessor` | `.sps`, `.spb`
|
|
603
|
-
| `GridsetProcessor` | `.gridset`
|
|
604
|
-
| `TouchChatProcessor` | `.ce`
|
|
605
|
-
| `ApplePanelsProcessor` | `.plist`
|
|
606
|
-
| `AstericsGridProcessor` | `.grd`
|
|
607
|
-
| `ExcelProcessor` | `.xlsx`
|
|
671
|
+
| Processor | File Extensions | Description |
|
|
672
|
+
| ----------------------- | ----------------------- | ----------------------------- |
|
|
673
|
+
| `DotProcessor` | `.dot` | Graphviz DOT format |
|
|
674
|
+
| `OpmlProcessor` | `.opml` | OPML hierarchical format |
|
|
675
|
+
| `ObfProcessor` | `.obf`, `.obz` | Open Board Format (JSON/ZIP) |
|
|
676
|
+
| `SnapProcessor` | `.sps`, `.spb` | Tobii Dynavox Snap format |
|
|
677
|
+
| `GridsetProcessor` | `.gridset`, `.gridsetx` | Smartbox Grid 3 format |
|
|
678
|
+
| `TouchChatProcessor` | `.ce` | PRC-Saltillo TouchChat format |
|
|
679
|
+
| `ApplePanelsProcessor` | `.plist` | iOS Apple Panels format |
|
|
680
|
+
| `AstericsGridProcessor` | `.grd` | Asterics Grid native format |
|
|
681
|
+
| `ExcelProcessor` | `.xlsx` | Microsoft Excel format |
|
|
608
682
|
|
|
609
683
|
---
|
|
610
684
|
|
package/dist/cli/index.js
CHANGED
|
@@ -23,6 +23,9 @@ function detectFormat(filePath) {
|
|
|
23
23
|
// Helper function to parse filtering options from CLI arguments
|
|
24
24
|
function parseFilteringOptions(options) {
|
|
25
25
|
const processorOptions = {};
|
|
26
|
+
if (options.gridsetPassword) {
|
|
27
|
+
processorOptions.gridsetPassword = options.gridsetPassword;
|
|
28
|
+
}
|
|
26
29
|
// Handle preserve all buttons flag
|
|
27
30
|
if (options.preserveAllButtons) {
|
|
28
31
|
processorOptions.preserveAllButtons = true;
|
|
@@ -63,6 +66,7 @@ commander_1.program
|
|
|
63
66
|
.option('--no-exclude-navigation', "Don't exclude navigation buttons (Home, Back)")
|
|
64
67
|
.option('--no-exclude-system', "Don't exclude system buttons (Delete, Clear, etc.)")
|
|
65
68
|
.option('--exclude-buttons <list>', 'Comma-separated list of button labels/terms to exclude')
|
|
69
|
+
.option('--gridset-password <password>', 'Password for encrypted Grid3 archives (.gridsetx)')
|
|
66
70
|
.action((file, options) => {
|
|
67
71
|
try {
|
|
68
72
|
// Parse filtering options
|
|
@@ -97,6 +101,7 @@ commander_1.program
|
|
|
97
101
|
.option('--no-exclude-navigation', "Don't exclude navigation buttons (Home, Back)")
|
|
98
102
|
.option('--no-exclude-system', "Don't exclude system buttons (Delete, Clear, etc.)")
|
|
99
103
|
.option('--exclude-buttons <list>', 'Comma-separated list of button labels/terms to exclude')
|
|
104
|
+
.option('--gridset-password <password>', 'Password for encrypted Grid3 archives (.gridsetx)')
|
|
100
105
|
.action((file, options) => {
|
|
101
106
|
try {
|
|
102
107
|
// Parse filtering options
|
|
@@ -142,6 +147,7 @@ commander_1.program
|
|
|
142
147
|
.option('--no-exclude-navigation', "Don't exclude navigation buttons (Home, Back)")
|
|
143
148
|
.option('--no-exclude-system', "Don't exclude system buttons (Delete, Clear, etc.)")
|
|
144
149
|
.option('--exclude-buttons <list>', 'Comma-separated list of button labels/terms to exclude')
|
|
150
|
+
.option('--gridset-password <password>', 'Password for encrypted Grid3 archives (.gridsetx)')
|
|
145
151
|
.action(async (input, output, options) => {
|
|
146
152
|
try {
|
|
147
153
|
if (!options.format) {
|
|
@@ -182,6 +188,87 @@ commander_1.program
|
|
|
182
188
|
process.exit(1);
|
|
183
189
|
}
|
|
184
190
|
});
|
|
191
|
+
commander_1.program
|
|
192
|
+
.command('validate <file>')
|
|
193
|
+
.description('Validate an AAC file format')
|
|
194
|
+
.option('--format <format>', 'Format type (auto-detected if not specified)')
|
|
195
|
+
.option('--json', 'Output results as JSON')
|
|
196
|
+
.option('--quiet', 'Only output validation result (valid/invalid)')
|
|
197
|
+
.option('--gridset-password <password>', 'Password for encrypted Grid3 archives (.gridsetx)')
|
|
198
|
+
.action(async (file, options) => {
|
|
199
|
+
try {
|
|
200
|
+
// Auto-detect format if not specified
|
|
201
|
+
const format = options.format || detectFormat(file);
|
|
202
|
+
// Get processor with gridset password if provided
|
|
203
|
+
const processorOptions = {};
|
|
204
|
+
if (options.gridsetPassword) {
|
|
205
|
+
processorOptions.gridsetPassword = options.gridsetPassword;
|
|
206
|
+
}
|
|
207
|
+
const processor = (0, analyze_1.getProcessor)(format, processorOptions);
|
|
208
|
+
// Check if processor supports validation
|
|
209
|
+
if (!processor.validate) {
|
|
210
|
+
console.error(`Error: Validation not supported for format '${format}'`);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
// Run validation
|
|
214
|
+
const result = await processor.validate(file);
|
|
215
|
+
// Output results
|
|
216
|
+
if (options.quiet) {
|
|
217
|
+
console.log(result.valid ? 'valid' : 'invalid');
|
|
218
|
+
}
|
|
219
|
+
else if (options.json) {
|
|
220
|
+
console.log(JSON.stringify(result, null, 2));
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
// Pretty print validation results
|
|
224
|
+
console.log(`\nValidation Results for: ${result.filename}`);
|
|
225
|
+
console.log(`Format: ${result.format}`);
|
|
226
|
+
console.log(`File size: ${result.filesize} bytes`);
|
|
227
|
+
console.log(`Status: ${result.valid ? 'โ VALID' : 'โ INVALID'}`);
|
|
228
|
+
console.log(`Errors: ${result.errors}`);
|
|
229
|
+
console.log(`Warnings: ${result.warnings}\n`);
|
|
230
|
+
if (result.errors > 0 || result.warnings > 0) {
|
|
231
|
+
if (result.errors > 0) {
|
|
232
|
+
console.log('Errors:');
|
|
233
|
+
result.results
|
|
234
|
+
.filter((r) => !r.valid)
|
|
235
|
+
.forEach((check) => {
|
|
236
|
+
console.log(` โ ${check.description}`);
|
|
237
|
+
if (check.error) {
|
|
238
|
+
console.log(` ${check.error}`);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (result.warnings > 0) {
|
|
243
|
+
console.log('\nWarnings:');
|
|
244
|
+
result.results.forEach((check) => {
|
|
245
|
+
if (check.warnings && check.warnings.length > 0) {
|
|
246
|
+
console.log(` โ ${check.description}`);
|
|
247
|
+
check.warnings.forEach((warning) => {
|
|
248
|
+
console.log(` ${warning}`);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Show sub-results if available
|
|
255
|
+
if (result.sub_results && result.sub_results.length > 0) {
|
|
256
|
+
console.log('\nSub-results:');
|
|
257
|
+
result.sub_results.forEach((sub, idx) => {
|
|
258
|
+
console.log(` [${idx + 1}] ${sub.filename}`);
|
|
259
|
+
console.log(` Status: ${sub.valid ? 'โ' : 'โ'} (${sub.errors} errors, ${sub.warnings} warnings)`);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
console.log('');
|
|
263
|
+
}
|
|
264
|
+
// Exit with appropriate code
|
|
265
|
+
process.exit(result.valid ? 0 : 1);
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.error('Error validating file:', error instanceof Error ? error.message : String(error));
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
185
272
|
// Show help if no command provided
|
|
186
273
|
if (process.argv.length <= 2) {
|
|
187
274
|
commander_1.program.help();
|
package/dist/core/analyze.js
CHANGED
|
@@ -27,6 +27,7 @@ function getProcessor(format, options) {
|
|
|
27
27
|
case 'ce': // TouchChat file extension
|
|
28
28
|
return new touchchatProcessor_1.TouchChatProcessor(options);
|
|
29
29
|
case 'gridset':
|
|
30
|
+
case 'gridsetx':
|
|
30
31
|
return new gridsetProcessor_1.GridsetProcessor(options); // Grid3 format
|
|
31
32
|
case 'grd': // Asterics Grid file extension
|
|
32
33
|
return new astericsGridProcessor_1.AstericsGridProcessor(options);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { AACTree, AACButton } from './treeStructure';
|
|
2
2
|
import { StringCasing } from './stringCasing';
|
|
3
|
+
import { ValidationResult } from '../validation/validationTypes';
|
|
3
4
|
export interface ProcessorOptions {
|
|
4
5
|
excludeNavigationButtons?: boolean;
|
|
5
6
|
excludeSystemButtons?: boolean;
|
|
7
|
+
gridsetPassword?: string;
|
|
6
8
|
customButtonFilter?: (button: AACButton) => boolean;
|
|
7
9
|
preserveAllButtons?: boolean;
|
|
8
10
|
}
|
|
@@ -44,6 +46,7 @@ declare abstract class BaseProcessor {
|
|
|
44
46
|
abstract loadIntoTree(filePathOrBuffer: string | Buffer): AACTree;
|
|
45
47
|
abstract processTexts(filePathOrBuffer: string | Buffer, translations: Map<string, string>, outputPath: string): Buffer;
|
|
46
48
|
abstract saveFromTree(tree: AACTree, outputPath: string): void | Promise<void>;
|
|
49
|
+
validate?(filePath: string): Promise<ValidationResult>;
|
|
47
50
|
/**
|
|
48
51
|
* Extract strings with metadata for external platform integration
|
|
49
52
|
* @param filePath - Path to the AAC file
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './core/baseProcessor';
|
|
|
3
3
|
export * from './core/stringCasing';
|
|
4
4
|
export * from './processors';
|
|
5
5
|
export { collectUnifiedHistory, listGrid3Users as listHistoryGrid3Users, listSnapUsers as listHistorySnapUsers, } from './analytics/history';
|
|
6
|
+
export * from './validation';
|
|
6
7
|
import { BaseProcessor } from './core/baseProcessor';
|
|
7
8
|
/**
|
|
8
9
|
* Factory function to get the appropriate processor for a file extension
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ var history_1 = require("./analytics/history");
|
|
|
27
27
|
Object.defineProperty(exports, "collectUnifiedHistory", { enumerable: true, get: function () { return history_1.collectUnifiedHistory; } });
|
|
28
28
|
Object.defineProperty(exports, "listHistoryGrid3Users", { enumerable: true, get: function () { return history_1.listGrid3Users; } });
|
|
29
29
|
Object.defineProperty(exports, "listHistorySnapUsers", { enumerable: true, get: function () { return history_1.listSnapUsers; } });
|
|
30
|
+
__exportStar(require("./validation"), exports);
|
|
30
31
|
const dotProcessor_1 = require("./processors/dotProcessor");
|
|
31
32
|
const excelProcessor_1 = require("./processors/excelProcessor");
|
|
32
33
|
const opmlProcessor_1 = require("./processors/opmlProcessor");
|
|
@@ -58,6 +59,7 @@ function getProcessor(filePathOrExtension) {
|
|
|
58
59
|
case '.obz':
|
|
59
60
|
return new obfProcessor_1.ObfProcessor();
|
|
60
61
|
case '.gridset':
|
|
62
|
+
case '.gridsetx':
|
|
61
63
|
return new gridsetProcessor_1.GridsetProcessor();
|
|
62
64
|
case '.spb':
|
|
63
65
|
case '.sps':
|
|
@@ -84,6 +86,7 @@ function getSupportedExtensions() {
|
|
|
84
86
|
'.obf',
|
|
85
87
|
'.obz',
|
|
86
88
|
'.gridset',
|
|
89
|
+
'.gridsetx',
|
|
87
90
|
'.spb',
|
|
88
91
|
'.sps',
|
|
89
92
|
'.ce',
|
|
@@ -7,6 +7,7 @@ exports.TouchChatSymbolResolver = exports.TouchChatSymbolExtractor = exports.Gri
|
|
|
7
7
|
exports.resolveSymbol = resolveSymbol;
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const password_1 = require("../processors/gridset/password");
|
|
10
11
|
// --- Base Classes ---
|
|
11
12
|
class SymbolExtractor {
|
|
12
13
|
}
|
|
@@ -76,8 +77,9 @@ class Grid3SymbolExtractor extends SymbolExtractor {
|
|
|
76
77
|
const zip = new AdmZip(filePath);
|
|
77
78
|
const parser = new XMLParser();
|
|
78
79
|
const refs = new Set();
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
const entries = (0, password_1.getZipEntriesWithPassword)(zip, (0, password_1.resolveGridsetPasswordFromEnv)());
|
|
81
|
+
entries.forEach((entry) => {
|
|
82
|
+
if (entry.entryName.endsWith('.gridset') || entry.entryName.endsWith('.gridsetx')) {
|
|
81
83
|
const xmlBuffer = entry.getData();
|
|
82
84
|
// Parse to validate XML structure (future: extract refs)
|
|
83
85
|
parser.parse(xmlBuffer.toString('utf8'));
|
|
@@ -15,7 +15,7 @@ export declare function getAllowedImageEntries(tree: AACTree): Set<string>;
|
|
|
15
15
|
* @param entryPath Entry name inside the zip
|
|
16
16
|
* @returns Image data buffer or null if not found
|
|
17
17
|
*/
|
|
18
|
-
export declare function openImage(gridsetBuffer: Buffer, entryPath: string): Buffer | null;
|
|
18
|
+
export declare function openImage(gridsetBuffer: Buffer, entryPath: string, password?: string | undefined): Buffer | null;
|
|
19
19
|
/**
|
|
20
20
|
* Generate a random GUID for Grid3 elements
|
|
21
21
|
* Grid3 uses GUIDs for grid identification
|
|
@@ -49,6 +49,7 @@ const path = __importStar(require("path"));
|
|
|
49
49
|
const child_process_1 = require("child_process");
|
|
50
50
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
51
51
|
const dotnetTicks_1 = require("../../utils/dotnetTicks");
|
|
52
|
+
const password_1 = require("./password");
|
|
52
53
|
function normalizeZipPath(p) {
|
|
53
54
|
const unified = p.replace(/\\/g, '/');
|
|
54
55
|
try {
|
|
@@ -94,10 +95,11 @@ function getAllowedImageEntries(tree) {
|
|
|
94
95
|
* @param entryPath Entry name inside the zip
|
|
95
96
|
* @returns Image data buffer or null if not found
|
|
96
97
|
*/
|
|
97
|
-
function openImage(gridsetBuffer, entryPath) {
|
|
98
|
+
function openImage(gridsetBuffer, entryPath, password = (0, password_1.resolveGridsetPasswordFromEnv)()) {
|
|
98
99
|
const zip = new adm_zip_1.default(gridsetBuffer);
|
|
100
|
+
const entries = (0, password_1.getZipEntriesWithPassword)(zip, password);
|
|
99
101
|
const want = normalizeZipPath(entryPath);
|
|
100
|
-
const entry =
|
|
102
|
+
const entry = entries.find((e) => normalizeZipPath(e.entryName) === want);
|
|
101
103
|
if (!entry)
|
|
102
104
|
return null;
|
|
103
105
|
return entry.getData();
|
|
@@ -292,7 +294,7 @@ function findGrid3Vocabularies(userName) {
|
|
|
292
294
|
if (!entry.isFile())
|
|
293
295
|
continue;
|
|
294
296
|
const ext = path.extname(entry.name).toLowerCase();
|
|
295
|
-
if (ext === '.gridset' || ext === '.grd' || ext === '.grdl') {
|
|
297
|
+
if (ext === '.gridset' || ext === '.gridsetx' || ext === '.grd' || ext === '.grdl') {
|
|
296
298
|
results.push({
|
|
297
299
|
userName: userDir.name,
|
|
298
300
|
gridsetPath: path.win32.join(gridSetsDir, entry.name),
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ProcessorOptions } from '../../core/baseProcessor';
|
|
2
|
+
import AdmZip from 'adm-zip';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the password to use for Grid3 archives.
|
|
5
|
+
* Preference order:
|
|
6
|
+
* 1. Explicit processor option
|
|
7
|
+
* 2. GRIDSET_PASSWORD env var
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveGridsetPassword(options?: ProcessorOptions, source?: string | Buffer): string | undefined;
|
|
10
|
+
export declare function resolveGridsetPasswordFromEnv(): string | undefined;
|
|
11
|
+
export declare function getZipEntriesWithPassword(zip: AdmZip, password?: string): AdmZip.IZipEntry[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveGridsetPassword = resolveGridsetPassword;
|
|
7
|
+
exports.resolveGridsetPasswordFromEnv = resolveGridsetPasswordFromEnv;
|
|
8
|
+
exports.getZipEntriesWithPassword = getZipEntriesWithPassword;
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the password to use for Grid3 archives.
|
|
12
|
+
* Preference order:
|
|
13
|
+
* 1. Explicit processor option
|
|
14
|
+
* 2. GRIDSET_PASSWORD env var
|
|
15
|
+
*/
|
|
16
|
+
function resolveGridsetPassword(options, source) {
|
|
17
|
+
if (options?.gridsetPassword)
|
|
18
|
+
return options.gridsetPassword;
|
|
19
|
+
if (process.env.GRIDSET_PASSWORD)
|
|
20
|
+
return process.env.GRIDSET_PASSWORD;
|
|
21
|
+
if (typeof source === 'string') {
|
|
22
|
+
const ext = path_1.default.extname(source).toLowerCase();
|
|
23
|
+
if (ext === '.gridsetx')
|
|
24
|
+
return process.env.GRIDSET_PASSWORD;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
function resolveGridsetPasswordFromEnv() {
|
|
29
|
+
return process.env.GRIDSET_PASSWORD;
|
|
30
|
+
}
|
|
31
|
+
// Wrapper to set the password before reading entries (typed getEntries lacks the optional arg)
|
|
32
|
+
function getZipEntriesWithPassword(zip, password) {
|
|
33
|
+
if (password) {
|
|
34
|
+
return zip.getEntries(password);
|
|
35
|
+
}
|
|
36
|
+
return zip.getEntries();
|
|
37
|
+
}
|
|
@@ -10,9 +10,13 @@ function normalizeZipPathLocal(p) {
|
|
|
10
10
|
return unified;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
function listZipEntries(zip) {
|
|
13
|
+
function listZipEntries(zip, zipEntries) {
|
|
14
14
|
try {
|
|
15
|
-
const raw =
|
|
15
|
+
const raw = Array.isArray(zipEntries) && zipEntries.length > 0
|
|
16
|
+
? zipEntries
|
|
17
|
+
: typeof zip?.getEntries === 'function'
|
|
18
|
+
? zip.getEntries()
|
|
19
|
+
: [];
|
|
16
20
|
let entries = [];
|
|
17
21
|
if (Array.isArray(raw))
|
|
18
22
|
entries = raw;
|
|
@@ -33,12 +37,12 @@ function joinBaseDir(baseDir, leaf) {
|
|
|
33
37
|
const base = normalizeZipPathLocal(baseDir).replace(/\/?$/, '/');
|
|
34
38
|
return normalizeZipPathLocal(base + leaf.replace(/^\//, ''));
|
|
35
39
|
}
|
|
36
|
-
function resolveGrid3CellImage(zip, args) {
|
|
40
|
+
function resolveGrid3CellImage(zip, args, zipEntries) {
|
|
37
41
|
const { baseDir, dynamicFiles } = args;
|
|
38
42
|
const imageName = args.imageName?.trim();
|
|
39
43
|
const x = args.x;
|
|
40
44
|
const y = args.y;
|
|
41
|
-
const entries = new Set(listZipEntries(zip));
|
|
45
|
+
const entries = new Set(listZipEntries(zip, zipEntries));
|
|
42
46
|
const has = (p) => entries.has(normalizeZipPathLocal(p));
|
|
43
47
|
// Built-in resource like [grid3x]...
|
|
44
48
|
if (imageName && imageName.startsWith('[')) {
|
|
@@ -64,7 +64,7 @@ export declare function wordlistToXml(wordlist: WordList): string;
|
|
|
64
64
|
* console.log(`Grid "${gridName}" has ${wordlist.items.length} items`);
|
|
65
65
|
* });
|
|
66
66
|
*/
|
|
67
|
-
export declare function extractWordlists(gridsetBuffer: Buffer): Map<string, WordList>;
|
|
67
|
+
export declare function extractWordlists(gridsetBuffer: Buffer, password?: string | undefined): Map<string, WordList>;
|
|
68
68
|
/**
|
|
69
69
|
* Updates or adds a wordlist to a specific grid in a gridset
|
|
70
70
|
*
|
|
@@ -79,4 +79,4 @@ export declare function extractWordlists(gridsetBuffer: Buffer): Map<string, Wor
|
|
|
79
79
|
* const updatedGridset = updateWordlist(gridsetBuffer, 'Greetings', newWordlist);
|
|
80
80
|
* fs.writeFileSync('updated-gridset.gridset', updatedGridset);
|
|
81
81
|
*/
|
|
82
|
-
export declare function updateWordlist(gridsetBuffer: Buffer, gridName: string, wordlist: WordList): Buffer;
|
|
82
|
+
export declare function updateWordlist(gridsetBuffer: Buffer, gridName: string, wordlist: WordList, password?: string | undefined): Buffer;
|
|
@@ -19,6 +19,7 @@ exports.extractWordlists = extractWordlists;
|
|
|
19
19
|
exports.updateWordlist = updateWordlist;
|
|
20
20
|
const adm_zip_1 = __importDefault(require("adm-zip"));
|
|
21
21
|
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
22
|
+
const password_1 = require("./password");
|
|
22
23
|
/**
|
|
23
24
|
* Creates a WordList object from an array of words/phrases or a dictionary
|
|
24
25
|
*
|
|
@@ -104,7 +105,7 @@ function wordlistToXml(wordlist) {
|
|
|
104
105
|
* console.log(`Grid "${gridName}" has ${wordlist.items.length} items`);
|
|
105
106
|
* });
|
|
106
107
|
*/
|
|
107
|
-
function extractWordlists(gridsetBuffer) {
|
|
108
|
+
function extractWordlists(gridsetBuffer, password = (0, password_1.resolveGridsetPasswordFromEnv)()) {
|
|
108
109
|
const wordlists = new Map();
|
|
109
110
|
const parser = new fast_xml_parser_1.XMLParser();
|
|
110
111
|
let zip;
|
|
@@ -114,8 +115,9 @@ function extractWordlists(gridsetBuffer) {
|
|
|
114
115
|
catch (error) {
|
|
115
116
|
throw new Error(`Invalid gridset buffer: ${error.message}`);
|
|
116
117
|
}
|
|
118
|
+
const entries = (0, password_1.getZipEntriesWithPassword)(zip, password);
|
|
117
119
|
// Process each grid file
|
|
118
|
-
|
|
120
|
+
entries.forEach((entry) => {
|
|
119
121
|
if (entry.entryName.startsWith('Grids/') && entry.entryName.endsWith('grid.xml')) {
|
|
120
122
|
try {
|
|
121
123
|
const xmlContent = entry.getData().toString('utf8');
|
|
@@ -169,7 +171,7 @@ function extractWordlists(gridsetBuffer) {
|
|
|
169
171
|
* const updatedGridset = updateWordlist(gridsetBuffer, 'Greetings', newWordlist);
|
|
170
172
|
* fs.writeFileSync('updated-gridset.gridset', updatedGridset);
|
|
171
173
|
*/
|
|
172
|
-
function updateWordlist(gridsetBuffer, gridName, wordlist) {
|
|
174
|
+
function updateWordlist(gridsetBuffer, gridName, wordlist, password = (0, password_1.resolveGridsetPasswordFromEnv)()) {
|
|
173
175
|
const parser = new fast_xml_parser_1.XMLParser();
|
|
174
176
|
const builder = new fast_xml_parser_1.XMLBuilder({
|
|
175
177
|
ignoreAttributes: false,
|
|
@@ -184,9 +186,10 @@ function updateWordlist(gridsetBuffer, gridName, wordlist) {
|
|
|
184
186
|
catch (error) {
|
|
185
187
|
throw new Error(`Invalid gridset buffer: ${error.message}`);
|
|
186
188
|
}
|
|
189
|
+
const entries = (0, password_1.getZipEntriesWithPassword)(zip, password);
|
|
187
190
|
let found = false;
|
|
188
191
|
// Find and update the grid
|
|
189
|
-
|
|
192
|
+
entries.forEach((entry) => {
|
|
190
193
|
if (entry.entryName.startsWith('Grids/') && entry.entryName.endsWith('grid.xml')) {
|
|
191
194
|
const match = entry.entryName.match(/^Grids\/([^/]+)\//);
|
|
192
195
|
const currentGridName = match ? match[1] : null;
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { BaseProcessor, ProcessorOptions, ExtractStringsResult, TranslatedString, SourceString } from '../core/baseProcessor';
|
|
2
2
|
import { AACTree } from '../core/treeStructure';
|
|
3
|
+
import { ValidationResult } from '../validation/validationTypes';
|
|
3
4
|
declare class GridsetProcessor extends BaseProcessor {
|
|
4
5
|
constructor(options?: ProcessorOptions);
|
|
6
|
+
/**
|
|
7
|
+
* Decrypt and inflate a Grid3 encrypted payload (DesktopContentEncrypter).
|
|
8
|
+
* Uses AES-256-CBC with key/IV derived from the password padded with spaces
|
|
9
|
+
* and then Deflate decompression.
|
|
10
|
+
*/
|
|
11
|
+
private decryptGridsetEntry;
|
|
12
|
+
private getGridsetPassword;
|
|
5
13
|
private ensureAlphaChannel;
|
|
6
14
|
private generateCommandsFromSemanticAction;
|
|
7
15
|
private convertGrid3StyleToAACStyle;
|
|
8
16
|
private getStyleById;
|
|
9
17
|
private textOf;
|
|
10
18
|
extractTexts(filePathOrBuffer: string | Buffer): string[];
|
|
11
|
-
loadIntoTree(filePathOrBuffer: Buffer): AACTree;
|
|
19
|
+
loadIntoTree(filePathOrBuffer: string | Buffer): AACTree;
|
|
12
20
|
processTexts(filePathOrBuffer: string | Buffer, translations: Map<string, string>, outputPath: string): Buffer;
|
|
13
21
|
saveFromTree(tree: AACTree, outputPath: string): void;
|
|
14
22
|
private calculateColumnDefinitions;
|
|
@@ -24,5 +32,11 @@ declare class GridsetProcessor extends BaseProcessor {
|
|
|
24
32
|
* Uses the generic implementation from BaseProcessor
|
|
25
33
|
*/
|
|
26
34
|
generateTranslatedDownload(filePath: string, translatedStrings: TranslatedString[], sourceStrings: SourceString[]): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* Validate Gridset file format
|
|
37
|
+
* @param filePath - Path to the file to validate
|
|
38
|
+
* @returns Promise with validation result
|
|
39
|
+
*/
|
|
40
|
+
validate(filePath: string): Promise<ValidationResult>;
|
|
27
41
|
}
|
|
28
42
|
export { GridsetProcessor };
|