@willwade/aac-processors 0.0.5 → 0.0.6
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/cli/index.js +2 -2
- package/dist/core/treeStructure.d.ts +9 -1
- package/dist/core/treeStructure.js +5 -1
- package/dist/processors/applePanelsProcessor.js +67 -41
- package/dist/processors/astericsGridProcessor.js +120 -88
- package/dist/processors/excelProcessor.d.ts +3 -3
- package/dist/processors/excelProcessor.js +48 -77
- package/dist/processors/gridset/wordlistHelpers.js +3 -2
- package/dist/processors/gridsetProcessor.js +97 -105
- package/dist/processors/obfProcessor.d.ts +2 -0
- package/dist/processors/obfProcessor.js +129 -52
- package/dist/processors/opmlProcessor.js +13 -3
- package/dist/processors/snapProcessor.js +18 -10
- package/dist/processors/touchchatProcessor.js +45 -22
- package/dist/types/aac.d.ts +4 -0
- package/package.json +1 -1
|
@@ -32,7 +32,6 @@ const path_1 = __importDefault(require("path"));
|
|
|
32
32
|
const ExcelJS = __importStar(require("exceljs"));
|
|
33
33
|
const baseProcessor_1 = require("../core/baseProcessor");
|
|
34
34
|
const treeStructure_1 = require("../core/treeStructure");
|
|
35
|
-
const treeStructure_2 = require("../core/treeStructure");
|
|
36
35
|
/**
|
|
37
36
|
* Excel Processor for converting AAC grids to Excel format
|
|
38
37
|
* Converts AAC tree structures to Excel workbooks with each page as a worksheet
|
|
@@ -44,47 +43,18 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
44
43
|
* @param filePathOrBuffer - Path to Excel file or Buffer containing Excel data
|
|
45
44
|
* @returns Array of all text content found in the Excel file
|
|
46
45
|
*/
|
|
47
|
-
extractTexts(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const workbook = new ExcelJS.Workbook();
|
|
51
|
-
if (Buffer.isBuffer(filePathOrBuffer)) {
|
|
52
|
-
// For buffer input, we need to read it differently
|
|
53
|
-
// This is a placeholder - actual implementation would need to handle buffer reading
|
|
54
|
-
throw new Error('Buffer input not yet implemented for Excel files');
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
// Read from file path
|
|
58
|
-
if (!fs_1.default.existsSync(filePathOrBuffer)) {
|
|
59
|
-
return texts;
|
|
60
|
-
}
|
|
61
|
-
// Note: ExcelJS readFile is async, but we need sync for this interface
|
|
62
|
-
// This is a limitation that would need to be addressed in a real implementation
|
|
63
|
-
throw new Error('Synchronous Excel reading not yet implemented');
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
console.warn(`Failed to extract texts from Excel file: ${error}`);
|
|
68
|
-
return texts;
|
|
69
|
-
}
|
|
46
|
+
extractTexts(_filePathOrBuffer) {
|
|
47
|
+
console.warn('ExcelProcessor.extractTexts is not implemented yet.');
|
|
48
|
+
return [];
|
|
70
49
|
}
|
|
71
50
|
/**
|
|
72
51
|
* Load Excel file into AACTree structure
|
|
73
52
|
* @param filePathOrBuffer - Path to Excel file or Buffer containing Excel data
|
|
74
53
|
* @returns AACTree representation of the Excel file
|
|
75
54
|
*/
|
|
76
|
-
loadIntoTree(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// For now, return empty tree as Excel -> AAC conversion is not the primary use case
|
|
80
|
-
// This would be implemented if bidirectional conversion is needed
|
|
81
|
-
console.warn('Excel to AAC conversion not implemented - returning empty tree');
|
|
82
|
-
return tree;
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
console.warn(`Failed to load Excel file into tree: ${error}`);
|
|
86
|
-
return tree;
|
|
87
|
-
}
|
|
55
|
+
loadIntoTree(_filePathOrBuffer) {
|
|
56
|
+
console.warn('ExcelProcessor.loadIntoTree is not implemented yet.');
|
|
57
|
+
return new treeStructure_1.AACTree();
|
|
88
58
|
}
|
|
89
59
|
/**
|
|
90
60
|
* Process texts in Excel file (apply translations)
|
|
@@ -93,18 +63,13 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
93
63
|
* @param outputPath - Path where translated Excel file should be saved
|
|
94
64
|
* @returns Buffer containing the translated Excel file
|
|
95
65
|
*/
|
|
96
|
-
processTexts(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
throw new Error('Excel text processing not yet implemented');
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
console.warn(`Failed to process Excel texts: ${error}`);
|
|
105
|
-
// Return empty buffer as fallback
|
|
106
|
-
return Buffer.alloc(0);
|
|
66
|
+
processTexts(_filePathOrBuffer, _translations, outputPath) {
|
|
67
|
+
console.warn('ExcelProcessor.processTexts is not implemented yet.');
|
|
68
|
+
const outputDir = path_1.default.dirname(outputPath);
|
|
69
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
70
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
107
71
|
}
|
|
72
|
+
return Buffer.alloc(0);
|
|
108
73
|
}
|
|
109
74
|
/**
|
|
110
75
|
* Convert an AAC page to an Excel worksheet
|
|
@@ -113,7 +78,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
113
78
|
* @param tree - Full AAC tree for navigation context
|
|
114
79
|
* @param usedNames - Set of already used worksheet names to avoid duplicates
|
|
115
80
|
*/
|
|
116
|
-
|
|
81
|
+
convertPageToWorksheet(workbook, page, tree, usedNames = new Set()) {
|
|
117
82
|
// Create worksheet with page name (sanitized for Excel and unique)
|
|
118
83
|
const worksheetName = this.getUniqueWorksheetName(page.name || page.id, usedNames);
|
|
119
84
|
const worksheet = workbook.addWorksheet(worksheetName);
|
|
@@ -122,16 +87,16 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
122
87
|
// Add navigation row if enabled (optional feature)
|
|
123
88
|
let startRow = 1;
|
|
124
89
|
if (this.shouldAddNavigationRow()) {
|
|
125
|
-
|
|
90
|
+
this.addNavigationRow(worksheet, page, tree);
|
|
126
91
|
startRow = 2; // Start content after navigation row
|
|
127
92
|
}
|
|
128
93
|
// Convert grid layout if available
|
|
129
94
|
if (page.grid && page.grid.length > 0) {
|
|
130
|
-
|
|
95
|
+
this.convertGridLayout(worksheet, page.grid, startRow);
|
|
131
96
|
}
|
|
132
97
|
else {
|
|
133
98
|
// Convert button list to grid layout
|
|
134
|
-
|
|
99
|
+
this.convertButtonsToGrid(worksheet, page.buttons, rows, cols, startRow);
|
|
135
100
|
}
|
|
136
101
|
// Apply worksheet formatting
|
|
137
102
|
this.formatWorksheet(worksheet, rows, cols, startRow);
|
|
@@ -165,14 +130,14 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
165
130
|
* @param grid - 2D array of AAC buttons
|
|
166
131
|
* @param startRow - Starting row number
|
|
167
132
|
*/
|
|
168
|
-
|
|
133
|
+
convertGridLayout(worksheet, grid, startRow) {
|
|
169
134
|
for (let row = 0; row < grid.length; row++) {
|
|
170
135
|
for (let col = 0; col < grid[row].length; col++) {
|
|
171
136
|
const button = grid[row][col];
|
|
172
137
|
if (button) {
|
|
173
138
|
const excelRow = startRow + row;
|
|
174
139
|
const excelCol = col + 1; // Excel columns are 1-based
|
|
175
|
-
|
|
140
|
+
this.setButtonCell(worksheet, button, excelRow, excelCol);
|
|
176
141
|
}
|
|
177
142
|
}
|
|
178
143
|
}
|
|
@@ -185,7 +150,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
185
150
|
* @param cols - Number of columns in grid
|
|
186
151
|
* @param startRow - Starting row number
|
|
187
152
|
*/
|
|
188
|
-
|
|
153
|
+
convertButtonsToGrid(worksheet, buttons, rows, cols, startRow) {
|
|
189
154
|
for (let i = 0; i < buttons.length; i++) {
|
|
190
155
|
const button = buttons[i];
|
|
191
156
|
const row = Math.floor(i / cols);
|
|
@@ -193,7 +158,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
193
158
|
if (row < rows) {
|
|
194
159
|
const excelRow = startRow + row;
|
|
195
160
|
const excelCol = col + 1; // Excel columns are 1-based
|
|
196
|
-
|
|
161
|
+
this.setButtonCell(worksheet, button, excelRow, excelCol);
|
|
197
162
|
}
|
|
198
163
|
}
|
|
199
164
|
}
|
|
@@ -204,7 +169,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
204
169
|
* @param row - Excel row number
|
|
205
170
|
* @param col - Excel column number
|
|
206
171
|
*/
|
|
207
|
-
|
|
172
|
+
setButtonCell(worksheet, button, row, col) {
|
|
208
173
|
const cell = worksheet.getCell(row, col);
|
|
209
174
|
// Set cell value to button label
|
|
210
175
|
cell.value = button.label || '';
|
|
@@ -217,7 +182,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
217
182
|
this.applyCellStyling(cell, button.style);
|
|
218
183
|
}
|
|
219
184
|
// Add navigation link if this is a navigation button
|
|
220
|
-
if (button.semanticAction?.intent ===
|
|
185
|
+
if (button.semanticAction?.intent === treeStructure_1.AACSemanticIntent.NAVIGATE_TO && button.targetPageId) {
|
|
221
186
|
this.addNavigationLink(cell, button.targetPageId);
|
|
222
187
|
}
|
|
223
188
|
// Set cell size for better visibility
|
|
@@ -229,14 +194,16 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
229
194
|
* @param style - AAC style object
|
|
230
195
|
*/
|
|
231
196
|
applyCellStyling(cell, style) {
|
|
232
|
-
|
|
197
|
+
let fill;
|
|
233
198
|
const font = {};
|
|
234
|
-
|
|
199
|
+
let border;
|
|
235
200
|
// Background color
|
|
236
201
|
if (style.backgroundColor) {
|
|
237
|
-
fill
|
|
238
|
-
|
|
239
|
-
|
|
202
|
+
fill = {
|
|
203
|
+
type: 'pattern',
|
|
204
|
+
pattern: 'solid',
|
|
205
|
+
fgColor: { argb: this.convertColorToArgb(style.backgroundColor) },
|
|
206
|
+
};
|
|
240
207
|
}
|
|
241
208
|
// Font color
|
|
242
209
|
if (style.fontColor) {
|
|
@@ -263,24 +230,27 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
263
230
|
font.underline = true;
|
|
264
231
|
}
|
|
265
232
|
// Border
|
|
266
|
-
if (style.borderColor || style.borderWidth) {
|
|
267
|
-
const
|
|
233
|
+
if (style.borderColor || typeof style.borderWidth === 'number') {
|
|
234
|
+
const borderWidth = style.borderWidth ?? 1;
|
|
235
|
+
const borderStyle = borderWidth > 1 ? 'thick' : 'thin';
|
|
268
236
|
const borderColor = style.borderColor
|
|
269
237
|
? { argb: this.convertColorToArgb(style.borderColor) }
|
|
270
238
|
: { argb: 'FF000000' }; // Default black
|
|
271
|
-
border
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
239
|
+
border = {
|
|
240
|
+
top: { style: borderStyle, color: borderColor },
|
|
241
|
+
left: { style: borderStyle, color: borderColor },
|
|
242
|
+
bottom: { style: borderStyle, color: borderColor },
|
|
243
|
+
right: { style: borderStyle, color: borderColor },
|
|
244
|
+
};
|
|
275
245
|
}
|
|
276
246
|
// Apply styling to cell
|
|
277
|
-
if (
|
|
247
|
+
if (fill) {
|
|
278
248
|
cell.fill = fill;
|
|
279
249
|
}
|
|
280
250
|
if (Object.keys(font).length > 0) {
|
|
281
251
|
cell.font = font;
|
|
282
252
|
}
|
|
283
|
-
if (
|
|
253
|
+
if (border) {
|
|
284
254
|
cell.border = border;
|
|
285
255
|
}
|
|
286
256
|
// Center align text
|
|
@@ -369,7 +339,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
369
339
|
* @param page - Current AAC page
|
|
370
340
|
* @param tree - Full AAC tree for navigation context
|
|
371
341
|
*/
|
|
372
|
-
|
|
342
|
+
addNavigationRow(worksheet, page, tree) {
|
|
373
343
|
const navButtons = ExcelProcessor.NAVIGATION_BUTTONS;
|
|
374
344
|
for (let i = 0; i < navButtons.length; i++) {
|
|
375
345
|
const cell = worksheet.getCell(1, i + 1);
|
|
@@ -440,9 +410,9 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
440
410
|
// - Max 31 characters
|
|
441
411
|
// - Cannot contain: \ / ? * [ ] :
|
|
442
412
|
// - Cannot be empty
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
413
|
+
let cleaned = (name || '').replace(/[\\/?*:]/g, '_');
|
|
414
|
+
cleaned = cleaned.replace(/\[/g, '_').replace(/\]/g, '_');
|
|
415
|
+
cleaned = cleaned.substring(0, 31);
|
|
446
416
|
if (cleaned.length === 0) {
|
|
447
417
|
return 'Sheet1';
|
|
448
418
|
}
|
|
@@ -499,11 +469,12 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
499
469
|
await this.saveFromTreeAsync(tree, outputPath);
|
|
500
470
|
}
|
|
501
471
|
catch (error) {
|
|
502
|
-
|
|
472
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
473
|
+
console.error('Failed to save Excel file:', message);
|
|
503
474
|
try {
|
|
504
475
|
const fallbackPath = outputPath.replace(/\.xlsx$/i, '_error.txt');
|
|
505
476
|
fs_1.default.mkdirSync(path_1.default.dirname(fallbackPath), { recursive: true });
|
|
506
|
-
fs_1.default.writeFileSync(fallbackPath, `Error saving Excel file: ${
|
|
477
|
+
fs_1.default.writeFileSync(fallbackPath, `Error saving Excel file: ${message}`);
|
|
507
478
|
}
|
|
508
479
|
catch (writeError) {
|
|
509
480
|
console.error('Failed to write Excel error file:', writeError);
|
|
@@ -532,7 +503,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
532
503
|
// Convert each AAC page to an Excel worksheet
|
|
533
504
|
for (const pageId in tree.pages) {
|
|
534
505
|
const page = tree.pages[pageId];
|
|
535
|
-
|
|
506
|
+
this.convertPageToWorksheet(workbook, page, tree, usedNames);
|
|
536
507
|
}
|
|
537
508
|
// Save the workbook
|
|
538
509
|
await workbook.xlsx.writeFile(outputPath);
|
|
@@ -49,7 +49,7 @@ function createWordlist(input) {
|
|
|
49
49
|
}
|
|
50
50
|
else if (typeof input === 'object') {
|
|
51
51
|
// Handle dictionary/object input
|
|
52
|
-
items = Object.entries(input).map(([
|
|
52
|
+
items = Object.entries(input).map(([, value]) => {
|
|
53
53
|
if (typeof value === 'string') {
|
|
54
54
|
return { text: value };
|
|
55
55
|
}
|
|
@@ -222,7 +222,8 @@ function updateWordlist(gridsetBuffer, gridName, wordlist) {
|
|
|
222
222
|
found = true;
|
|
223
223
|
}
|
|
224
224
|
catch (error) {
|
|
225
|
-
|
|
225
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
226
|
+
throw new Error(`Failed to update wordlist in grid "${gridName}": ${message}`);
|
|
226
227
|
}
|
|
227
228
|
}
|
|
228
229
|
}
|
|
@@ -56,7 +56,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
56
56
|
r: text,
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
|
-
r: {
|
|
59
|
+
r: { __cdata: ' ' },
|
|
60
60
|
},
|
|
61
61
|
],
|
|
62
62
|
},
|
|
@@ -131,92 +131,85 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
131
131
|
},
|
|
132
132
|
};
|
|
133
133
|
case 'SPEAK_TEXT':
|
|
134
|
-
case 'SPEAK_IMMEDIATE':
|
|
135
|
-
// For communication buttons, insert text into message bar (sentence building)
|
|
136
|
-
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
{
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
{
|
|
156
|
-
r: { '__cdata': ' ' },
|
|
157
|
-
},
|
|
158
|
-
],
|
|
159
|
-
},
|
|
134
|
+
case 'SPEAK_IMMEDIATE': {
|
|
135
|
+
// Users can speak the complete sentence with a dedicated Speak button // Use two <s> elements: one for the word, one for the space (CDATA preserves whitespace) // Grid3 requires explicit trailing space for automatic word spacing // For communication buttons, insert text into message bar (sentence building)
|
|
136
|
+
let text = semanticAction.text || button.message || button.label || '';
|
|
137
|
+
// Remove trailing space from message if present (we'll add it as separate segment)
|
|
138
|
+
if (text.endsWith(' ')) {
|
|
139
|
+
text = text.slice(0, -1);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
Command: {
|
|
143
|
+
'@_ID': 'Action.InsertText',
|
|
144
|
+
Parameter: {
|
|
145
|
+
'@_Key': 'text',
|
|
146
|
+
p: {
|
|
147
|
+
s: [
|
|
148
|
+
{
|
|
149
|
+
r: text,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
r: { __cdata: ' ' },
|
|
153
|
+
},
|
|
154
|
+
],
|
|
160
155
|
},
|
|
161
156
|
},
|
|
162
|
-
}
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
case 'INSERT_TEXT': {
|
|
161
|
+
// Use two <s> elements: one for the word, one for the space (CDATA preserves whitespace) // Add trailing space for word buttons to enable sentence building
|
|
162
|
+
let text = semanticAction.text || button.message || button.label || '';
|
|
163
|
+
// Remove trailing space from message if present (we'll add it as separate segment)
|
|
164
|
+
if (text.endsWith(' ')) {
|
|
165
|
+
text = text.slice(0, -1);
|
|
163
166
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
p: {
|
|
179
|
-
s: [
|
|
180
|
-
{
|
|
181
|
-
r: text,
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
r: { '__cdata': ' ' },
|
|
185
|
-
},
|
|
186
|
-
],
|
|
187
|
-
},
|
|
167
|
+
return {
|
|
168
|
+
Command: {
|
|
169
|
+
'@_ID': 'Action.InsertText',
|
|
170
|
+
Parameter: {
|
|
171
|
+
'@_Key': 'text',
|
|
172
|
+
p: {
|
|
173
|
+
s: [
|
|
174
|
+
{
|
|
175
|
+
r: text,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
r: { __cdata: ' ' },
|
|
179
|
+
},
|
|
180
|
+
],
|
|
188
181
|
},
|
|
189
182
|
},
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
default: {
|
|
194
187
|
// Use two <s> elements: one for the word, one for the space (CDATA preserves whitespace)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
},
|
|
188
|
+
// Fallback to insert text with structured XML format
|
|
189
|
+
let text = semanticAction.text || button.message || button.label || '';
|
|
190
|
+
// Remove trailing space from message if present (we'll add it as separate segment)
|
|
191
|
+
if (text.endsWith(' ')) {
|
|
192
|
+
text = text.slice(0, -1);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
Command: {
|
|
196
|
+
'@_ID': 'Action.InsertText',
|
|
197
|
+
Parameter: {
|
|
198
|
+
'@_Key': 'text',
|
|
199
|
+
p: {
|
|
200
|
+
s: [
|
|
201
|
+
{
|
|
202
|
+
r: text,
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
r: { __cdata: ' ' },
|
|
206
|
+
},
|
|
207
|
+
],
|
|
216
208
|
},
|
|
217
209
|
},
|
|
218
|
-
}
|
|
219
|
-
}
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
220
213
|
}
|
|
221
214
|
}
|
|
222
215
|
// Helper function to convert Grid 3 style to AACStyle
|
|
@@ -288,10 +281,8 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
288
281
|
if (entries) {
|
|
289
282
|
const arr = Array.isArray(entries) ? entries : [entries];
|
|
290
283
|
for (const ent of arr) {
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
ent.staticFile ||
|
|
294
|
-
'').replace(/\\/g, '/');
|
|
284
|
+
const rawStaticFile = ent['@_StaticFile'] || ent.StaticFile || ent.staticFile;
|
|
285
|
+
const staticFile = typeof rawStaticFile === 'string' ? rawStaticFile.replace(/\\/g, '/') : '';
|
|
295
286
|
if (!staticFile)
|
|
296
287
|
continue;
|
|
297
288
|
const df = ent.DynamicFiles || ent.dynamicFiles;
|
|
@@ -987,26 +978,22 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
987
978
|
const buttonImages = new Map();
|
|
988
979
|
// Helper function to add style and return its ID
|
|
989
980
|
const addStyle = (style) => {
|
|
990
|
-
if (!style
|
|
991
|
-
return '';
|
|
992
|
-
const obj = style;
|
|
993
|
-
if (Object.keys(obj).length === 0)
|
|
981
|
+
if (!style)
|
|
994
982
|
return '';
|
|
995
|
-
const
|
|
983
|
+
const normalizedStyle = { ...style };
|
|
984
|
+
const styleKey = JSON.stringify(normalizedStyle);
|
|
996
985
|
const existing = uniqueStyles.get(styleKey);
|
|
997
986
|
if (existing)
|
|
998
987
|
return existing.id;
|
|
999
988
|
const styleId = `Style${styleIdCounter++}`;
|
|
1000
|
-
uniqueStyles.set(styleKey, { id: styleId, style:
|
|
989
|
+
uniqueStyles.set(styleKey, { id: styleId, style: normalizedStyle });
|
|
1001
990
|
return styleId;
|
|
1002
991
|
};
|
|
1003
992
|
// Collect styles from all pages and buttons
|
|
1004
993
|
Object.values(tree.pages).forEach((page) => {
|
|
1005
|
-
|
|
1006
|
-
addStyle(page.style);
|
|
994
|
+
addStyle(page.style);
|
|
1007
995
|
page.buttons.forEach((button) => {
|
|
1008
|
-
|
|
1009
|
-
addStyle(button.style);
|
|
996
|
+
addStyle(button.style);
|
|
1010
997
|
});
|
|
1011
998
|
});
|
|
1012
999
|
// Get the home/start grid from tree.rootId, fallback to first page
|
|
@@ -1053,8 +1040,8 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1053
1040
|
// When TileColour is present, BackColour is the surround (outer area)
|
|
1054
1041
|
// For "None" surround, just use BackColour for the fill (no TileColour)
|
|
1055
1042
|
BackColour: this.ensureAlphaChannel(style.backgroundColor),
|
|
1056
|
-
BorderColour: this.ensureAlphaChannel(style.borderColor)
|
|
1057
|
-
FontColour: this.ensureAlphaChannel(style.fontColor)
|
|
1043
|
+
BorderColour: this.ensureAlphaChannel(style.borderColor),
|
|
1044
|
+
FontColour: this.ensureAlphaChannel(style.fontColor),
|
|
1058
1045
|
FontName: style.fontFamily || 'Arial',
|
|
1059
1046
|
FontSize: style.fontSize?.toString() || '16',
|
|
1060
1047
|
};
|
|
@@ -1081,7 +1068,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1081
1068
|
// Collect grid file paths for FileMap.xml
|
|
1082
1069
|
const gridFilePaths = [];
|
|
1083
1070
|
// Create a grid for each page
|
|
1084
|
-
Object.values(tree.pages).forEach((page
|
|
1071
|
+
Object.values(tree.pages).forEach((page) => {
|
|
1085
1072
|
const gridData = {
|
|
1086
1073
|
Grid: {
|
|
1087
1074
|
'@_xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
@@ -1121,8 +1108,9 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1121
1108
|
if (button.image) {
|
|
1122
1109
|
// Try to determine file extension from image name or default to PNG
|
|
1123
1110
|
let imageExt = 'png';
|
|
1124
|
-
|
|
1125
|
-
|
|
1111
|
+
const imageMatch = button.image.match(/\.(png|jpg|jpeg|gif|svg)$/i);
|
|
1112
|
+
if (imageMatch) {
|
|
1113
|
+
imageExt = imageMatch[1].toLowerCase();
|
|
1126
1114
|
}
|
|
1127
1115
|
// Grid3 dynamically constructs image filenames by prepending cell coordinates
|
|
1128
1116
|
// The XML should only contain the suffix: -0-text-0.{ext}
|
|
@@ -1131,7 +1119,9 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1131
1119
|
// Extract image data from button parameters if available
|
|
1132
1120
|
// (AstericsGridProcessor stores it there during loadIntoTree)
|
|
1133
1121
|
let imageData = Buffer.alloc(0);
|
|
1134
|
-
if (button.parameters &&
|
|
1122
|
+
if (button.parameters &&
|
|
1123
|
+
button.parameters.imageData &&
|
|
1124
|
+
Buffer.isBuffer(button.parameters.imageData)) {
|
|
1135
1125
|
imageData = button.parameters.imageData;
|
|
1136
1126
|
}
|
|
1137
1127
|
// Store image data for later writing to ZIP
|
|
@@ -1182,7 +1172,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1182
1172
|
}
|
|
1183
1173
|
return cellData;
|
|
1184
1174
|
}),
|
|
1185
|
-
]
|
|
1175
|
+
],
|
|
1186
1176
|
}
|
|
1187
1177
|
: { Cell: [] },
|
|
1188
1178
|
},
|
|
@@ -1202,7 +1192,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1202
1192
|
zip.addFile(gridPath, Buffer.from(xmlContent, 'utf8'));
|
|
1203
1193
|
});
|
|
1204
1194
|
// Write image files to ZIP
|
|
1205
|
-
buttonImages.forEach((imgData
|
|
1195
|
+
buttonImages.forEach((imgData) => {
|
|
1206
1196
|
if (imgData.imageData && imgData.imageData.length > 0) {
|
|
1207
1197
|
// Create image path in the grid's directory
|
|
1208
1198
|
const imagePath = `Grids\\${imgData.pageName}\\${imgData.x}-${imgData.y}-0-text-0.${imgData.ext}`;
|
|
@@ -1221,7 +1211,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1221
1211
|
const imageFiles = [];
|
|
1222
1212
|
// Collect image filenames for buttons on this page
|
|
1223
1213
|
// IMPORTANT: FileMap.xml requires full paths like "Grids\PageName\1-5-0-text-0.png"
|
|
1224
|
-
buttonImages.forEach((imgData
|
|
1214
|
+
buttonImages.forEach((imgData) => {
|
|
1225
1215
|
if (imgData.pageName === gridName && imgData.imageData.length > 0) {
|
|
1226
1216
|
const imagePath = `Grids\\${gridName}\\${imgData.x}-${imgData.y}-0-text-0.${imgData.ext}`;
|
|
1227
1217
|
imageFiles.push(imagePath);
|
|
@@ -1229,9 +1219,11 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1229
1219
|
});
|
|
1230
1220
|
return {
|
|
1231
1221
|
'@_StaticFile': gridPath,
|
|
1232
|
-
DynamicFiles: imageFiles.length > 0
|
|
1233
|
-
|
|
1234
|
-
|
|
1222
|
+
DynamicFiles: imageFiles.length > 0
|
|
1223
|
+
? {
|
|
1224
|
+
File: imageFiles,
|
|
1225
|
+
}
|
|
1226
|
+
: {},
|
|
1235
1227
|
};
|
|
1236
1228
|
}),
|
|
1237
1229
|
},
|
|
@@ -5,6 +5,8 @@ declare class ObfProcessor extends BaseProcessor {
|
|
|
5
5
|
private processBoard;
|
|
6
6
|
extractTexts(filePathOrBuffer: string | Buffer): string[];
|
|
7
7
|
loadIntoTree(filePathOrBuffer: string | Buffer): AACTree;
|
|
8
|
+
private buildGridMetadata;
|
|
9
|
+
private createObfBoardFromPage;
|
|
8
10
|
processTexts(filePathOrBuffer: string | Buffer, translations: Map<string, string>, outputPath: string): Buffer;
|
|
9
11
|
saveFromTree(tree: AACTree, outputPath: string): void;
|
|
10
12
|
/**
|