@willwade/aac-processors 0.1.19 → 0.1.21

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.
Files changed (94) hide show
  1. package/dist/browser/core/baseProcessor.js +4 -0
  2. package/dist/browser/processors/applePanelsProcessor.js +24 -31
  3. package/dist/browser/processors/astericsGridProcessor.js +10 -3
  4. package/dist/browser/processors/dotProcessor.js +5 -2
  5. package/dist/browser/processors/gridset/colorUtils.js +354 -0
  6. package/dist/browser/processors/gridset/helpers.js +49 -45
  7. package/dist/browser/processors/gridset/index.js +61 -0
  8. package/dist/browser/processors/gridset/styleHelpers.js +205 -0
  9. package/dist/browser/processors/gridset/symbolExtractor.js +331 -0
  10. package/dist/browser/processors/gridset/symbolSearch.js +248 -0
  11. package/dist/browser/processors/gridset/symbols.js +35 -68
  12. package/dist/browser/processors/gridsetProcessor.js +32 -41
  13. package/dist/browser/processors/obfProcessor.js +53 -45
  14. package/dist/browser/processors/opmlProcessor.js +5 -2
  15. package/dist/browser/processors/snap/helpers.js +49 -45
  16. package/dist/browser/processors/snapProcessor.js +67 -31
  17. package/dist/browser/processors/touchchatProcessor.js +54 -45
  18. package/dist/browser/utilities/analytics/reference/index.js +27 -19
  19. package/dist/browser/utils/io.js +67 -14
  20. package/dist/browser/utils/sqlite.js +6 -8
  21. package/dist/browser/utils/zip.js +45 -43
  22. package/dist/browser/validation/baseValidator.js +5 -0
  23. package/dist/browser/validation/gridsetValidator.js +12 -20
  24. package/dist/browser/validation/obfValidator.js +5 -4
  25. package/dist/browser/validation/snapValidator.js +9 -5
  26. package/dist/browser/validation/touchChatValidator.js +21 -11
  27. package/dist/cli/index.js +10 -15
  28. package/dist/core/baseProcessor.d.ts +7 -7
  29. package/dist/core/baseProcessor.js +4 -0
  30. package/dist/processors/applePanelsProcessor.js +29 -36
  31. package/dist/processors/astericsGridProcessor.js +20 -13
  32. package/dist/processors/dotProcessor.js +10 -7
  33. package/dist/processors/excelProcessor.js +9 -12
  34. package/dist/processors/gridset/helpers.d.ts +9 -11
  35. package/dist/processors/gridset/helpers.js +49 -71
  36. package/dist/processors/gridset/imageDebug.d.ts +3 -5
  37. package/dist/processors/gridset/imageDebug.js +4 -4
  38. package/dist/processors/gridset/password.d.ts +1 -1
  39. package/dist/processors/gridset/symbolExtractor.d.ts +5 -3
  40. package/dist/processors/gridset/symbolExtractor.js +15 -38
  41. package/dist/processors/gridset/symbolSearch.d.ts +3 -2
  42. package/dist/processors/gridset/symbolSearch.js +12 -34
  43. package/dist/processors/gridset/symbols.d.ts +8 -6
  44. package/dist/processors/gridset/symbols.js +34 -67
  45. package/dist/processors/gridset/wordlistHelpers.d.ts +4 -6
  46. package/dist/processors/gridset/wordlistHelpers.js +15 -74
  47. package/dist/processors/gridsetProcessor.js +36 -68
  48. package/dist/processors/obfProcessor.js +58 -73
  49. package/dist/processors/obfsetProcessor.js +2 -2
  50. package/dist/processors/opmlProcessor.js +10 -7
  51. package/dist/processors/snap/helpers.d.ts +8 -8
  52. package/dist/processors/snap/helpers.js +50 -72
  53. package/dist/processors/snapProcessor.js +66 -30
  54. package/dist/processors/touchchatProcessor.js +54 -45
  55. package/dist/utilities/analytics/index.d.ts +3 -2
  56. package/dist/utilities/analytics/index.js +8 -10
  57. package/dist/utilities/analytics/reference/index.d.ts +5 -3
  58. package/dist/utilities/analytics/reference/index.js +26 -18
  59. package/dist/utilities/symbolTools.d.ts +4 -2
  60. package/dist/utilities/symbolTools.js +16 -15
  61. package/dist/utils/io.d.ts +24 -6
  62. package/dist/utils/io.js +64 -14
  63. package/dist/utils/sqlite.d.ts +2 -0
  64. package/dist/utils/sqlite.js +6 -8
  65. package/dist/utils/zip.d.ts +7 -3
  66. package/dist/utils/zip.js +45 -43
  67. package/dist/validation/applePanelsValidator.d.ts +2 -1
  68. package/dist/validation/applePanelsValidator.js +9 -11
  69. package/dist/validation/astericsValidator.d.ts +2 -1
  70. package/dist/validation/astericsValidator.js +5 -4
  71. package/dist/validation/baseValidator.d.ts +2 -2
  72. package/dist/validation/baseValidator.js +5 -0
  73. package/dist/validation/dotValidator.d.ts +2 -1
  74. package/dist/validation/dotValidator.js +5 -4
  75. package/dist/validation/excelValidator.d.ts +2 -1
  76. package/dist/validation/excelValidator.js +5 -4
  77. package/dist/validation/gridsetValidator.d.ts +2 -1
  78. package/dist/validation/gridsetValidator.js +11 -22
  79. package/dist/validation/index.d.ts +2 -2
  80. package/dist/validation/index.js +5 -4
  81. package/dist/validation/obfValidator.d.ts +2 -1
  82. package/dist/validation/obfValidator.js +5 -4
  83. package/dist/validation/obfsetValidator.d.ts +2 -1
  84. package/dist/validation/obfsetValidator.js +5 -4
  85. package/dist/validation/opmlValidator.d.ts +2 -1
  86. package/dist/validation/opmlValidator.js +5 -4
  87. package/dist/validation/snapValidator.d.ts +2 -1
  88. package/dist/validation/snapValidator.js +9 -5
  89. package/dist/validation/touchChatValidator.d.ts +4 -6
  90. package/dist/validation/touchChatValidator.js +21 -11
  91. package/dist/validation/validationTypes.d.ts +8 -1
  92. package/package.json +1 -1
  93. package/dist/core/fileProcessor.d.ts +0 -7
  94. package/dist/core/fileProcessor.js +0 -52
@@ -12,7 +12,8 @@
12
12
  *
13
13
  * This module provides symbol resolution and metadata extraction.
14
14
  */
15
- import { getFs, getNodeRequire, getPath } from '../../utils/io';
15
+ import { defaultFileAdapter } from '../../utils/io';
16
+ import { getZipAdapter } from '../../utils/zip';
16
17
  /**
17
18
  * Default Grid 3 installation paths by platform
18
19
  */
@@ -55,38 +56,6 @@ export const SYMBOL_LIBRARIES = {
55
56
  * Default locale to use
56
57
  */
57
58
  export const DEFAULT_LOCALE = 'en-GB';
58
- function getNodeFs() {
59
- try {
60
- return getFs();
61
- }
62
- catch {
63
- throw new Error('Symbol library access is not available in this environment.');
64
- }
65
- }
66
- function getNodePath() {
67
- try {
68
- return getPath();
69
- }
70
- catch {
71
- throw new Error('Path utilities are not available in this environment.');
72
- }
73
- }
74
- let cachedAdmZip = null;
75
- function getAdmZip() {
76
- if (cachedAdmZip)
77
- return cachedAdmZip;
78
- try {
79
- const nodeRequire = getNodeRequire();
80
- // eslint-disable-next-line @typescript-eslint/no-var-requires
81
- const module = nodeRequire('adm-zip');
82
- const resolved = module.default || module;
83
- cachedAdmZip = resolved;
84
- return resolved;
85
- }
86
- catch {
87
- throw new Error('Symbol library access requires AdmZip in this environment.');
88
- }
89
- }
90
59
  /**
91
60
  * Parse a symbol reference string
92
61
  * @param reference - Symbol reference like "[widgit]/food/apple.png"
@@ -124,12 +93,12 @@ export function isSymbolReference(reference) {
124
93
  * Get the default Grid 3 installation path for the current platform
125
94
  * @returns Default Grid 3 path or empty string if not found
126
95
  */
127
- export function getDefaultGrid3Path() {
96
+ export function getDefaultGrid3Path(fileAdapter) {
97
+ const { pathExists } = fileAdapter ?? defaultFileAdapter;
128
98
  const platform = (typeof process !== 'undefined' && process.platform ? process.platform : 'unknown');
129
99
  const defaultPath = DEFAULT_GRID3_PATHS[platform] || '';
130
100
  try {
131
- const fs = getNodeFs();
132
- if (defaultPath && fs.existsSync(defaultPath)) {
101
+ if (defaultPath && pathExists(defaultPath)) {
133
102
  return defaultPath;
134
103
  }
135
104
  // Try to find Grid 3 in common locations
@@ -141,7 +110,7 @@ export function getDefaultGrid3Path() {
141
110
  '/opt/smartbox/grid3',
142
111
  ];
143
112
  for (const testPath of commonPaths) {
144
- if (fs.existsSync(testPath)) {
113
+ if (pathExists(testPath)) {
145
114
  return testPath;
146
115
  }
147
116
  }
@@ -157,9 +126,9 @@ export function getDefaultGrid3Path() {
157
126
  * @param grid3Path - Grid 3 installation path
158
127
  * @returns Path to Symbol Libraries directory (e.g., "C:\...\Grid 3\Resources\Symbols")
159
128
  */
160
- export function getSymbolLibrariesDir(grid3Path) {
161
- const path = getNodePath();
162
- return path.join(grid3Path, SYMBOLS_SUBDIR);
129
+ export function getSymbolLibrariesDir(grid3Path, fileAdapter = defaultFileAdapter) {
130
+ const { join } = fileAdapter;
131
+ return join(grid3Path, SYMBOLS_SUBDIR);
163
132
  }
164
133
  /**
165
134
  * Get the symbol search indexes directory path for a given locale
@@ -168,38 +137,37 @@ export function getSymbolLibrariesDir(grid3Path) {
168
137
  * @param locale - Locale code (e.g., 'en-GB')
169
138
  * @returns Path to symbol search indexes directory (e.g., "C:\...\Grid 3\Locale\en-GB\symbolsearch")
170
139
  */
171
- export function getSymbolSearchIndexesDir(grid3Path, locale = DEFAULT_LOCALE) {
172
- const path = getNodePath();
173
- return path.join(grid3Path, SYMBOLSEARCH_SUBDIR, locale, 'symbolsearch');
140
+ export function getSymbolSearchIndexesDir(grid3Path, locale = DEFAULT_LOCALE, fileAdapter = defaultFileAdapter) {
141
+ const { join } = fileAdapter;
142
+ return join(grid3Path, SYMBOLSEARCH_SUBDIR, locale, 'symbolsearch');
174
143
  }
175
144
  /**
176
145
  * Get all available symbol libraries in the Grid 3 installation
177
146
  * @param options - Resolution options
178
147
  * @returns Array of symbol library information
179
148
  */
180
- export function getAvailableSymbolLibraries(options = {}) {
149
+ export function getAvailableSymbolLibraries(options = {}, fileAdapter) {
150
+ const { pathExists, getFileSize, listDir, join, basename } = fileAdapter ?? defaultFileAdapter;
181
151
  const grid3Path = options.grid3Path || options.symbolDir || getDefaultGrid3Path();
182
152
  if (!grid3Path) {
183
153
  return [];
184
154
  }
185
- const symbolsDir = getSymbolLibrariesDir(grid3Path);
186
- const fs = getNodeFs();
187
- if (!fs.existsSync(symbolsDir)) {
155
+ const symbolsDir = getSymbolLibrariesDir(grid3Path, fileAdapter);
156
+ if (!pathExists(symbolsDir)) {
188
157
  return [];
189
158
  }
190
159
  const libraries = [];
191
- const files = fs.readdirSync(symbolsDir);
160
+ const files = listDir(symbolsDir);
192
161
  for (const file of files) {
193
162
  if (file.endsWith('.symbols')) {
194
- const path = getNodePath();
195
- const fullPath = path.join(symbolsDir, file);
196
- const stats = fs.statSync(fullPath);
197
- const libraryName = path.basename(file, '.symbols');
163
+ const fullPath = join(symbolsDir, file);
164
+ const size = getFileSize(fullPath);
165
+ const libraryName = basename(file, '.symbols');
198
166
  libraries.push({
199
167
  name: libraryName,
200
168
  pixFile: fullPath, // Reuse this field for the .symbols file path
201
169
  exists: true,
202
- size: stats.size,
170
+ size,
203
171
  locale: 'global', // .symbols files are not locale-specific
204
172
  });
205
173
  }
@@ -212,12 +180,13 @@ export function getAvailableSymbolLibraries(options = {}) {
212
180
  * @param options - Resolution options
213
181
  * @returns Symbol library info or undefined if not found
214
182
  */
215
- export function getSymbolLibraryInfo(libraryName, options = {}) {
183
+ export function getSymbolLibraryInfo(libraryName, options = {}, fileAdapter) {
184
+ const { pathExists, getFileSize, join } = fileAdapter ?? defaultFileAdapter;
216
185
  const grid3Path = options.grid3Path || options.symbolDir || getDefaultGrid3Path();
217
186
  if (!grid3Path) {
218
187
  return undefined;
219
188
  }
220
- const symbolsDir = getSymbolLibrariesDir(grid3Path);
189
+ const symbolsDir = getSymbolLibrariesDir(grid3Path, fileAdapter);
221
190
  const normalizedLibName = libraryName.toLowerCase();
222
191
  // Try different case variations
223
192
  const variations = [
@@ -226,16 +195,14 @@ export function getSymbolLibraryInfo(libraryName, options = {}) {
226
195
  libraryName + '.symbols',
227
196
  ];
228
197
  for (const file of variations) {
229
- const path = getNodePath();
230
- const fullPath = path.join(symbolsDir, file);
231
- const fs = getNodeFs();
232
- if (fs.existsSync(fullPath)) {
233
- const stats = fs.statSync(fullPath);
198
+ const fullPath = join(symbolsDir, file);
199
+ if (pathExists(fullPath)) {
200
+ const size = getFileSize(fullPath);
234
201
  return {
235
202
  name: libraryName,
236
203
  pixFile: fullPath,
237
204
  exists: true,
238
- size: stats.size,
205
+ size,
239
206
  locale: 'global',
240
207
  };
241
208
  }
@@ -248,7 +215,7 @@ export function getSymbolLibraryInfo(libraryName, options = {}) {
248
215
  * @param options - Resolution options
249
216
  * @returns Resolution result with image data if found
250
217
  */
251
- export function resolveSymbolReference(reference, options = {}) {
218
+ export async function resolveSymbolReference(reference, options = {}, fileAdapter = defaultFileAdapter, zipAdapter) {
252
219
  const parsed = parseSymbolReference(reference);
253
220
  if (!parsed.isValid) {
254
221
  return {
@@ -275,16 +242,16 @@ export function resolveSymbolReference(reference, options = {}) {
275
242
  }
276
243
  try {
277
244
  // .symbols files are ZIP archives
278
- const AdmZip = getAdmZip();
279
- const zip = new AdmZip(libraryInfo.pixFile);
245
+ const zipFile = libraryInfo.pixFile;
246
+ const zip = zipAdapter ? await zipAdapter(zipFile) : await getZipAdapter(zipFile, fileAdapter);
280
247
  // The path in the symbol reference becomes the path within the symbols/ folder
281
248
  // e.g., [tawasl]/above bw.png becomes symbols/above bw.png
282
249
  const symbolPath = `symbols/${parsed.path}`;
283
- const entry = zip.getEntry(symbolPath);
250
+ const entry = await zip.readFile(symbolPath);
284
251
  if (!entry) {
285
252
  // Try without the symbols/ prefix (in case reference already includes it)
286
253
  const altPath = parsed.path.startsWith('symbols/') ? parsed.path : `symbols/${parsed.path}`;
287
- const altEntry = zip.getEntry(altPath);
254
+ const altEntry = await zip.readFile(altPath);
288
255
  if (!altEntry) {
289
256
  return {
290
257
  reference: parsed,
@@ -295,7 +262,7 @@ export function resolveSymbolReference(reference, options = {}) {
295
262
  };
296
263
  }
297
264
  // Found with alternate path
298
- const data = altEntry.getData();
265
+ const data = Buffer.from(altEntry);
299
266
  return {
300
267
  reference: parsed,
301
268
  found: true,
@@ -305,7 +272,7 @@ export function resolveSymbolReference(reference, options = {}) {
305
272
  };
306
273
  }
307
274
  // Found the symbol!
308
- const data = entry.getData();
275
+ const data = Buffer.from(entry);
309
276
  return {
310
277
  reference: parsed,
311
278
  found: true,
@@ -13,8 +13,7 @@ import { parseSymbolReference } from './gridset/symbols';
13
13
  import { isSymbolLibraryReference } from './gridset/resolver';
14
14
  import { generateCloneId } from '../utilities/analytics/utils/idGenerator';
15
15
  import { translateWithSymbols, extractSymbolsFromButton } from './gridset/symbolAlignment';
16
- import { readBinaryFromInput, decodeText, writeBinaryToPath, getNodeRequire, isNodeRuntime, } from '../utils/io';
17
- import { openZipFromInput } from '../utils/zip';
16
+ import { decodeText } from '../utils/io';
18
17
  class GridsetProcessor extends BaseProcessor {
19
18
  constructor(options) {
20
19
  super(options);
@@ -395,19 +394,18 @@ class GridsetProcessor extends BaseProcessor {
395
394
  return texts;
396
395
  }
397
396
  async loadIntoTree(filePathOrBuffer) {
397
+ const { readBinaryFromInput } = this.options.fileAdapter;
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
+ zipResult = await this.options.zipAdapter(zipInput);
405
403
  }
406
404
  catch (error) {
407
405
  throw new Error(`Invalid ZIP file format: ${error.message}`);
408
406
  }
409
407
  const password = this.getGridsetPassword(filePathOrBuffer);
410
- const entries = getZipEntriesFromAdapter(zipResult.zip, password);
408
+ const entries = getZipEntriesFromAdapter(zipResult, password);
411
409
  const options = {
412
410
  ignoreAttributes: false,
413
411
  ignoreDeclaration: true,
@@ -1683,6 +1681,7 @@ class GridsetProcessor extends BaseProcessor {
1683
1681
  return tree;
1684
1682
  }
1685
1683
  async processTexts(filePathOrBuffer, translations, outputPath) {
1684
+ const { readBinaryFromInput } = this.options.fileAdapter;
1686
1685
  // Load the tree, apply translations, and save to new file
1687
1686
  const tree = await this.loadIntoTree(filePathOrBuffer);
1688
1687
  // Apply translations to all text content
@@ -1785,6 +1784,7 @@ class GridsetProcessor extends BaseProcessor {
1785
1784
  * @returns Buffer of the translated gridset
1786
1785
  */
1787
1786
  async processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
1787
+ const { readBinaryFromInput } = this.options.fileAdapter;
1788
1788
  const tree = await this.loadIntoTree(filePathOrBuffer);
1789
1789
  // Validate translations using shared utility
1790
1790
  const buttonIds = Object.values(tree.pages).flatMap((page) => page.buttons.map((b) => b.id));
@@ -1826,36 +1826,12 @@ class GridsetProcessor extends BaseProcessor {
1826
1826
  return readBinaryFromInput(outputPath);
1827
1827
  }
1828
1828
  async saveFromTree(tree, outputPath) {
1829
- const useNodeZip = isNodeRuntime();
1830
- let addText;
1831
- let addBinary;
1832
- let finalizeZip;
1833
- if (useNodeZip) {
1834
- const AdmZip = getNodeRequire()('adm-zip');
1835
- const zip = new AdmZip();
1836
- addText = (entryPath, content) => {
1837
- zip.addFile(entryPath, Buffer.from(content, 'utf8'));
1838
- };
1839
- addBinary = (entryPath, content) => {
1840
- zip.addFile(entryPath, Buffer.from(content));
1841
- };
1842
- finalizeZip = () => Promise.resolve(zip.toBuffer());
1843
- }
1844
- else {
1845
- const module = await import('jszip');
1846
- const JSZip = module.default || module;
1847
- const zip = new JSZip();
1848
- addText = (entryPath, content) => {
1849
- zip.file(entryPath, content, { binary: false });
1850
- };
1851
- addBinary = (entryPath, content) => {
1852
- zip.file(entryPath, content);
1853
- };
1854
- finalizeZip = async () => zip.generateAsync({ type: 'uint8array' });
1855
- }
1829
+ const files = [];
1830
+ const { writeBinaryToPath } = this.options.fileAdapter;
1831
+ const zip = await this.options.zipAdapter();
1856
1832
  if (Object.keys(tree.pages).length === 0) {
1857
1833
  // Create empty zip for empty tree
1858
- const zipBuffer = await finalizeZip();
1834
+ const zipBuffer = await zip.writeFiles([]);
1859
1835
  writeBinaryToPath(outputPath, zipBuffer);
1860
1836
  return;
1861
1837
  }
@@ -1928,7 +1904,10 @@ class GridsetProcessor extends BaseProcessor {
1928
1904
  suppressEmptyNode: true,
1929
1905
  });
1930
1906
  const settingsXmlContent = settingsBuilder.build(settingsData);
1931
- addText('Settings0/settings.xml', settingsXmlContent);
1907
+ files.push({
1908
+ name: 'Settings0/settings.xml',
1909
+ data: settingsXmlContent,
1910
+ });
1932
1911
  // Create Settings0/Styles/style.xml if there are styles
1933
1912
  if (uniqueStyles.size > 0) {
1934
1913
  const stylesArray = Array.from(uniqueStyles.values()).map(({ id, style }) => {
@@ -1961,7 +1940,10 @@ class GridsetProcessor extends BaseProcessor {
1961
1940
  indentBy: ' ',
1962
1941
  });
1963
1942
  const styleXmlContent = styleBuilder.build(styleData);
1964
- addText('Settings0/Styles/styles.xml', styleXmlContent);
1943
+ files.push({
1944
+ name: 'Settings0/Styles/styles.xml',
1945
+ data: styleXmlContent,
1946
+ });
1965
1947
  }
1966
1948
  // Collect grid file paths for FileMap.xml
1967
1949
  const gridFilePaths = [];
@@ -2102,14 +2084,20 @@ class GridsetProcessor extends BaseProcessor {
2102
2084
  // Add to zip in Grids folder with proper Grid3 naming
2103
2085
  const gridPath = `Grids/${page.name || page.id}/grid.xml`;
2104
2086
  gridFilePaths.push(gridPath);
2105
- addText(gridPath, xmlContent);
2087
+ files.push({
2088
+ name: gridPath,
2089
+ data: xmlContent,
2090
+ });
2106
2091
  });
2107
2092
  // Write image files to ZIP
2108
2093
  buttonImages.forEach((imgData) => {
2109
2094
  if (imgData.imageData && imgData.imageData.length > 0) {
2110
2095
  // Create image path in the grid's directory
2111
2096
  const imagePath = `Grids/${imgData.pageName}/${imgData.x}-${imgData.y}-0-text-0.${imgData.ext}`;
2112
- addBinary(imagePath, imgData.imageData);
2097
+ files.push({
2098
+ name: imagePath,
2099
+ data: imgData.imageData,
2100
+ });
2113
2101
  }
2114
2102
  });
2115
2103
  // Create FileMap.xml to map all grid files with their dynamic image files
@@ -2148,9 +2136,12 @@ class GridsetProcessor extends BaseProcessor {
2148
2136
  indentBy: ' ',
2149
2137
  });
2150
2138
  const fileMapXmlContent = fileMapBuilder.build(fileMapData);
2151
- addText('FileMap.xml', fileMapXmlContent);
2139
+ files.push({
2140
+ name: 'FileMap.xml',
2141
+ data: fileMapXmlContent,
2142
+ });
2152
2143
  // Write the zip file
2153
- const zipBuffer = await finalizeZip();
2144
+ const zipBuffer = await zip.writeFiles(files);
2154
2145
  writeBinaryToPath(outputPath, zipBuffer);
2155
2146
  }
2156
2147
  // Helper method to calculate column definitions based on page layout
@@ -2248,7 +2239,7 @@ class GridsetProcessor extends BaseProcessor {
2248
2239
  * @returns Promise with validation result
2249
2240
  */
2250
2241
  async validate(filePath) {
2251
- return GridsetValidator.validateFile(filePath);
2242
+ return GridsetValidator.validateFile(filePath, this.options.fileAdapter);
2252
2243
  }
2253
2244
  }
2254
2245
  export { GridsetProcessor };
@@ -2,8 +2,8 @@ import { BaseProcessor, } from '../core/baseProcessor';
2
2
  import { AACTree, AACPage, AACButton, AACSemanticCategory, AACSemanticIntent, } from '../core/treeStructure';
3
3
  import { generateCloneId } from '../utilities/analytics/utils/idGenerator';
4
4
  import { extractAllButtonsForTranslation, validateTranslationResults, } from '../utilities/translation/translationProcessor';
5
- import { readBinaryFromInput, readTextFromInput, writeTextToPath, encodeBase64, decodeText, getNodeRequire, isNodeRuntime, } from '../utils/io';
6
- import { openZipFromInput } from '../utils/zip';
5
+ import { encodeBase64, decodeText } from '../utils/io';
6
+ import { getZipAdapter } from '../utils/zip';
7
7
  const OBF_FORMAT_VERSION = 'open-board-0.1';
8
8
  /**
9
9
  * Map OBF hidden value to AAC standard visibility
@@ -120,13 +120,9 @@ class ObfProcessor extends BaseProcessor {
120
120
  return 'image/png';
121
121
  }
122
122
  }
123
- async processBoard(boardData, _boardPath) {
123
+ async processBoard(boardData, _boardPath, isZipEntry) {
124
124
  const sourceButtons = boardData.buttons || [];
125
125
  // Calculate page ID first (used to make button IDs unique)
126
- const isZipEntry = _boardPath &&
127
- _boardPath.endsWith('.obf') &&
128
- !_boardPath.includes('/') &&
129
- !_boardPath.includes('\\');
130
126
  const pageId = isZipEntry
131
127
  ? _boardPath // Zip entry - use filename to match navigation paths
132
128
  : boardData?.id
@@ -287,6 +283,7 @@ class ObfProcessor extends BaseProcessor {
287
283
  return texts;
288
284
  }
289
285
  async loadIntoTree(filePathOrBuffer) {
286
+ const { readBinaryFromInput, readTextFromInput } = this.options.fileAdapter;
290
287
  // Detailed logging for debugging input
291
288
  const bufferLength = typeof filePathOrBuffer === 'string'
292
289
  ? null
@@ -328,7 +325,7 @@ class ObfProcessor extends BaseProcessor {
328
325
  const boardData = tryParseObfJson(content);
329
326
  if (boardData) {
330
327
  console.log('[OBF] Detected .obf file, parsed as JSON');
331
- const page = await this.processBoard(boardData, filePathOrBuffer);
328
+ const page = await this.processBoard(boardData, filePathOrBuffer, false);
332
329
  tree.addPage(page);
333
330
  // Set metadata from root board
334
331
  tree.metadata.format = 'obf';
@@ -367,7 +364,7 @@ class ObfProcessor extends BaseProcessor {
367
364
  if (!asJson)
368
365
  throw new Error('Invalid OBF content: not JSON and not ZIP');
369
366
  console.log('[OBF] Detected buffer/string as OBF JSON');
370
- const page = await this.processBoard(asJson, '[bufferOrString]');
367
+ const page = await this.processBoard(asJson, '[bufferOrString]', false);
371
368
  tree.addPage(page);
372
369
  // Set metadata from root board
373
370
  tree.metadata.format = 'obf';
@@ -384,10 +381,7 @@ class ObfProcessor extends BaseProcessor {
384
381
  return tree;
385
382
  }
386
383
  try {
387
- const zipResult = this.options.zipAdapter
388
- ? await this.options.zipAdapter(filePathOrBuffer)
389
- : await openZipFromInput(filePathOrBuffer);
390
- this.zipFile = zipResult.zip;
384
+ this.zipFile = await this.options.zipAdapter(filePathOrBuffer);
391
385
  }
392
386
  catch (err) {
393
387
  console.error('[OBF] Error loading ZIP:', err);
@@ -396,17 +390,42 @@ class ObfProcessor extends BaseProcessor {
396
390
  // Store the ZIP file reference for image extraction
397
391
  this.imageCache.clear(); // Clear cache for new file
398
392
  console.log('[OBF] Detected zip archive, extracting .obf files');
399
- // Collect all .obf entries
400
- const obfEntries = this.zipFile
401
- .listFiles()
402
- .filter((name) => name.toLowerCase().endsWith('.obf'));
393
+ // List manifest and OBF files
394
+ const filesInZip = this.zipFile.listFiles();
395
+ const manifestFile = filesInZip.filter((name) => name.toLowerCase() === 'manifest.json');
396
+ let obfEntries = filesInZip.filter((name) => name.toLowerCase().endsWith('.obf'));
397
+ // Attempt to read manifest
398
+ if (manifestFile && manifestFile.length === 1) {
399
+ try {
400
+ const content = await this.zipFile.readFile(manifestFile[0]);
401
+ const data = decodeText(content);
402
+ const str = typeof data === 'string' ? data : readTextFromInput(data);
403
+ if (!str.trim())
404
+ throw new Error('Manifest object missing');
405
+ const manifestObject = JSON.parse(str);
406
+ if (!manifestObject)
407
+ throw new Error('Manifest object is empty');
408
+ // Replace OBF file list
409
+ if (manifestObject.paths && manifestObject.paths.boards) {
410
+ obfEntries = Object.values(manifestObject.paths.boards);
411
+ }
412
+ // Move root board to top of list
413
+ if (manifestObject.root) {
414
+ obfEntries = obfEntries.filter((item) => item !== manifestObject.root);
415
+ obfEntries.unshift(manifestObject.root);
416
+ }
417
+ }
418
+ catch (err) {
419
+ console.warn('[OBF] Error processing mainfest', err);
420
+ }
421
+ }
403
422
  // Process each .obf entry
404
423
  for (const entryName of obfEntries) {
405
424
  try {
406
425
  const content = await this.zipFile.readFile(entryName);
407
426
  const boardData = tryParseObfJson(decodeText(content));
408
427
  if (boardData) {
409
- const page = await this.processBoard(boardData, entryName);
428
+ const page = await this.processBoard(boardData, entryName, true);
410
429
  tree.addPage(page);
411
430
  // Set metadata if not already set (use first board as reference)
412
431
  if (!tree.metadata.format) {
@@ -518,6 +537,7 @@ class ObfProcessor extends BaseProcessor {
518
537
  };
519
538
  }
520
539
  async processTexts(filePathOrBuffer, translations, outputPath) {
540
+ const { readBinaryFromInput } = this.options.fileAdapter;
521
541
  // Load the tree, apply translations, and save to new file
522
542
  const tree = await this.loadIntoTree(filePathOrBuffer);
523
543
  // Apply translations to all text content
@@ -550,6 +570,7 @@ class ObfProcessor extends BaseProcessor {
550
570
  return readBinaryFromInput(outputPath);
551
571
  }
552
572
  async saveFromTree(tree, outputPath) {
573
+ const { writeTextToPath, writeBinaryToPath } = this.options.fileAdapter;
553
574
  if (outputPath.endsWith('.obf')) {
554
575
  // Save as single OBF JSON file
555
576
  const rootPage = tree.rootId ? tree.getPage(tree.rootId) : Object.values(tree.pages)[0];
@@ -560,32 +581,18 @@ class ObfProcessor extends BaseProcessor {
560
581
  writeTextToPath(outputPath, JSON.stringify(obfBoard, null, 2));
561
582
  }
562
583
  else {
563
- // Save as OBZ (zip with multiple OBF files)
564
- if (isNodeRuntime()) {
565
- const AdmZip = getNodeRequire()('adm-zip');
566
- const zip = new AdmZip();
567
- Object.values(tree.pages).forEach((page) => {
568
- const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
569
- const obfContent = JSON.stringify(obfBoard, null, 2);
570
- zip.addFile(`${page.id}.obf`, Buffer.from(obfContent, 'utf8'));
571
- });
572
- const zipBuffer = zip.toBuffer();
573
- const { writeBinaryToPath } = await import('../utils/io');
574
- writeBinaryToPath(outputPath, zipBuffer);
575
- }
576
- else {
577
- const module = await import('jszip');
578
- const JSZip = module.default || module;
579
- const zip = new JSZip();
580
- Object.values(tree.pages).forEach((page) => {
581
- const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
582
- const obfContent = JSON.stringify(obfBoard, null, 2);
583
- zip.file(`${page.id}.obf`, obfContent);
584
- });
585
- const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
586
- const { writeBinaryToPath } = await import('../utils/io');
587
- writeBinaryToPath(outputPath, zipBuffer);
588
- }
584
+ const files = Object.values(tree.pages).map((page) => {
585
+ const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
586
+ const obfContent = JSON.stringify(obfBoard, null, 2);
587
+ const name = page.id.endsWith('.obf') ? page.id : `${page.id}.obf`;
588
+ return {
589
+ name,
590
+ data: new TextEncoder().encode(obfContent),
591
+ };
592
+ });
593
+ const zip = await getZipAdapter(undefined, this.options.fileAdapter);
594
+ const zipData = await zip.writeFiles(files);
595
+ writeBinaryToPath(outputPath, zipData);
589
596
  }
590
597
  }
591
598
  /**
@@ -609,7 +616,7 @@ class ObfProcessor extends BaseProcessor {
609
616
  */
610
617
  async validate(filePath) {
611
618
  const ObfValidator = this.getObfValidator();
612
- return ObfValidator.validateFile(filePath);
619
+ return ObfValidator.validateFile(filePath, this.options.fileAdapter);
613
620
  }
614
621
  /**
615
622
  * Extract symbol information from an OBF/OBZ file for LLM-based translation.
@@ -651,6 +658,7 @@ class ObfProcessor extends BaseProcessor {
651
658
  * @returns Buffer of the translated OBF/OBZ file
652
659
  */
653
660
  async processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
661
+ const { readBinaryFromInput } = this.options.fileAdapter;
654
662
  const tree = await this.loadIntoTree(filePathOrBuffer);
655
663
  // Validate translations using shared utility
656
664
  const buttonIds = Object.values(tree.pages).flatMap((page) => page.buttons.map((b) => b.id));
@@ -1,9 +1,8 @@
1
1
  import { BaseProcessor, } from '../core/baseProcessor';
2
2
  import { AACTree, AACPage, AACButton, AACSemanticIntent } from '../core/treeStructure';
3
- // Removed unused import: FileProcessor
4
3
  import { XMLParser, XMLValidator, XMLBuilder } from 'fast-xml-parser';
5
4
  import { ValidationFailureError, buildValidationResultFromMessage, } from '../validation/validationTypes';
6
- import { getBasename, readBinaryFromInput, readTextFromInput, writeBinaryToPath, writeTextToPath, encodeText, } from '../utils/io';
5
+ import { getBasename, encodeText } from '../utils/io';
7
6
  class OpmlProcessor extends BaseProcessor {
8
7
  constructor(options) {
9
8
  super(options);
@@ -50,6 +49,7 @@ class OpmlProcessor extends BaseProcessor {
50
49
  return { page, childPages };
51
50
  }
52
51
  async extractTexts(filePathOrBuffer) {
52
+ const { readTextFromInput } = this.options.fileAdapter;
53
53
  await Promise.resolve();
54
54
  const content = readTextFromInput(filePathOrBuffer);
55
55
  const parser = new XMLParser({ ignoreAttributes: false });
@@ -82,6 +82,7 @@ class OpmlProcessor extends BaseProcessor {
82
82
  return texts;
83
83
  }
84
84
  async loadIntoTree(filePathOrBuffer) {
85
+ const { readBinaryFromInput, readTextFromInput } = this.options.fileAdapter;
85
86
  await Promise.resolve();
86
87
  const filename = typeof filePathOrBuffer === 'string' ? getBasename(filePathOrBuffer) : 'upload.opml';
87
88
  const buffer = readBinaryFromInput(filePathOrBuffer);
@@ -168,6 +169,7 @@ class OpmlProcessor extends BaseProcessor {
168
169
  }
169
170
  }
170
171
  async processTexts(filePathOrBuffer, translations, outputPath) {
172
+ const { writeBinaryToPath, readTextFromInput } = this.options.fileAdapter;
171
173
  await Promise.resolve();
172
174
  const content = readTextFromInput(filePathOrBuffer);
173
175
  let translatedContent = content;
@@ -184,6 +186,7 @@ class OpmlProcessor extends BaseProcessor {
184
186
  return resultBuffer;
185
187
  }
186
188
  async saveFromTree(tree, outputPath) {
189
+ const { writeTextToPath } = this.options.fileAdapter;
187
190
  await Promise.resolve();
188
191
  // Helper to recursively build outline nodes with cycle detection
189
192
  function buildOutline(page, visited = new Set()) {