@willwade/aac-processors 0.2.8 → 0.2.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/dist/browser/processors/gridsetProcessor.js +243 -77
- package/dist/browser/processors/obfProcessor.js +106 -49
- package/dist/browser/utilities/analytics/morphology/index.js +0 -1
- package/dist/processors/gridsetProcessor.d.ts +5 -0
- package/dist/processors/gridsetProcessor.js +243 -77
- package/dist/processors/obfProcessor.d.ts +2 -1
- package/dist/processors/obfProcessor.js +106 -49
- package/dist/utilities/analytics/morphology/index.d.ts +0 -1
- package/dist/utilities/analytics/morphology/index.js +1 -3
- package/package.json +1 -1
|
@@ -106,7 +106,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
106
106
|
// Images are typically stored in an 'images' folder or root
|
|
107
107
|
const possiblePaths = [
|
|
108
108
|
imageData.path, // Explicit path if provided
|
|
109
|
-
`images/${imageData.
|
|
109
|
+
`images/${imageData.path || imageId}`, // Standard images folder
|
|
110
110
|
imageData.id, // Just the ID
|
|
111
111
|
].filter(Boolean);
|
|
112
112
|
for (const imagePath of possiblePaths) {
|
|
@@ -152,14 +152,18 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
152
152
|
return 'image/png';
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
|
|
155
|
+
getPageFilename(id, metadata) {
|
|
156
|
+
if (metadata._obfPagePaths && id in metadata._obfPagePaths)
|
|
157
|
+
return metadata._obfPagePaths[id];
|
|
158
|
+
if (id.endsWith('.obf'))
|
|
159
|
+
return id;
|
|
160
|
+
return `${id}.obf`;
|
|
161
|
+
}
|
|
162
|
+
async processBoard(boardData, _boardPath) {
|
|
156
163
|
const sourceButtons = boardData.buttons || [];
|
|
157
164
|
// Calculate page ID first (used to make button IDs unique)
|
|
158
|
-
const pageId =
|
|
159
|
-
|
|
160
|
-
: boardData?.id
|
|
161
|
-
? String(boardData.id)
|
|
162
|
-
: _boardPath?.split(/[/\\]/).pop() || '';
|
|
165
|
+
const pageId = boardData?.id ? String(boardData.id) : _boardPath?.split(/[/\\]/).pop() || '';
|
|
166
|
+
const images = boardData.images;
|
|
163
167
|
const buttons = await Promise.all(sourceButtons.map(async (btn) => {
|
|
164
168
|
const semanticAction = btn.load_board
|
|
165
169
|
? {
|
|
@@ -183,11 +187,16 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
183
187
|
// Resolve image if image_id is present
|
|
184
188
|
let resolvedImage;
|
|
185
189
|
let imageBuffer;
|
|
186
|
-
if (btn.image_id &&
|
|
187
|
-
resolvedImage =
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
190
|
+
if (btn.image_id && images) {
|
|
191
|
+
resolvedImage = (await this.extractImageAsDataUrl(btn.image_id, images)) || undefined;
|
|
192
|
+
imageBuffer = (await this.extractImageAsBuffer(btn.image_id, images)) || undefined;
|
|
193
|
+
// save image data
|
|
194
|
+
if (images) {
|
|
195
|
+
const imageIndex = images?.findIndex((img) => img.id === btn.image_id);
|
|
196
|
+
if (imageIndex !== -1) {
|
|
197
|
+
images[imageIndex].data = resolvedImage;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
191
200
|
}
|
|
192
201
|
// Build parameters object for Grid3 export compatibility
|
|
193
202
|
const buttonParameters = {};
|
|
@@ -224,7 +233,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
224
233
|
parentId: null,
|
|
225
234
|
locale: boardData.locale,
|
|
226
235
|
descriptionHtml: boardData.description_html,
|
|
227
|
-
images
|
|
236
|
+
images,
|
|
228
237
|
sounds: boardData.sounds,
|
|
229
238
|
});
|
|
230
239
|
// Process grid layout if available
|
|
@@ -314,7 +323,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
314
323
|
return texts;
|
|
315
324
|
}
|
|
316
325
|
async loadIntoTree(filePathOrBuffer) {
|
|
317
|
-
const { readBinaryFromInput, readTextFromInput } = this.options.fileAdapter;
|
|
326
|
+
const { readBinaryFromInput, readTextFromInput, listDir, join, isDirectory } = this.options.fileAdapter;
|
|
318
327
|
// Detailed logging for debugging input
|
|
319
328
|
const bufferLength = typeof filePathOrBuffer === 'string'
|
|
320
329
|
? null
|
|
@@ -356,7 +365,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
356
365
|
const boardData = await tryParseObfJson(content);
|
|
357
366
|
if (boardData) {
|
|
358
367
|
console.log('[OBF] Detected .obf file, parsed as JSON');
|
|
359
|
-
const page = await this.processBoard(boardData, filePathOrBuffer
|
|
368
|
+
const page = await this.processBoard(boardData, filePathOrBuffer);
|
|
360
369
|
tree.addPage(page);
|
|
361
370
|
// Set metadata from root board
|
|
362
371
|
tree.metadata.format = 'obf';
|
|
@@ -380,22 +389,30 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
380
389
|
throw err;
|
|
381
390
|
}
|
|
382
391
|
}
|
|
383
|
-
//
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
392
|
+
// Determine if input is ZIP, directory, or OBF JSON string/buffer
|
|
393
|
+
let fileType = 'obf';
|
|
394
|
+
if (typeof filePathOrBuffer !== 'string') {
|
|
395
|
+
const bytes = await readBinaryFromInput(filePathOrBuffer);
|
|
396
|
+
if (bytes.length >= 2 && bytes[0] === 0x50 && bytes[1] === 0x4b)
|
|
397
|
+
fileType = 'zip';
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
if (await isDirectory(filePathOrBuffer)) {
|
|
401
|
+
fileType = 'dir';
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
const lowered = filePathOrBuffer.toLowerCase();
|
|
405
|
+
if (lowered.endsWith('.zip') || lowered.endsWith('.obz'))
|
|
406
|
+
fileType = 'zip';
|
|
388
407
|
}
|
|
389
|
-
const bytes = await readBinaryFromInput(input);
|
|
390
|
-
return bytes.length >= 2 && bytes[0] === 0x50 && bytes[1] === 0x4b;
|
|
391
408
|
}
|
|
392
409
|
// Check if input is a buffer or string that parses as OBF JSON; throw if neither JSON nor ZIP
|
|
393
|
-
if (
|
|
410
|
+
if (fileType === 'obf') {
|
|
394
411
|
const asJson = await tryParseObfJson(filePathOrBuffer);
|
|
395
412
|
if (!asJson)
|
|
396
413
|
throw new Error('Invalid OBF content: not JSON and not ZIP');
|
|
397
414
|
console.log('[OBF] Detected buffer/string as OBF JSON');
|
|
398
|
-
const page = await this.processBoard(asJson, '[bufferOrString]'
|
|
415
|
+
const page = await this.processBoard(asJson, '[bufferOrString]');
|
|
399
416
|
tree.addPage(page);
|
|
400
417
|
// Set metadata from root board
|
|
401
418
|
tree.metadata.format = 'obf';
|
|
@@ -411,18 +428,31 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
411
428
|
tree.rootId = page.id;
|
|
412
429
|
return tree;
|
|
413
430
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
431
|
+
this.zipFile = {
|
|
432
|
+
readFile: async (name) => {
|
|
433
|
+
return await readBinaryFromInput(join(filePathOrBuffer, name));
|
|
434
|
+
},
|
|
435
|
+
listFiles: () => {
|
|
436
|
+
throw new Error('Not implemented for directory input');
|
|
437
|
+
},
|
|
438
|
+
writeFiles: () => {
|
|
439
|
+
throw new Error('Not implemented for directory input');
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
if (fileType === 'zip') {
|
|
443
|
+
try {
|
|
444
|
+
this.zipFile = await this.options.zipAdapter(filePathOrBuffer);
|
|
445
|
+
}
|
|
446
|
+
catch (err) {
|
|
447
|
+
console.error('[OBF] Error loading ZIP:', err);
|
|
448
|
+
throw err;
|
|
449
|
+
}
|
|
420
450
|
}
|
|
421
451
|
// Store the ZIP file reference for image extraction
|
|
422
452
|
this.imageCache.clear(); // Clear cache for new file
|
|
423
|
-
console.log('[OBF] Detected zip archive, extracting .obf files');
|
|
453
|
+
console.log('[OBF] Detected zip archive or directory, extracting .obf files');
|
|
424
454
|
// List manifest and OBF files
|
|
425
|
-
const filesInZip = this.zipFile.listFiles();
|
|
455
|
+
const filesInZip = fileType === 'zip' ? this.zipFile.listFiles() : await listDir(filePathOrBuffer);
|
|
426
456
|
const manifestFile = filesInZip.filter((name) => name.toLowerCase() === 'manifest.json');
|
|
427
457
|
let obfEntries = filesInZip.filter((name) => name.toLowerCase().endsWith('.obf'));
|
|
428
458
|
// Attempt to read manifest
|
|
@@ -456,7 +486,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
456
486
|
const content = await this.zipFile.readFile(entryName);
|
|
457
487
|
const boardData = await tryParseObfJson((0, io_1.decodeText)(content));
|
|
458
488
|
if (boardData) {
|
|
459
|
-
const page = await this.processBoard(boardData, entryName
|
|
489
|
+
const page = await this.processBoard(boardData, entryName);
|
|
460
490
|
tree.addPage(page);
|
|
461
491
|
// Set metadata if not already set (use first board as reference)
|
|
462
492
|
if (!tree.metadata.format) {
|
|
@@ -465,12 +495,16 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
465
495
|
tree.metadata.description = boardData.description_html;
|
|
466
496
|
tree.metadata.locale = boardData.locale;
|
|
467
497
|
tree.metadata.id = boardData.id;
|
|
498
|
+
tree.metadata._obfPagePaths = { [page.id]: entryName };
|
|
468
499
|
if (boardData.url)
|
|
469
500
|
tree.metadata.url = boardData.url;
|
|
470
501
|
if (boardData.locale)
|
|
471
502
|
tree.metadata.languages = [boardData.locale];
|
|
472
503
|
tree.rootId = page.id;
|
|
473
504
|
}
|
|
505
|
+
else {
|
|
506
|
+
tree.metadata._obfPagePaths[page.id] = entryName;
|
|
507
|
+
}
|
|
474
508
|
}
|
|
475
509
|
else {
|
|
476
510
|
console.warn('[OBF] Skipped entry (not valid OBF JSON):', entryName);
|
|
@@ -523,11 +557,18 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
523
557
|
}
|
|
524
558
|
return { rows: totalRows, columns: totalColumns, order, buttonPositions };
|
|
525
559
|
}
|
|
526
|
-
createObfBoardFromPage(page, fallbackName, metadata) {
|
|
560
|
+
createObfBoardFromPage(page, fallbackName, metadata, embedData = false) {
|
|
527
561
|
const { rows, columns, order, buttonPositions } = this.buildGridMetadata(page);
|
|
528
562
|
const boardName = metadata?.name && page.id === metadata?.defaultHomePageId
|
|
529
563
|
? metadata.name
|
|
530
564
|
: page.name || fallbackName;
|
|
565
|
+
let images = Array.isArray(page.images) ? page.images : [];
|
|
566
|
+
if (!embedData) {
|
|
567
|
+
images = images.map((image) => {
|
|
568
|
+
delete image.data;
|
|
569
|
+
return image;
|
|
570
|
+
});
|
|
571
|
+
}
|
|
531
572
|
return {
|
|
532
573
|
format: OBF_FORMAT_VERSION,
|
|
533
574
|
id: page.id,
|
|
@@ -564,7 +605,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
564
605
|
hidden: button.visibility === 'Hidden' || false,
|
|
565
606
|
};
|
|
566
607
|
}),
|
|
567
|
-
images
|
|
608
|
+
images,
|
|
568
609
|
sounds: Array.isArray(page.sounds) ? page.sounds : [],
|
|
569
610
|
};
|
|
570
611
|
}
|
|
@@ -601,23 +642,22 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
601
642
|
await this.saveFromTree(tree, outputPath);
|
|
602
643
|
return await readBinaryFromInput(outputPath);
|
|
603
644
|
}
|
|
604
|
-
async saveFromTree(tree, outputPath) {
|
|
605
|
-
const { writeTextToPath, writeBinaryToPath, pathExists } = this.options.fileAdapter;
|
|
645
|
+
async saveFromTree(tree, outputPath, embedData = false) {
|
|
646
|
+
const { writeTextToPath, writeBinaryToPath, pathExists, mkDir, join } = this.options.fileAdapter;
|
|
606
647
|
if (outputPath.endsWith('.obf')) {
|
|
607
648
|
// Save as single OBF JSON file
|
|
608
649
|
const rootPage = tree.rootId ? tree.getPage(tree.rootId) : Object.values(tree.pages)[0];
|
|
609
650
|
if (!rootPage) {
|
|
610
651
|
throw new Error('No pages to save');
|
|
611
652
|
}
|
|
612
|
-
const obfBoard = this.createObfBoardFromPage(rootPage, 'Exported Board', tree.metadata);
|
|
653
|
+
const obfBoard = this.createObfBoardFromPage(rootPage, 'Exported Board', tree.metadata, embedData);
|
|
613
654
|
await writeTextToPath(outputPath, JSON.stringify(obfBoard, null, 2));
|
|
614
655
|
}
|
|
615
656
|
else {
|
|
616
|
-
const getPageFilename = (id) => (id.endsWith('.obf') ? id : `${id}.obf`);
|
|
617
657
|
const files = Object.values(tree.pages).map((page) => {
|
|
618
|
-
const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
|
|
658
|
+
const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata, embedData);
|
|
619
659
|
const obfContent = JSON.stringify(obfBoard, null, 2);
|
|
620
|
-
const name = getPageFilename(page.id);
|
|
660
|
+
const name = this.getPageFilename(page.id, tree.metadata);
|
|
621
661
|
return {
|
|
622
662
|
name,
|
|
623
663
|
data: new TextEncoder().encode(obfContent),
|
|
@@ -627,7 +667,10 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
627
667
|
format: OBF_FORMAT_VERSION,
|
|
628
668
|
root: tree.metadata.defaultHomePageId,
|
|
629
669
|
paths: {
|
|
630
|
-
boards: Object.fromEntries(Object.entries(tree.pages).map(([id, page]) => [
|
|
670
|
+
boards: Object.fromEntries(Object.entries(tree.pages).map(([id, page]) => [
|
|
671
|
+
id,
|
|
672
|
+
this.getPageFilename(page.id, tree.metadata),
|
|
673
|
+
])),
|
|
631
674
|
images: {}, //TODO Add support for saving images as files
|
|
632
675
|
sounds: {}, //TODO Add support for saving sounds as files
|
|
633
676
|
},
|
|
@@ -636,10 +679,22 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
636
679
|
name: 'manifest.json',
|
|
637
680
|
data: new TextEncoder().encode(JSON.stringify(manifest)),
|
|
638
681
|
});
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
682
|
+
if (outputPath.endsWith('.obz') || outputPath.endsWith('.zip')) {
|
|
683
|
+
console.log('[OBF] Saving to ZIP file:', outputPath);
|
|
684
|
+
const fileExists = await pathExists(outputPath);
|
|
685
|
+
this.zipFile = await this.options.zipAdapter(fileExists ? outputPath : undefined, this.options.fileAdapter);
|
|
686
|
+
const zipData = await this.zipFile.writeFiles(files);
|
|
687
|
+
await writeBinaryToPath(outputPath, zipData);
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
console.log('[OBF] Saving to directory:', outputPath);
|
|
691
|
+
if (!(await pathExists(outputPath)))
|
|
692
|
+
await mkDir(outputPath);
|
|
693
|
+
for (const file of files) {
|
|
694
|
+
const filePath = join(outputPath, file.name);
|
|
695
|
+
await writeBinaryToPath(filePath, file.data);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
643
698
|
}
|
|
644
699
|
}
|
|
645
700
|
/**
|
|
@@ -666,13 +721,12 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
666
721
|
const AdmZip = (await Promise.resolve().then(() => __importStar(require('adm-zip')))).default;
|
|
667
722
|
const originalZip = new AdmZip(originalPath);
|
|
668
723
|
const outputZip = new AdmZip();
|
|
669
|
-
const getPageFilename = (id) => (id.endsWith('.obf') ? id : `${id}.obf`);
|
|
670
724
|
// Track which .obf files we're modifying
|
|
671
725
|
const modifiedObfFiles = new Set();
|
|
672
726
|
// Generate new .obf files for pages in the tree
|
|
673
727
|
const newObfFiles = new Map();
|
|
674
728
|
for (const page of Object.values(tree.pages)) {
|
|
675
|
-
const obfFilename = getPageFilename(page.id);
|
|
729
|
+
const obfFilename = this.getPageFilename(page.id, tree.metadata);
|
|
676
730
|
modifiedObfFiles.add(obfFilename);
|
|
677
731
|
const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
|
|
678
732
|
const obfContent = JSON.stringify(obfBoard, null, 2);
|
|
@@ -685,7 +739,10 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
685
739
|
format: OBF_FORMAT_VERSION,
|
|
686
740
|
root: tree.metadata.defaultHomePageId,
|
|
687
741
|
paths: {
|
|
688
|
-
boards: Object.fromEntries(Object.entries(tree.pages).map(([id, page]) => [
|
|
742
|
+
boards: Object.fromEntries(Object.entries(tree.pages).map(([id, page]) => [
|
|
743
|
+
id,
|
|
744
|
+
this.getPageFilename(page.id, tree.metadata),
|
|
745
|
+
])),
|
|
689
746
|
images: {},
|
|
690
747
|
sounds: {},
|
|
691
748
|
},
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { MorphologyEngine } from './engine';
|
|
2
|
-
export { Grid3VerbsParser } from './grid3VerbsParser';
|
|
3
2
|
export { WordFormGenerator } from './wordFormGenerator';
|
|
4
3
|
export type { MorphRuleSet, MorphRule, MorphWordForms, AstericsWordForm, VerbFormWithConditions, Grid3VerbFormsDetailed, } from './types';
|
|
5
4
|
export type { Grid3VerbForms } from './grid3VerbsParser';
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WordFormGenerator = exports.
|
|
3
|
+
exports.WordFormGenerator = exports.MorphologyEngine = void 0;
|
|
4
4
|
var engine_1 = require("./engine");
|
|
5
5
|
Object.defineProperty(exports, "MorphologyEngine", { enumerable: true, get: function () { return engine_1.MorphologyEngine; } });
|
|
6
|
-
var grid3VerbsParser_1 = require("./grid3VerbsParser");
|
|
7
|
-
Object.defineProperty(exports, "Grid3VerbsParser", { enumerable: true, get: function () { return grid3VerbsParser_1.Grid3VerbsParser; } });
|
|
8
6
|
var wordFormGenerator_1 = require("./wordFormGenerator");
|
|
9
7
|
Object.defineProperty(exports, "WordFormGenerator", { enumerable: true, get: function () { return wordFormGenerator_1.WordFormGenerator; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@willwade/aac-processors",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
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",
|