@willwade/aac-processors 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -37
- 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/dotProcessor.js +31 -12
- package/dist/processors/excelProcessor.d.ts +3 -3
- package/dist/processors/excelProcessor.js +48 -77
- package/dist/processors/gridset/colorUtils.d.ts +69 -0
- package/dist/processors/gridset/colorUtils.js +331 -0
- package/dist/processors/gridset/helpers.d.ts +30 -0
- package/dist/processors/gridset/helpers.js +69 -0
- package/dist/processors/gridset/styleHelpers.d.ts +3 -4
- package/dist/processors/gridset/styleHelpers.js +10 -44
- package/dist/processors/gridset/wordlistHelpers.js +3 -2
- package/dist/processors/gridsetProcessor.js +97 -105
- package/dist/processors/index.d.ts +5 -3
- package/dist/processors/index.js +21 -2
- package/dist/processors/obfProcessor.d.ts +2 -0
- package/dist/processors/obfProcessor.js +133 -53
- package/dist/processors/opmlProcessor.js +13 -3
- package/dist/processors/snapProcessor.js +45 -15
- package/dist/processors/touchchatProcessor.js +45 -22
- package/dist/types/aac.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -658,13 +658,8 @@ npm test
|
|
|
658
658
|
|
|
659
659
|
- The repository keeps `package.json` at `0.0.0-development`; release tags control the published version.
|
|
660
660
|
- Create a GitHub release with a semantic tag (e.g. `v2.1.0`). Publishing only runs for non-prerelease tags.
|
|
661
|
-
- Add an `NPM_TOKEN` repository secret with publish rights. The release workflow uses it to authenticate and calls `npm publish`.
|
|
662
661
|
- The workflow (`.github/workflows/publish.yml`) automatically installs dependencies, rewrites the package version from the tag, and runs the standard publish pipeline.
|
|
663
662
|
|
|
664
|
-
**Private distribution options**
|
|
665
|
-
- Unscoped packages on npm must be public. To keep this package private, re-scope it (e.g. `@your-org/aac-processors`) and configure `publishConfig.access: "restricted"`—this requires an npm org with paid seats.
|
|
666
|
-
- Alternatively, publish to GitHub Packages by adjusting the workflow’s registry URL and using a `GITHUB_TOKEN` with the `packages: write` permission.
|
|
667
|
-
|
|
668
663
|
---
|
|
669
664
|
|
|
670
665
|
## 📄 License
|
|
@@ -715,15 +710,6 @@ Inspired by the Python AACProcessors project
|
|
|
715
710
|
- [ ] **Add Symbol Tools coverage** (currently 0%) - Implement tests for PCS and ARASAAC symbol lookups to reach >70% coverage
|
|
716
711
|
- [ ] **Fix property-based test failures** - Resolve TypeScript interface compatibility issues in edge case generators
|
|
717
712
|
|
|
718
|
-
### Recently Completed ✅
|
|
719
|
-
|
|
720
|
-
- [x] **Core utilities test coverage** - Complete implementation for analyze.ts and fileProcessor.ts (0% → 100% coverage, 45 comprehensive tests, src/core/ directory 30% → 83% coverage)
|
|
721
|
-
- [x] **CLI test infrastructure** - Fixed DotProcessor parsing and test expectations (0 → 25 passing tests, 100% CLI functionality)
|
|
722
|
-
- [x] **Critical linting fixes** - Resolved unsafe argument types and CLI type safety issues (177 → 123 errors, 32% improvement)
|
|
723
|
-
- [x] **Audio test syntax fixes** - Fixed non-null assertion errors in audio tests (21 → 5 failing tests, 76% improvement)
|
|
724
|
-
- [x] **Comprehensive styling support** - Complete implementation across all AAC formats with cross-format preservation (Added: AACStyle interface, enhanced all processors, 7 new test cases, complete documentation)
|
|
725
|
-
- [x] **TouchChatProcessor save/load functionality** - Fixed button persistence and ID mapping (coverage improved from 57.62% to 86.44%)
|
|
726
|
-
- [x] **Build integration** - Ensure `npm run build` is executed before CLI tests (Fixed: All test scripts now automatically build before running)
|
|
727
713
|
|
|
728
714
|
### Medium Priority
|
|
729
715
|
|
|
@@ -749,29 +735,6 @@ Inspired by the Python AACProcessors project
|
|
|
749
735
|
- [ ] **CI/CD improvements** - Add automated releases and npm publishing
|
|
750
736
|
- [ ] **Documentation improvements** - Add more real-world examples and tutorials
|
|
751
737
|
|
|
752
|
-
### Known Issues
|
|
753
|
-
|
|
754
|
-
- ⚠️ **Audio Persistence**: 5 functional tests failing due to audio recording not persisting through SnapProcessor save/load cycle
|
|
755
|
-
- ⚠️ **Grid3 Layout**: Grid sizes not reliable and X,Y positions incorrect, particularly affecting Grid3 processor functionality
|
|
756
|
-
- ⚠️ **Database Constraints**: UNIQUE constraint violations with real-world data files (Page.Id and buttons.id conflicts)
|
|
757
|
-
- ⚠️ **Linting**: 123 ESLint issues remaining (mostly in test files, reduced from 177)
|
|
758
|
-
- ⚠️ **SnapProcessor**: Lowest coverage at 48.32%, missing comprehensive audio handling tests
|
|
759
|
-
- ⚠️ **Memory usage**: Large files (>50MB) may cause memory pressure
|
|
760
|
-
- ⚠️ **Concurrent access**: Some processors not fully thread-safe for simultaneous writes
|
|
761
|
-
|
|
762
|
-
### 🧪 Current Test Status & Immediate Follow-Up
|
|
763
|
-
|
|
764
|
-
As of the latest run (`npm test`), **47 suites pass / 6 fail (10 individual tests)**. Remaining blockers are:
|
|
765
|
-
|
|
766
|
-
1. **Edge-case loaders** – corrupted JSON/XML fixtures still expect explicit exceptions. Decide whether to restore the old throwing behaviour (Asterics/OPML/DOT) or update the tests to accept the softer error reporting.
|
|
767
|
-
2. **Gridset exports & styling** – round-trip continues to lose a button and the styling suite cannot find `style.xml`. GridsetProcessor needs to preserve button arrays and emit the styling assets Grid 3 expects.
|
|
768
|
-
3. **DOT navigation semantics** – the deterministic DOT test still falls back to `SPEAK`. Improve semantic reconstruction when loading navigation edges so navigation buttons survive round-trips.
|
|
769
|
-
4. **Advanced workflow scenario** – the multi-format pipeline still loses Spanish translations (likely during the Gridset ⇄ Snap steps); trace text propagation and patch the conversion chain.
|
|
770
|
-
5. **Styling suite** – Grid 3 export still lacks `style.xml`; ensure the styling assets are generated alongside the gridset payload.
|
|
771
|
-
6. **Memory comparison suite** – memory delta expectations are still unmet (TouchChat GC + DOT vs others). Either recalibrate the harness or tune the processors before re-enabling the assertions.
|
|
772
|
-
|
|
773
|
-
Clearing these items will put the test matrix back in the green and unblock the release.
|
|
774
|
-
|
|
775
738
|
## More enhancements
|
|
776
739
|
|
|
777
740
|
- Much more effort put into fixing the layout issues. Grid sizes are not reliably and X, Y positions too. Particularly in the Grid3
|
package/dist/cli/index.js
CHANGED
|
@@ -142,7 +142,7 @@ commander_1.program
|
|
|
142
142
|
.option('--no-exclude-navigation', "Don't exclude navigation buttons (Home, Back)")
|
|
143
143
|
.option('--no-exclude-system', "Don't exclude system buttons (Delete, Clear, etc.)")
|
|
144
144
|
.option('--exclude-buttons <list>', 'Comma-separated list of button labels/terms to exclude')
|
|
145
|
-
.action((input, output, options) => {
|
|
145
|
+
.action(async (input, output, options) => {
|
|
146
146
|
try {
|
|
147
147
|
if (!options.format) {
|
|
148
148
|
console.error('Error: --format option is required for convert command');
|
|
@@ -157,7 +157,7 @@ commander_1.program
|
|
|
157
157
|
const tree = inputProcessor.loadIntoTree(input);
|
|
158
158
|
// Save using output format with same filtering options
|
|
159
159
|
const outputProcessor = (0, analyze_1.getProcessor)(options.format, filteringOptions);
|
|
160
|
-
outputProcessor.saveFromTree(tree, output);
|
|
160
|
+
await outputProcessor.saveFromTree(tree, output);
|
|
161
161
|
// Show filtering summary
|
|
162
162
|
let filteringSummary = '';
|
|
163
163
|
if (filteringOptions.preserveAllButtons) {
|
|
@@ -163,7 +163,11 @@ export declare class AACPage implements IAACPage {
|
|
|
163
163
|
buttons: AACButton[];
|
|
164
164
|
parentId: string | null;
|
|
165
165
|
style?: AACStyle;
|
|
166
|
-
|
|
166
|
+
locale?: string;
|
|
167
|
+
descriptionHtml?: string;
|
|
168
|
+
images?: any[];
|
|
169
|
+
sounds?: any[];
|
|
170
|
+
constructor({ id, name, grid, buttons, parentId, style, locale, descriptionHtml, images, sounds, }: {
|
|
167
171
|
id: string;
|
|
168
172
|
name?: string;
|
|
169
173
|
grid?: Array<Array<AACButton | null>> | {
|
|
@@ -173,6 +177,10 @@ export declare class AACPage implements IAACPage {
|
|
|
173
177
|
buttons?: AACButton[];
|
|
174
178
|
parentId?: string | null;
|
|
175
179
|
style?: AACStyle;
|
|
180
|
+
locale?: string;
|
|
181
|
+
descriptionHtml?: string;
|
|
182
|
+
images?: any[];
|
|
183
|
+
sounds?: any[];
|
|
176
184
|
});
|
|
177
185
|
addButton(button: AACButton): void;
|
|
178
186
|
}
|
|
@@ -152,7 +152,7 @@ class AACButton {
|
|
|
152
152
|
}
|
|
153
153
|
exports.AACButton = AACButton;
|
|
154
154
|
class AACPage {
|
|
155
|
-
constructor({ id, name = '', grid = [], buttons = [], parentId = null, style, }) {
|
|
155
|
+
constructor({ id, name = '', grid = [], buttons = [], parentId = null, style, locale, descriptionHtml, images, sounds, }) {
|
|
156
156
|
this.id = id;
|
|
157
157
|
this.name = name;
|
|
158
158
|
if (Array.isArray(grid)) {
|
|
@@ -169,6 +169,10 @@ class AACPage {
|
|
|
169
169
|
this.buttons = buttons;
|
|
170
170
|
this.parentId = parentId;
|
|
171
171
|
this.style = style;
|
|
172
|
+
this.locale = locale;
|
|
173
|
+
this.descriptionHtml = descriptionHtml;
|
|
174
|
+
this.images = images;
|
|
175
|
+
this.sounds = sounds;
|
|
172
176
|
}
|
|
173
177
|
addButton(button) {
|
|
174
178
|
this.buttons.push(button);
|
|
@@ -10,6 +10,45 @@ const treeStructure_1 = require("../core/treeStructure");
|
|
|
10
10
|
const plist_1 = __importDefault(require("plist"));
|
|
11
11
|
const fs_1 = __importDefault(require("fs"));
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
|
+
function isNormalizedPanel(panel) {
|
|
14
|
+
return typeof panel.id === 'string';
|
|
15
|
+
}
|
|
16
|
+
function normalizePanel(panel, fallbackId) {
|
|
17
|
+
const rawId = panel.ID || fallbackId;
|
|
18
|
+
const buttons = Array.isArray(panel.PanelObjects)
|
|
19
|
+
? panel.PanelObjects.filter((obj) => obj.PanelObjectType === 'Button')
|
|
20
|
+
: [];
|
|
21
|
+
const normalizedButtons = buttons.map((btn) => {
|
|
22
|
+
const firstAction = Array.isArray(btn.Actions) && btn.Actions.length > 0 ? btn.Actions[0] : undefined;
|
|
23
|
+
const isCharSequence = firstAction &&
|
|
24
|
+
(firstAction.ActionType === 'ActionPressKeyCharSequence' ||
|
|
25
|
+
firstAction.ActionType === 'ActionSendKeys');
|
|
26
|
+
const charString = isCharSequence ? firstAction?.ActionParam?.CharString : undefined;
|
|
27
|
+
const targetPanel = firstAction && firstAction.ActionType === 'ActionOpenPanel'
|
|
28
|
+
? firstAction.ActionParam?.PanelID?.replace(/^USER\./, '')
|
|
29
|
+
: undefined;
|
|
30
|
+
return {
|
|
31
|
+
label: btn.DisplayText || 'Button',
|
|
32
|
+
message: charString || btn.DisplayText || 'Button',
|
|
33
|
+
DisplayColor: btn.DisplayColor,
|
|
34
|
+
DisplayImageWeight: btn.DisplayImageWeight,
|
|
35
|
+
FontSize: btn.FontSize,
|
|
36
|
+
Rect: btn.Rect,
|
|
37
|
+
targetPanel,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
id: rawId.replace(/^USER\./, ''),
|
|
42
|
+
name: panel.Name || 'Panel',
|
|
43
|
+
buttons: normalizedButtons,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function normalizeActionParameters(input) {
|
|
47
|
+
if (typeof input === 'object' && input !== null) {
|
|
48
|
+
return { ...input };
|
|
49
|
+
}
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
13
52
|
class ApplePanelsProcessor extends baseProcessor_1.BaseProcessor {
|
|
14
53
|
constructor(options) {
|
|
15
54
|
super(options);
|
|
@@ -81,41 +120,21 @@ class ApplePanelsProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
81
120
|
// Handle both old format (panels array) and new Apple Panels format (Panels dict)
|
|
82
121
|
let panelsData = [];
|
|
83
122
|
if (Array.isArray(parsedData.panels)) {
|
|
84
|
-
|
|
85
|
-
|
|
123
|
+
panelsData = parsedData.panels.map((panel, index) => {
|
|
124
|
+
if (isNormalizedPanel(panel)) {
|
|
125
|
+
return panel;
|
|
126
|
+
}
|
|
127
|
+
const panelData = panel || {
|
|
128
|
+
PanelObjects: [],
|
|
129
|
+
};
|
|
130
|
+
return normalizePanel(panelData, `panel_${index}`);
|
|
131
|
+
});
|
|
86
132
|
}
|
|
87
133
|
else if (parsedData.Panels) {
|
|
88
|
-
// Apple Panels format: convert Panels dict to array
|
|
89
134
|
const panelsDict = parsedData.Panels;
|
|
90
135
|
panelsData = Object.keys(panelsDict).map((panelId) => {
|
|
91
|
-
const
|
|
92
|
-
return
|
|
93
|
-
id: (panel.ID || panelId).replace(/^USER\./, ''), // Strip USER. prefix to maintain original IDs
|
|
94
|
-
name: panel.Name || 'Panel',
|
|
95
|
-
buttons: (panel.PanelObjects || [])
|
|
96
|
-
.filter((obj) => obj.PanelObjectType === 'Button')
|
|
97
|
-
.map((btn) => {
|
|
98
|
-
const firstAction = Array.isArray(btn.Actions) && btn.Actions.length > 0 ? btn.Actions[0] : undefined;
|
|
99
|
-
const isCharSequence = firstAction &&
|
|
100
|
-
(firstAction.ActionType === 'ActionPressKeyCharSequence' ||
|
|
101
|
-
firstAction.ActionType === 'ActionSendKeys');
|
|
102
|
-
const charString = isCharSequence
|
|
103
|
-
? (firstAction.ActionParam?.CharString ?? undefined)
|
|
104
|
-
: undefined;
|
|
105
|
-
const targetPanel = firstAction && firstAction.ActionType === 'ActionOpenPanel'
|
|
106
|
-
? firstAction.ActionParam?.PanelID?.replace(/^USER\./, '')
|
|
107
|
-
: undefined;
|
|
108
|
-
return {
|
|
109
|
-
label: btn.DisplayText || 'Button',
|
|
110
|
-
message: charString || btn.DisplayText || 'Button',
|
|
111
|
-
DisplayColor: btn.DisplayColor,
|
|
112
|
-
DisplayImageWeight: btn.DisplayImageWeight,
|
|
113
|
-
FontSize: btn.FontSize,
|
|
114
|
-
Rect: btn.Rect,
|
|
115
|
-
targetPanel,
|
|
116
|
-
};
|
|
117
|
-
}),
|
|
118
|
-
};
|
|
136
|
+
const rawPanel = panelsDict[panelId] || { PanelObjects: [] };
|
|
137
|
+
return normalizePanel(rawPanel, panelId);
|
|
119
138
|
});
|
|
120
139
|
}
|
|
121
140
|
const data = { panels: panelsData };
|
|
@@ -219,15 +238,24 @@ class ApplePanelsProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
219
238
|
Object.values(tree.pages).forEach((page) => {
|
|
220
239
|
// Translate page names
|
|
221
240
|
if (page.name && translations.has(page.name)) {
|
|
222
|
-
|
|
241
|
+
const translatedName = translations.get(page.name);
|
|
242
|
+
if (translatedName !== undefined) {
|
|
243
|
+
page.name = translatedName;
|
|
244
|
+
}
|
|
223
245
|
}
|
|
224
246
|
// Translate button labels and messages
|
|
225
247
|
page.buttons.forEach((button) => {
|
|
226
248
|
if (button.label && translations.has(button.label)) {
|
|
227
|
-
|
|
249
|
+
const translatedLabel = translations.get(button.label);
|
|
250
|
+
if (translatedLabel !== undefined) {
|
|
251
|
+
button.label = translatedLabel;
|
|
252
|
+
}
|
|
228
253
|
}
|
|
229
254
|
if (button.message && translations.has(button.message)) {
|
|
230
|
-
|
|
255
|
+
const translatedMessage = translations.get(button.message);
|
|
256
|
+
if (translatedMessage !== undefined) {
|
|
257
|
+
button.message = translatedMessage;
|
|
258
|
+
}
|
|
231
259
|
}
|
|
232
260
|
if (button.semanticAction) {
|
|
233
261
|
const intentStr = String(button.semanticAction.intent);
|
|
@@ -304,10 +332,8 @@ class ApplePanelsProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
304
332
|
const panelId = `USER.${page.id}`;
|
|
305
333
|
// Detect actual grid dimensions from the source data
|
|
306
334
|
let gridCols = 4; // Default fallback
|
|
307
|
-
let gridRows = Math.ceil(page.buttons.length / gridCols);
|
|
308
335
|
if (page.grid && page.grid.length > 0) {
|
|
309
336
|
// Use actual grid dimensions from source
|
|
310
|
-
gridRows = page.grid.length;
|
|
311
337
|
gridCols = page.grid[0] ? page.grid[0].length : 4;
|
|
312
338
|
// Find the actual used area to avoid empty space
|
|
313
339
|
let maxUsedX = 0, maxUsedY = 0;
|
|
@@ -322,7 +348,6 @@ class ApplePanelsProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
322
348
|
// Use the actual used dimensions if they're reasonable
|
|
323
349
|
if (maxUsedX > 0 && maxUsedY > 0) {
|
|
324
350
|
gridCols = maxUsedX + 1;
|
|
325
|
-
gridRows = maxUsedY + 1;
|
|
326
351
|
}
|
|
327
352
|
}
|
|
328
353
|
else {
|
|
@@ -340,7 +365,6 @@ class ApplePanelsProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
340
365
|
else {
|
|
341
366
|
gridCols = 8; // 8 columns for very large sets
|
|
342
367
|
}
|
|
343
|
-
gridRows = Math.ceil(buttonCount / gridCols);
|
|
344
368
|
}
|
|
345
369
|
const panelObjects = page.buttons.map((button, buttonIndex) => {
|
|
346
370
|
// Find button position in grid layout and convert to Rect format
|
|
@@ -379,7 +403,8 @@ class ApplePanelsProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
379
403
|
FontSize: button.style?.fontSize || 12,
|
|
380
404
|
ID: `Button.${button.id}`,
|
|
381
405
|
PanelObjectType: 'Button',
|
|
382
|
-
Rect: rect,
|
|
406
|
+
Rect: rect ?? '{{0, 0}, {100, 25}}',
|
|
407
|
+
Actions: [],
|
|
383
408
|
};
|
|
384
409
|
if (button.style?.backgroundColor) {
|
|
385
410
|
buttonObj.DisplayColor = button.style.backgroundColor;
|
|
@@ -414,8 +439,9 @@ class ApplePanelsProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
414
439
|
UsesPinnedResizing: false,
|
|
415
440
|
};
|
|
416
441
|
});
|
|
442
|
+
const panelsValue = Object.fromEntries(Object.entries(panelsDict).map(([key, value]) => [key, value]));
|
|
417
443
|
const panelDefinitions = {
|
|
418
|
-
Panels:
|
|
444
|
+
Panels: panelsValue,
|
|
419
445
|
ToolbarOrdering: {
|
|
420
446
|
ToolbarIdentifiersAfterBasePanel: [],
|
|
421
447
|
ToolbarIdentifiersPriorToBasePanel: [],
|
|
@@ -439,7 +465,7 @@ class ApplePanelsProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
439
465
|
if (button.semanticAction?.platformData?.applePanels) {
|
|
440
466
|
const applePanelsData = button.semanticAction.platformData.applePanels;
|
|
441
467
|
return {
|
|
442
|
-
ActionParam: applePanelsData.parameters,
|
|
468
|
+
ActionParam: normalizeActionParameters(applePanelsData.parameters),
|
|
443
469
|
ActionRecordedOffset: 0.0,
|
|
444
470
|
ActionType: applePanelsData.actionType,
|
|
445
471
|
ID: `Action.${button.id}`,
|