@willwade/aac-processors 0.1.20 → 0.2.0
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/core/baseProcessor.js +4 -0
- package/dist/browser/processors/applePanelsProcessor.js +33 -40
- package/dist/browser/processors/astericsGridProcessor.js +31 -26
- package/dist/browser/processors/dotProcessor.js +11 -12
- package/dist/browser/processors/gridset/colorUtils.js +354 -0
- package/dist/browser/processors/gridset/helpers.js +60 -53
- package/dist/browser/processors/gridset/index.js +61 -0
- package/dist/browser/processors/gridset/styleHelpers.js +205 -0
- package/dist/browser/processors/gridset/symbolExtractor.js +331 -0
- package/dist/browser/processors/gridset/symbolSearch.js +248 -0
- package/dist/browser/processors/gridset/symbols.js +39 -72
- package/dist/browser/processors/gridsetProcessor.js +39 -48
- package/dist/browser/processors/obfProcessor.js +39 -53
- package/dist/browser/processors/opmlProcessor.js +11 -12
- package/dist/browser/processors/snap/helpers.js +57 -49
- package/dist/browser/processors/snapProcessor.js +48 -51
- package/dist/browser/processors/touchchatProcessor.js +60 -52
- package/dist/browser/utilities/analytics/history.js +24 -18
- package/dist/browser/utilities/analytics/metrics/comparison.js +16 -16
- package/dist/browser/utilities/analytics/metrics/vocabulary.js +2 -2
- package/dist/browser/utilities/analytics/reference/browser.js +16 -16
- package/dist/browser/utilities/analytics/reference/index.js +44 -35
- package/dist/browser/utils/io.js +78 -21
- package/dist/browser/utils/sqlite.js +8 -10
- package/dist/browser/utils/zip.js +43 -43
- package/dist/browser/validation/baseValidator.js +5 -0
- package/dist/browser/validation/gridsetValidator.js +12 -20
- package/dist/browser/validation/obfValidator.js +6 -5
- package/dist/browser/validation/snapValidator.js +11 -7
- package/dist/browser/validation/touchChatValidator.js +23 -13
- package/dist/cli/index.js +22 -24
- package/dist/core/baseProcessor.d.ts +7 -7
- package/dist/core/baseProcessor.js +4 -0
- package/dist/processors/applePanelsProcessor.js +32 -39
- package/dist/processors/astericsGridProcessor.d.ts +4 -4
- package/dist/processors/astericsGridProcessor.js +30 -25
- package/dist/processors/dotProcessor.js +10 -11
- package/dist/processors/excelProcessor.d.ts +3 -3
- package/dist/processors/excelProcessor.js +14 -20
- package/dist/processors/gridset/helpers.d.ts +12 -14
- package/dist/processors/gridset/helpers.js +60 -79
- package/dist/processors/gridset/imageDebug.d.ts +3 -5
- package/dist/processors/gridset/imageDebug.js +4 -4
- package/dist/processors/gridset/password.d.ts +1 -1
- package/dist/processors/gridset/symbolExtractor.d.ts +5 -3
- package/dist/processors/gridset/symbolExtractor.js +15 -38
- package/dist/processors/gridset/symbolSearch.d.ts +11 -10
- package/dist/processors/gridset/symbolSearch.js +29 -51
- package/dist/processors/gridset/symbols.d.ts +8 -6
- package/dist/processors/gridset/symbols.js +38 -71
- package/dist/processors/gridset/wordlistHelpers.d.ts +4 -6
- package/dist/processors/gridset/wordlistHelpers.js +15 -74
- package/dist/processors/gridsetProcessor.d.ts +2 -2
- package/dist/processors/gridsetProcessor.js +38 -70
- package/dist/processors/obfProcessor.d.ts +2 -2
- package/dist/processors/obfProcessor.js +38 -75
- package/dist/processors/obfsetProcessor.js +2 -3
- package/dist/processors/opmlProcessor.js +10 -11
- package/dist/processors/snap/helpers.d.ts +9 -9
- package/dist/processors/snap/helpers.js +58 -76
- package/dist/processors/snapProcessor.d.ts +2 -2
- package/dist/processors/snapProcessor.js +47 -50
- package/dist/processors/touchchatProcessor.d.ts +2 -2
- package/dist/processors/touchchatProcessor.js +59 -51
- package/dist/types/aac.d.ts +2 -2
- package/dist/utilities/analytics/history.d.ts +8 -8
- package/dist/utilities/analytics/history.js +24 -18
- package/dist/utilities/analytics/index.d.ts +3 -2
- package/dist/utilities/analytics/index.js +9 -10
- package/dist/utilities/analytics/metrics/comparison.d.ts +1 -1
- package/dist/utilities/analytics/metrics/comparison.js +16 -16
- package/dist/utilities/analytics/metrics/vocabulary.d.ts +1 -1
- package/dist/utilities/analytics/metrics/vocabulary.js +2 -2
- package/dist/utilities/analytics/reference/browser.d.ts +9 -9
- package/dist/utilities/analytics/reference/browser.js +16 -16
- package/dist/utilities/analytics/reference/index.d.ts +25 -23
- package/dist/utilities/analytics/reference/index.js +43 -34
- package/dist/utilities/symbolTools.d.ts +8 -6
- package/dist/utilities/symbolTools.js +21 -18
- package/dist/utils/io.d.ts +24 -6
- package/dist/utils/io.js +79 -25
- package/dist/utils/sqlite.d.ts +3 -1
- package/dist/utils/sqlite.js +7 -9
- package/dist/utils/zip.d.ts +7 -3
- package/dist/utils/zip.js +43 -43
- package/dist/validation/applePanelsValidator.d.ts +2 -1
- package/dist/validation/applePanelsValidator.js +10 -11
- package/dist/validation/astericsValidator.d.ts +2 -1
- package/dist/validation/astericsValidator.js +5 -4
- package/dist/validation/baseValidator.d.ts +2 -2
- package/dist/validation/baseValidator.js +5 -0
- package/dist/validation/dotValidator.d.ts +2 -1
- package/dist/validation/dotValidator.js +5 -4
- package/dist/validation/excelValidator.d.ts +2 -1
- package/dist/validation/excelValidator.js +5 -4
- package/dist/validation/gridsetValidator.d.ts +2 -1
- package/dist/validation/gridsetValidator.js +11 -22
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +5 -4
- package/dist/validation/obfValidator.d.ts +2 -1
- package/dist/validation/obfValidator.js +5 -4
- package/dist/validation/obfsetValidator.d.ts +2 -1
- package/dist/validation/obfsetValidator.js +5 -4
- package/dist/validation/opmlValidator.d.ts +2 -1
- package/dist/validation/opmlValidator.js +5 -4
- package/dist/validation/snapValidator.d.ts +2 -1
- package/dist/validation/snapValidator.js +10 -6
- package/dist/validation/touchChatValidator.d.ts +4 -6
- package/dist/validation/touchChatValidator.js +22 -12
- package/dist/validation/validationTypes.d.ts +8 -1
- package/package.json +1 -1
- package/dist/core/fileProcessor.d.ts +0 -7
- package/dist/core/fileProcessor.js +0 -57
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid 3 Symbol Search Implementation
|
|
3
|
+
*
|
|
4
|
+
* The .pix files are simple text mappings:
|
|
5
|
+
* searchTerm=symbolFilename=searchTerm
|
|
6
|
+
*
|
|
7
|
+
* Example:
|
|
8
|
+
* above bw=above bw.png=above bw
|
|
9
|
+
* active family=active family.png=active family
|
|
10
|
+
*/
|
|
11
|
+
import { defaultFileAdapter } from '../../utils/io';
|
|
12
|
+
/**
|
|
13
|
+
* Parse a .pix file into search index
|
|
14
|
+
* @param pixFilePath - Path to .pix file
|
|
15
|
+
* @returns Search index
|
|
16
|
+
*/
|
|
17
|
+
export async function parsePixFile(pixFilePath, fileAdapter = defaultFileAdapter) {
|
|
18
|
+
const { readTextFromInput, basename } = fileAdapter;
|
|
19
|
+
const content = await readTextFromInput(pixFilePath);
|
|
20
|
+
const library = basename(pixFilePath, '.pix');
|
|
21
|
+
const searchTerms = new Map();
|
|
22
|
+
const filenames = new Map();
|
|
23
|
+
const lines = content.split('\n');
|
|
24
|
+
for (const line of lines) {
|
|
25
|
+
const trimmed = line.trim();
|
|
26
|
+
if (!trimmed || trimmed.startsWith('encoding=')) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// Format: searchTerm=symbolFilename=searchTerm
|
|
30
|
+
const parts = trimmed.split('=');
|
|
31
|
+
if (parts.length >= 3) {
|
|
32
|
+
const searchTerm = parts[0];
|
|
33
|
+
const symbolFilename = parts[1];
|
|
34
|
+
const displayName = parts[2];
|
|
35
|
+
searchTerms.set(searchTerm.toLowerCase(), symbolFilename);
|
|
36
|
+
filenames.set(symbolFilename, displayName || searchTerm);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { library, searchTerms, filenames };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Load search indexes for all available libraries
|
|
43
|
+
* @param options - Search options
|
|
44
|
+
* @returns Map of library name to search index
|
|
45
|
+
*/
|
|
46
|
+
export async function loadSearchIndexes(options = {}, fileAdapter = defaultFileAdapter) {
|
|
47
|
+
const { listDir, pathExists, join, basename } = fileAdapter;
|
|
48
|
+
const { grid3Path, locale = 'en-GB', libraries: specifiedLibs } = options;
|
|
49
|
+
if (!grid3Path) {
|
|
50
|
+
throw new Error('grid3Path is required for symbol search');
|
|
51
|
+
}
|
|
52
|
+
const searchIndexesDir = join(grid3Path, 'Locale', locale, 'symbolsearch');
|
|
53
|
+
if (!(await pathExists(searchIndexesDir))) {
|
|
54
|
+
throw new Error(`Symbol search directory not found: ${searchIndexesDir}`);
|
|
55
|
+
}
|
|
56
|
+
const indexes = new Map();
|
|
57
|
+
const files = await listDir(searchIndexesDir);
|
|
58
|
+
for (const file of files) {
|
|
59
|
+
if (!file.endsWith('.pix')) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const libraryName = basename(file, '.pix');
|
|
63
|
+
// Filter libraries if specified
|
|
64
|
+
if (specifiedLibs && specifiedLibs.length > 0) {
|
|
65
|
+
if (!specifiedLibs.some((lib) => lib.toLowerCase() === libraryName.toLowerCase())) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const pixFilePath = join(searchIndexesDir, file);
|
|
71
|
+
const index = await parsePixFile(pixFilePath);
|
|
72
|
+
indexes.set(libraryName, index);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
console.warn(`Failed to load index for ${libraryName}:`, error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return indexes;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Search for symbols by term
|
|
82
|
+
* @param searchTerm - Term to search for
|
|
83
|
+
* @param options - Search options
|
|
84
|
+
* @returns Array of search results
|
|
85
|
+
*/
|
|
86
|
+
export async function searchSymbols(searchTerm, options = {}) {
|
|
87
|
+
const indexes = await loadSearchIndexes(options);
|
|
88
|
+
const results = [];
|
|
89
|
+
const lowerSearchTerm = searchTerm.toLowerCase().trim();
|
|
90
|
+
const limit = options.limit || 100;
|
|
91
|
+
for (const [libraryName, index] of indexes.entries()) {
|
|
92
|
+
// Exact match first
|
|
93
|
+
if (index.searchTerms.has(lowerSearchTerm)) {
|
|
94
|
+
const symbolFilename = index.searchTerms.get(lowerSearchTerm);
|
|
95
|
+
if (symbolFilename) {
|
|
96
|
+
results.push({
|
|
97
|
+
searchTerm: lowerSearchTerm,
|
|
98
|
+
symbolFilename,
|
|
99
|
+
displayName: index.filenames.get(symbolFilename) || lowerSearchTerm,
|
|
100
|
+
library: libraryName,
|
|
101
|
+
exactMatch: true,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Fuzzy match if enabled
|
|
106
|
+
if (options.fuzzyMatch !== false) {
|
|
107
|
+
for (const [term, symbolFilename] of index.searchTerms.entries()) {
|
|
108
|
+
if (term.includes(lowerSearchTerm) || lowerSearchTerm.includes(term)) {
|
|
109
|
+
// Skip if already added as exact match
|
|
110
|
+
if (results.some((r) => r.library === libraryName && r.symbolFilename === symbolFilename)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
results.push({
|
|
114
|
+
searchTerm: lowerSearchTerm,
|
|
115
|
+
symbolFilename,
|
|
116
|
+
displayName: index.filenames.get(symbolFilename) || term,
|
|
117
|
+
library: libraryName,
|
|
118
|
+
exactMatch: false,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Sort by exact match first, then by library
|
|
125
|
+
results.sort((a, b) => {
|
|
126
|
+
if (a.exactMatch !== b.exactMatch) {
|
|
127
|
+
return a.exactMatch ? -1 : 1;
|
|
128
|
+
}
|
|
129
|
+
return a.library.localeCompare(b.library);
|
|
130
|
+
});
|
|
131
|
+
return results.slice(0, limit);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get symbol filename for a specific search term
|
|
135
|
+
* @param searchTerm - Search term to look up
|
|
136
|
+
* @param library - Library name
|
|
137
|
+
* @param options - Search options
|
|
138
|
+
* @returns Symbol filename or undefined
|
|
139
|
+
*/
|
|
140
|
+
export async function getSymbolFilename(searchTerm, library, options = {}) {
|
|
141
|
+
const indexes = await loadSearchIndexes({
|
|
142
|
+
...options,
|
|
143
|
+
libraries: [library],
|
|
144
|
+
});
|
|
145
|
+
const index = indexes.get(library.toLowerCase());
|
|
146
|
+
if (!index) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
return index.searchTerms.get(searchTerm.toLowerCase());
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get display name for a symbol filename
|
|
153
|
+
* @param symbolFilename - Symbol filename (e.g., "above bw.png")
|
|
154
|
+
* @param library - Library name
|
|
155
|
+
* @param options - Search options
|
|
156
|
+
* @returns Display name or undefined
|
|
157
|
+
*/
|
|
158
|
+
export async function getSymbolDisplayName(symbolFilename, library, options = {}) {
|
|
159
|
+
const indexes = await loadSearchIndexes({
|
|
160
|
+
...options,
|
|
161
|
+
libraries: [library],
|
|
162
|
+
});
|
|
163
|
+
const index = indexes.get(library.toLowerCase());
|
|
164
|
+
if (!index) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
return index.filenames.get(symbolFilename);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get all search terms for a library
|
|
171
|
+
* @param library - Library name
|
|
172
|
+
* @param options - Search options
|
|
173
|
+
* @returns Array of search terms
|
|
174
|
+
*/
|
|
175
|
+
export async function getAllSearchTerms(library, options = {}) {
|
|
176
|
+
const indexes = await loadSearchIndexes({
|
|
177
|
+
...options,
|
|
178
|
+
libraries: [library],
|
|
179
|
+
});
|
|
180
|
+
const index = indexes.get(library.toLowerCase());
|
|
181
|
+
if (!index) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
return Array.from(index.searchTerms.keys());
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Search suggestions (autocomplete)
|
|
188
|
+
* @param partialTerm - Partial search term
|
|
189
|
+
* @param options - Search options
|
|
190
|
+
* @returns Array of suggested terms
|
|
191
|
+
*/
|
|
192
|
+
export async function getSearchSuggestions(partialTerm, options = {}) {
|
|
193
|
+
const indexes = await loadSearchIndexes(options);
|
|
194
|
+
const suggestions = new Set();
|
|
195
|
+
const lowerPartial = partialTerm.toLowerCase().trim();
|
|
196
|
+
for (const index of indexes.values()) {
|
|
197
|
+
for (const term of index.searchTerms.keys()) {
|
|
198
|
+
if (term.startsWith(lowerPartial)) {
|
|
199
|
+
suggestions.add(term);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return Array.from(suggestions).sort().slice(0, 20);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Search for symbols and return results with library references
|
|
207
|
+
* @param searchTerm - Term to search for
|
|
208
|
+
* @param options - Search options
|
|
209
|
+
* @returns Array of full symbol references
|
|
210
|
+
*/
|
|
211
|
+
export async function searchSymbolsWithReferences(searchTerm, options = {}) {
|
|
212
|
+
const results = await searchSymbols(searchTerm, options);
|
|
213
|
+
return results.map((r) => `[${r.library}]${r.symbolFilename}`);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Count symbols in each library
|
|
217
|
+
* @param options - Search options
|
|
218
|
+
* @returns Map of library name to symbol count
|
|
219
|
+
*/
|
|
220
|
+
export async function countLibrarySymbols(options = {}) {
|
|
221
|
+
const indexes = await loadSearchIndexes(options);
|
|
222
|
+
const counts = new Map();
|
|
223
|
+
for (const [libraryName, index] of indexes.entries()) {
|
|
224
|
+
counts.set(libraryName, index.searchTerms.size);
|
|
225
|
+
}
|
|
226
|
+
return counts;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get symbol search statistics
|
|
230
|
+
* @param options - Search options
|
|
231
|
+
* @returns Statistics about available symbols
|
|
232
|
+
*/
|
|
233
|
+
export async function getSymbolSearchStats(options = {}) {
|
|
234
|
+
const indexes = await loadSearchIndexes(options);
|
|
235
|
+
const stats = {
|
|
236
|
+
totalLibraries: indexes.size,
|
|
237
|
+
totalSymbols: 0,
|
|
238
|
+
libraries: {},
|
|
239
|
+
};
|
|
240
|
+
for (const [libraryName, index] of indexes.entries()) {
|
|
241
|
+
stats.totalSymbols += index.searchTerms.size;
|
|
242
|
+
stats.libraries[libraryName] = {
|
|
243
|
+
symbolCount: index.searchTerms.size,
|
|
244
|
+
exampleTerms: Array.from(index.searchTerms.keys()).slice(0, 10),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return stats;
|
|
248
|
+
}
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
*
|
|
13
13
|
* This module provides symbol resolution and metadata extraction.
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
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 async 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
|
-
|
|
132
|
-
if (defaultPath && fs.existsSync(defaultPath)) {
|
|
101
|
+
if (defaultPath && (await 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 (
|
|
113
|
+
if (await 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
|
|
162
|
-
return
|
|
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
|
|
173
|
-
return
|
|
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 = {}) {
|
|
181
|
-
const
|
|
149
|
+
export async function getAvailableSymbolLibraries(options = {}, fileAdapter) {
|
|
150
|
+
const { pathExists, getFileSize, listDir, join, basename } = fileAdapter ?? defaultFileAdapter;
|
|
151
|
+
const grid3Path = options.grid3Path || options.symbolDir || (await getDefaultGrid3Path());
|
|
182
152
|
if (!grid3Path) {
|
|
183
153
|
return [];
|
|
184
154
|
}
|
|
185
|
-
const symbolsDir = getSymbolLibrariesDir(grid3Path);
|
|
186
|
-
|
|
187
|
-
if (!fs.existsSync(symbolsDir)) {
|
|
155
|
+
const symbolsDir = getSymbolLibrariesDir(grid3Path, fileAdapter);
|
|
156
|
+
if (!(await pathExists(symbolsDir))) {
|
|
188
157
|
return [];
|
|
189
158
|
}
|
|
190
159
|
const libraries = [];
|
|
191
|
-
const files =
|
|
160
|
+
const files = await listDir(symbolsDir);
|
|
192
161
|
for (const file of files) {
|
|
193
162
|
if (file.endsWith('.symbols')) {
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
const libraryName = path.basename(file, '.symbols');
|
|
163
|
+
const fullPath = join(symbolsDir, file);
|
|
164
|
+
const size = await 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
|
|
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 = {}) {
|
|
216
|
-
const
|
|
183
|
+
export async function getSymbolLibraryInfo(libraryName, options = {}, fileAdapter) {
|
|
184
|
+
const { pathExists, getFileSize, join } = fileAdapter ?? defaultFileAdapter;
|
|
185
|
+
const grid3Path = options.grid3Path || options.symbolDir || (await 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
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (fs.existsSync(fullPath)) {
|
|
233
|
-
const stats = fs.statSync(fullPath);
|
|
198
|
+
const fullPath = join(symbolsDir, file);
|
|
199
|
+
if (await pathExists(fullPath)) {
|
|
200
|
+
const size = await getFileSize(fullPath);
|
|
234
201
|
return {
|
|
235
202
|
name: libraryName,
|
|
236
203
|
pixFile: fullPath,
|
|
237
204
|
exists: true,
|
|
238
|
-
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 {
|
|
@@ -257,7 +224,7 @@ export function resolveSymbolReference(reference, options = {}) {
|
|
|
257
224
|
error: 'Invalid symbol reference format',
|
|
258
225
|
};
|
|
259
226
|
}
|
|
260
|
-
const grid3Path = options.grid3Path || getDefaultGrid3Path();
|
|
227
|
+
const grid3Path = options.grid3Path || (await getDefaultGrid3Path());
|
|
261
228
|
if (!grid3Path) {
|
|
262
229
|
return {
|
|
263
230
|
reference: parsed,
|
|
@@ -265,7 +232,7 @@ export function resolveSymbolReference(reference, options = {}) {
|
|
|
265
232
|
error: 'Grid 3 installation not found. Please specify grid3Path.',
|
|
266
233
|
};
|
|
267
234
|
}
|
|
268
|
-
const libraryInfo = getSymbolLibraryInfo(parsed.library, { grid3Path });
|
|
235
|
+
const libraryInfo = await getSymbolLibraryInfo(parsed.library, { grid3Path });
|
|
269
236
|
if (!libraryInfo || !libraryInfo.exists) {
|
|
270
237
|
return {
|
|
271
238
|
reference: parsed,
|
|
@@ -275,16 +242,16 @@ export function resolveSymbolReference(reference, options = {}) {
|
|
|
275
242
|
}
|
|
276
243
|
try {
|
|
277
244
|
// .symbols files are ZIP archives
|
|
278
|
-
const
|
|
279
|
-
const zip =
|
|
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.
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 {
|
|
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
|
-
const zipInput = readBinaryFromInput(filePathOrBuffer);
|
|
402
|
-
zipResult = this.options.zipAdapter
|
|
403
|
-
? await this.options.zipAdapter(zipInput)
|
|
404
|
-
: await openZipFromInput(zipInput);
|
|
401
|
+
const zipInput = await readBinaryFromInput(filePathOrBuffer);
|
|
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
|
|
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
|
|
@@ -1743,7 +1742,7 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
1743
1742
|
});
|
|
1744
1743
|
// Save the translated tree and return its content
|
|
1745
1744
|
await this.saveFromTree(tree, outputPath);
|
|
1746
|
-
return readBinaryFromInput(outputPath);
|
|
1745
|
+
return await readBinaryFromInput(outputPath);
|
|
1747
1746
|
}
|
|
1748
1747
|
/**
|
|
1749
1748
|
* Extract symbol information from a gridset for LLM-based translation.
|
|
@@ -1752,7 +1751,7 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
1752
1751
|
* This method uses shared translation utilities that work across all AAC formats.
|
|
1753
1752
|
*
|
|
1754
1753
|
* @param filePathOrBuffer - Path to gridset file or buffer
|
|
1755
|
-
* @returns
|
|
1754
|
+
* @returns Promise resolving to symbol information for LLM processing
|
|
1756
1755
|
*/
|
|
1757
1756
|
async extractSymbolsForLLM(filePathOrBuffer) {
|
|
1758
1757
|
const tree = await this.loadIntoTree(filePathOrBuffer);
|
|
@@ -1782,9 +1781,10 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
1782
1781
|
* @param llmTranslations - Array of LLM translations with symbol info
|
|
1783
1782
|
* @param outputPath - Where to save the translated gridset
|
|
1784
1783
|
* @param options - Translation options (e.g., allowPartial for testing)
|
|
1785
|
-
* @returns
|
|
1784
|
+
* @returns Promise resolving to a 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));
|
|
@@ -1823,40 +1823,16 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
1823
1823
|
});
|
|
1824
1824
|
// Save and return
|
|
1825
1825
|
await this.saveFromTree(tree, outputPath);
|
|
1826
|
-
return readBinaryFromInput(outputPath);
|
|
1826
|
+
return await readBinaryFromInput(outputPath);
|
|
1827
1827
|
}
|
|
1828
1828
|
async saveFromTree(tree, outputPath) {
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1831
|
-
|
|
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
|
|
1859
|
-
writeBinaryToPath(outputPath, zipBuffer);
|
|
1834
|
+
const zipBuffer = await zip.writeFiles([]);
|
|
1835
|
+
await writeBinaryToPath(outputPath, zipBuffer);
|
|
1860
1836
|
return;
|
|
1861
1837
|
}
|
|
1862
1838
|
// Collect all unique styles from pages and buttons
|
|
@@ -1928,7 +1904,10 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
1928
1904
|
suppressEmptyNode: true,
|
|
1929
1905
|
});
|
|
1930
1906
|
const settingsXmlContent = settingsBuilder.build(settingsData);
|
|
1931
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,10 +2136,13 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
2148
2136
|
indentBy: ' ',
|
|
2149
2137
|
});
|
|
2150
2138
|
const fileMapXmlContent = fileMapBuilder.build(fileMapData);
|
|
2151
|
-
|
|
2139
|
+
files.push({
|
|
2140
|
+
name: 'FileMap.xml',
|
|
2141
|
+
data: fileMapXmlContent,
|
|
2142
|
+
});
|
|
2152
2143
|
// Write the zip file
|
|
2153
|
-
const zipBuffer = await
|
|
2154
|
-
writeBinaryToPath(outputPath, zipBuffer);
|
|
2144
|
+
const zipBuffer = await zip.writeFiles(files);
|
|
2145
|
+
await writeBinaryToPath(outputPath, zipBuffer);
|
|
2155
2146
|
}
|
|
2156
2147
|
// Helper method to calculate column definitions based on page layout
|
|
2157
2148
|
calculateColumnDefinitions(page) {
|
|
@@ -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 };
|