@willwade/aac-processors 0.0.3

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 (89) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +787 -0
  3. package/dist/cli/index.d.ts +2 -0
  4. package/dist/cli/index.js +189 -0
  5. package/dist/cli/prettyPrint.d.ts +2 -0
  6. package/dist/cli/prettyPrint.js +28 -0
  7. package/dist/core/analyze.d.ts +6 -0
  8. package/dist/core/analyze.js +49 -0
  9. package/dist/core/baseProcessor.d.ts +94 -0
  10. package/dist/core/baseProcessor.js +208 -0
  11. package/dist/core/fileProcessor.d.ts +7 -0
  12. package/dist/core/fileProcessor.js +51 -0
  13. package/dist/core/stringCasing.d.ts +37 -0
  14. package/dist/core/stringCasing.js +174 -0
  15. package/dist/core/treeStructure.d.ts +190 -0
  16. package/dist/core/treeStructure.js +223 -0
  17. package/dist/index.d.ts +23 -0
  18. package/dist/index.js +96 -0
  19. package/dist/optional/symbolTools.d.ts +28 -0
  20. package/dist/optional/symbolTools.js +126 -0
  21. package/dist/processors/applePanelsProcessor.d.ts +23 -0
  22. package/dist/processors/applePanelsProcessor.js +521 -0
  23. package/dist/processors/astericsGridProcessor.d.ts +49 -0
  24. package/dist/processors/astericsGridProcessor.js +1427 -0
  25. package/dist/processors/dotProcessor.d.ts +21 -0
  26. package/dist/processors/dotProcessor.js +191 -0
  27. package/dist/processors/excelProcessor.d.ts +145 -0
  28. package/dist/processors/excelProcessor.js +556 -0
  29. package/dist/processors/gridset/helpers.d.ts +4 -0
  30. package/dist/processors/gridset/helpers.js +48 -0
  31. package/dist/processors/gridset/resolver.d.ts +8 -0
  32. package/dist/processors/gridset/resolver.js +100 -0
  33. package/dist/processors/gridsetProcessor.d.ts +28 -0
  34. package/dist/processors/gridsetProcessor.js +1339 -0
  35. package/dist/processors/index.d.ts +14 -0
  36. package/dist/processors/index.js +42 -0
  37. package/dist/processors/obfProcessor.d.ts +21 -0
  38. package/dist/processors/obfProcessor.js +278 -0
  39. package/dist/processors/opmlProcessor.d.ts +21 -0
  40. package/dist/processors/opmlProcessor.js +235 -0
  41. package/dist/processors/snap/helpers.d.ts +4 -0
  42. package/dist/processors/snap/helpers.js +27 -0
  43. package/dist/processors/snapProcessor.d.ts +44 -0
  44. package/dist/processors/snapProcessor.js +586 -0
  45. package/dist/processors/touchchat/helpers.d.ts +4 -0
  46. package/dist/processors/touchchat/helpers.js +27 -0
  47. package/dist/processors/touchchatProcessor.d.ts +27 -0
  48. package/dist/processors/touchchatProcessor.js +768 -0
  49. package/dist/types/aac.d.ts +47 -0
  50. package/dist/types/aac.js +2 -0
  51. package/docs/.keep +1 -0
  52. package/docs/ApplePanels.md +309 -0
  53. package/docs/Grid3-XML-Format.md +1788 -0
  54. package/docs/TobiiDynavox-Snap-Details.md +394 -0
  55. package/docs/asterics-Grid-fileformat-details.md +443 -0
  56. package/docs/obf_.obz Open Board File Formats.md +432 -0
  57. package/docs/touchchat.md +520 -0
  58. package/examples/.coverage +0 -0
  59. package/examples/.keep +1 -0
  60. package/examples/README.md +31 -0
  61. package/examples/communikate.dot +2637 -0
  62. package/examples/demo.js +143 -0
  63. package/examples/example-images.gridset +0 -0
  64. package/examples/example.ce +0 -0
  65. package/examples/example.dot +14 -0
  66. package/examples/example.grd +1 -0
  67. package/examples/example.gridset +0 -0
  68. package/examples/example.obf +27 -0
  69. package/examples/example.obz +0 -0
  70. package/examples/example.opml +18 -0
  71. package/examples/example.spb +0 -0
  72. package/examples/example.sps +0 -0
  73. package/examples/example2.grd +1 -0
  74. package/examples/gemini_response.txt +845 -0
  75. package/examples/image-map.js +45 -0
  76. package/examples/package-lock.json +1326 -0
  77. package/examples/package.json +10 -0
  78. package/examples/styled-output/converted-snap-to-touchchat.ce +0 -0
  79. package/examples/styled-output/styled-example.ce +0 -0
  80. package/examples/styled-output/styled-example.gridset +0 -0
  81. package/examples/styled-output/styled-example.obf +37 -0
  82. package/examples/styled-output/styled-example.spb +0 -0
  83. package/examples/styling-example.ts +316 -0
  84. package/examples/translate.js +39 -0
  85. package/examples/translate_demo.js +254 -0
  86. package/examples/translation_cache.json +44894 -0
  87. package/examples/typescript-demo.ts +251 -0
  88. package/examples/unified-interface-demo.ts +183 -0
  89. package/package.json +106 -0
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const prettyPrint_1 = require("./prettyPrint");
9
+ const analyze_1 = require("../core/analyze");
10
+ const path_1 = __importDefault(require("path"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ // Helper function to detect format from file/folder path
13
+ function detectFormat(filePath) {
14
+ // Check if it's a folder ending with .ascconfig
15
+ if (fs_1.default.existsSync(filePath) &&
16
+ fs_1.default.statSync(filePath).isDirectory() &&
17
+ filePath.endsWith('.ascconfig')) {
18
+ return 'ascconfig';
19
+ }
20
+ // Otherwise use file extension
21
+ return path_1.default.extname(filePath).slice(1);
22
+ }
23
+ // Helper function to parse filtering options from CLI arguments
24
+ function parseFilteringOptions(options) {
25
+ const processorOptions = {};
26
+ // Handle preserve all buttons flag
27
+ if (options.preserveAllButtons) {
28
+ processorOptions.preserveAllButtons = true;
29
+ return processorOptions; // If preserving all, ignore other options
30
+ }
31
+ // Handle specific exclusion flags
32
+ if (options.excludeNavigation !== undefined) {
33
+ processorOptions.excludeNavigationButtons = options.excludeNavigation;
34
+ }
35
+ if (options.excludeSystem !== undefined) {
36
+ processorOptions.excludeSystemButtons = options.excludeSystem;
37
+ }
38
+ // Handle custom button exclusion list
39
+ if (options.excludeButtons) {
40
+ const excludeList = options.excludeButtons
41
+ .split(',')
42
+ .map((s) => s.trim().toLowerCase())
43
+ .filter((s) => s.length > 0);
44
+ if (excludeList.length > 0) {
45
+ processorOptions.customButtonFilter = (button) => {
46
+ const label = button.label?.toLowerCase() || '';
47
+ const message = button.message?.toLowerCase() || '';
48
+ // Exclude if button label or message contains any of the excluded terms
49
+ return !excludeList.some((term) => label.includes(term) || message.includes(term));
50
+ };
51
+ }
52
+ }
53
+ return processorOptions;
54
+ }
55
+ // Set version from package.json
56
+ const packageJson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '../../package.json'), 'utf8'));
57
+ commander_1.program.version(packageJson.version);
58
+ commander_1.program
59
+ .command('analyze <file>')
60
+ .option('--format <format>', 'Format type (auto-detected if not specified)')
61
+ .option('--pretty', 'Pretty print output')
62
+ .option('--preserve-all-buttons', 'Preserve all buttons including navigation/system buttons')
63
+ .option('--no-exclude-navigation', "Don't exclude navigation buttons (Home, Back)")
64
+ .option('--no-exclude-system', "Don't exclude system buttons (Delete, Clear, etc.)")
65
+ .option('--exclude-buttons <list>', 'Comma-separated list of button labels/terms to exclude')
66
+ .action((file, options) => {
67
+ try {
68
+ // Parse filtering options
69
+ const filteringOptions = parseFilteringOptions(options);
70
+ // Auto-detect format if not specified
71
+ const format = options.format || detectFormat(file);
72
+ const processor = (0, analyze_1.getProcessor)(format, filteringOptions);
73
+ const tree = processor.loadIntoTree(file);
74
+ const result = {
75
+ format,
76
+ tree,
77
+ filtering: filteringOptions,
78
+ };
79
+ if (options.pretty) {
80
+ console.log((0, prettyPrint_1.prettyPrintTree)(result.tree));
81
+ }
82
+ else {
83
+ console.log(JSON.stringify(result, null, 2));
84
+ }
85
+ }
86
+ catch (error) {
87
+ console.error('Error analyzing file:', error instanceof Error ? error.message : String(error));
88
+ process.exit(1);
89
+ }
90
+ });
91
+ commander_1.program
92
+ .command('extract <file>')
93
+ .option('--format <format>', 'Format type (auto-detected if not specified)')
94
+ .option('--verbose', 'Verbose output')
95
+ .option('--quiet', 'Quiet output')
96
+ .option('--preserve-all-buttons', 'Preserve all buttons including navigation/system buttons')
97
+ .option('--no-exclude-navigation', "Don't exclude navigation buttons (Home, Back)")
98
+ .option('--no-exclude-system', "Don't exclude system buttons (Delete, Clear, etc.)")
99
+ .option('--exclude-buttons <list>', 'Comma-separated list of button labels/terms to exclude')
100
+ .action((file, options) => {
101
+ try {
102
+ // Parse filtering options
103
+ const filteringOptions = parseFilteringOptions(options);
104
+ // Auto-detect format if not specified
105
+ const format = options.format || detectFormat(file);
106
+ const processor = (0, analyze_1.getProcessor)(format, filteringOptions);
107
+ const texts = processor.extractTexts(file);
108
+ if (!options.quiet) {
109
+ if (options.verbose) {
110
+ console.log(`Extracting texts from ${file} (format: ${format})`);
111
+ console.log(`Found ${texts.length} text entries:`);
112
+ // Show filtering info in verbose mode
113
+ if (filteringOptions.preserveAllButtons) {
114
+ console.log('Filtering: All buttons preserved');
115
+ }
116
+ else {
117
+ const filters = [];
118
+ if (filteringOptions.excludeNavigationButtons !== false)
119
+ filters.push('navigation');
120
+ if (filteringOptions.excludeSystemButtons !== false)
121
+ filters.push('system');
122
+ if (filteringOptions.customButtonFilter)
123
+ filters.push('custom');
124
+ if (filters.length > 0) {
125
+ console.log(`Filtering: Excluding ${filters.join(', ')} buttons`);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ // Output the texts (one per line for easier processing)
131
+ texts.forEach((text) => console.log(text));
132
+ }
133
+ catch (error) {
134
+ console.error('Error extracting texts:', error instanceof Error ? error.message : String(error));
135
+ process.exit(1);
136
+ }
137
+ });
138
+ commander_1.program
139
+ .command('convert <input> <output>')
140
+ .option('--format <format>', 'Output format (required)')
141
+ .option('--preserve-all-buttons', 'Preserve all buttons including navigation/system buttons')
142
+ .option('--no-exclude-navigation', "Don't exclude navigation buttons (Home, Back)")
143
+ .option('--no-exclude-system', "Don't exclude system buttons (Delete, Clear, etc.)")
144
+ .option('--exclude-buttons <list>', 'Comma-separated list of button labels/terms to exclude')
145
+ .action((input, output, options) => {
146
+ try {
147
+ if (!options.format) {
148
+ console.error('Error: --format option is required for convert command');
149
+ process.exit(1);
150
+ }
151
+ // Parse filtering options
152
+ const filteringOptions = parseFilteringOptions(options);
153
+ // Auto-detect input format
154
+ const inputFormat = detectFormat(input);
155
+ const inputProcessor = (0, analyze_1.getProcessor)(inputFormat, filteringOptions);
156
+ // Load the tree (handle both files and folders)
157
+ const tree = inputProcessor.loadIntoTree(input);
158
+ // Save using output format with same filtering options
159
+ const outputProcessor = (0, analyze_1.getProcessor)(options.format, filteringOptions);
160
+ outputProcessor.saveFromTree(tree, output);
161
+ // Show filtering summary
162
+ let filteringSummary = '';
163
+ if (filteringOptions.preserveAllButtons) {
164
+ filteringSummary = ' (all buttons preserved)';
165
+ }
166
+ else {
167
+ const filters = [];
168
+ if (filteringOptions.excludeNavigationButtons !== false)
169
+ filters.push('navigation');
170
+ if (filteringOptions.excludeSystemButtons !== false)
171
+ filters.push('system');
172
+ if (filteringOptions.customButtonFilter)
173
+ filters.push('custom');
174
+ if (filters.length > 0) {
175
+ filteringSummary = ` (filtered: ${filters.join(', ')} buttons)`;
176
+ }
177
+ }
178
+ console.log(`Successfully converted ${input} to ${output} (${options.format} format)${filteringSummary}`);
179
+ }
180
+ catch (error) {
181
+ console.error('Error converting file:', error instanceof Error ? error.message : String(error));
182
+ process.exit(1);
183
+ }
184
+ });
185
+ // Show help if no command provided
186
+ if (process.argv.length <= 2) {
187
+ commander_1.program.help();
188
+ }
189
+ commander_1.program.parse(process.argv);
@@ -0,0 +1,2 @@
1
+ import { AACTree } from '../core/treeStructure';
2
+ export declare function prettyPrintTree(tree: AACTree): string;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prettyPrintTree = prettyPrintTree;
4
+ function prettyPrintTree(tree) {
5
+ let output = '';
6
+ for (const pageId in tree.pages) {
7
+ const page = tree.pages[pageId];
8
+ output += `Page: ${page.name} (ID: ${page.id})\n`;
9
+ if (!page.buttons || page.buttons.length === 0) {
10
+ output += ' (no buttons)\n';
11
+ }
12
+ else {
13
+ for (const btn of page.buttons) {
14
+ const intentStr = String(btn.semanticAction?.intent);
15
+ const isNavigate = intentStr === 'NAVIGATE_TO' || !!btn.targetPageId;
16
+ const buttonType = isNavigate ? 'NAVIGATE' : 'SPEAK';
17
+ output += ` - Button: ${JSON.stringify(btn.label)} [${buttonType}`;
18
+ if (isNavigate) {
19
+ const target = btn.semanticAction?.targetId || btn.targetPageId;
20
+ if (target)
21
+ output += ` to page: ${target}`;
22
+ }
23
+ output += ']\n';
24
+ }
25
+ }
26
+ }
27
+ return output;
28
+ }
@@ -0,0 +1,6 @@
1
+ import { AACTree } from './treeStructure';
2
+ import { BaseProcessor, ProcessorOptions } from './baseProcessor';
3
+ export declare function getProcessor(format: string, options?: ProcessorOptions): BaseProcessor;
4
+ export declare function analyze(file: string, format: string): {
5
+ tree: AACTree;
6
+ };
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getProcessor = getProcessor;
4
+ exports.analyze = analyze;
5
+ const opmlProcessor_1 = require("../processors/opmlProcessor");
6
+ const obfProcessor_1 = require("../processors/obfProcessor");
7
+ const touchchatProcessor_1 = require("../processors/touchchatProcessor");
8
+ const gridsetProcessor_1 = require("../processors/gridsetProcessor");
9
+ const astericsGridProcessor_1 = require("../processors/astericsGridProcessor");
10
+ const snapProcessor_1 = require("../processors/snapProcessor");
11
+ const dotProcessor_1 = require("../processors/dotProcessor");
12
+ const excelProcessor_1 = require("../processors/excelProcessor");
13
+ const applePanelsProcessor_1 = require("../processors/applePanelsProcessor");
14
+ function getProcessor(format, options) {
15
+ const normalizedFormat = (format || '').toLowerCase();
16
+ switch (normalizedFormat) {
17
+ case 'opml':
18
+ return new opmlProcessor_1.OpmlProcessor(options);
19
+ case 'obf':
20
+ return new obfProcessor_1.ObfProcessor(options);
21
+ case 'touchchat':
22
+ case 'ce': // TouchChat file extension
23
+ return new touchchatProcessor_1.TouchChatProcessor(options);
24
+ case 'gridset':
25
+ return new gridsetProcessor_1.GridsetProcessor(options); // Grid3 format
26
+ case 'grd': // Asterics Grid file extension
27
+ return new astericsGridProcessor_1.AstericsGridProcessor(options);
28
+ case 'snap':
29
+ case 'sps': // Snap file extension
30
+ case 'spb': // Snap backup file extension
31
+ return new snapProcessor_1.SnapProcessor(options);
32
+ case 'dot':
33
+ return new dotProcessor_1.DotProcessor(options);
34
+ case 'excel':
35
+ case 'xlsx': // Excel file extension
36
+ return new excelProcessor_1.ExcelProcessor(options);
37
+ case 'applepanels':
38
+ case 'panels': // Apple Panels file extension
39
+ case 'ascconfig': // Apple Panels folder format
40
+ return new applePanelsProcessor_1.ApplePanelsProcessor(options);
41
+ default:
42
+ throw new Error('Unknown format: ' + format);
43
+ }
44
+ }
45
+ function analyze(file, format) {
46
+ const processor = getProcessor(format);
47
+ const tree = processor.loadIntoTree(file);
48
+ return { tree };
49
+ }
@@ -0,0 +1,94 @@
1
+ import { AACTree, AACButton } from './treeStructure';
2
+ import { StringCasing } from './stringCasing';
3
+ export interface ProcessorOptions {
4
+ excludeNavigationButtons?: boolean;
5
+ excludeSystemButtons?: boolean;
6
+ customButtonFilter?: (button: AACButton) => boolean;
7
+ preserveAllButtons?: boolean;
8
+ }
9
+ export interface ExtractedString {
10
+ string: string;
11
+ vocabPlacementMeta: VocabPlacementMetadata;
12
+ }
13
+ export interface VocabPlacementMetadata {
14
+ vocabLocations: VocabLocation[];
15
+ }
16
+ export interface VocabLocation {
17
+ table: string;
18
+ id: number;
19
+ column: string;
20
+ casing: StringCasing;
21
+ }
22
+ export interface ProcessingError {
23
+ message: string;
24
+ step: 'EXTRACT' | 'PROCESS' | 'SAVE';
25
+ }
26
+ export interface ExtractStringsResult {
27
+ errors: ProcessingError[];
28
+ extractedStrings: ExtractedString[];
29
+ }
30
+ export interface TranslatedString {
31
+ sourcestringid: number;
32
+ overridestring: string;
33
+ translatedstring: string;
34
+ }
35
+ export interface SourceString {
36
+ id: number;
37
+ sourcestring: string;
38
+ vocabplacementmetadata: VocabPlacementMetadata;
39
+ }
40
+ declare abstract class BaseProcessor {
41
+ protected options: ProcessorOptions;
42
+ constructor(options?: ProcessorOptions);
43
+ abstract extractTexts(filePathOrBuffer: string | Buffer): string[];
44
+ abstract loadIntoTree(filePathOrBuffer: string | Buffer): AACTree;
45
+ abstract processTexts(filePathOrBuffer: string | Buffer, translations: Map<string, string>, outputPath: string): Buffer;
46
+ abstract saveFromTree(tree: AACTree, outputPath: string): void | Promise<void>;
47
+ /**
48
+ * Extract strings with metadata for external platform integration
49
+ * @param filePath - Path to the AAC file
50
+ * @returns Promise with extracted strings and any errors
51
+ */
52
+ extractStringsWithMetadata?(filePath: string): Promise<ExtractStringsResult>;
53
+ /**
54
+ * Generate translated download with external translation data
55
+ * @param filePath - Path to the original AAC file
56
+ * @param translatedStrings - Array of translated string data
57
+ * @param sourceStrings - Array of source string data with metadata
58
+ * @returns Promise with path to the generated translated file
59
+ */
60
+ generateTranslatedDownload?(filePath: string, translatedStrings: TranslatedString[], sourceStrings: SourceString[]): Promise<string>;
61
+ protected shouldFilterButton(button: AACButton): boolean;
62
+ protected filterPageButtons(buttons: AACButton[]): AACButton[];
63
+ /**
64
+ * Generic implementation for extracting strings with metadata
65
+ * Can be used by any processor that doesn't need format-specific logic
66
+ * @param filePath - Path to the AAC file
67
+ * @returns Promise with extracted strings and metadata
68
+ */
69
+ protected extractStringsWithMetadataGeneric(filePath: string): Promise<ExtractStringsResult>;
70
+ /**
71
+ * Generic implementation for generating translated downloads
72
+ * Can be used by any processor that doesn't need format-specific logic
73
+ * @param filePath - Path to the original AAC file
74
+ * @param translatedStrings - Array of translated string data
75
+ * @param sourceStrings - Array of source string data
76
+ * @returns Promise with path to the generated translated file
77
+ */
78
+ protected generateTranslatedDownloadGeneric(filePath: string, translatedStrings: TranslatedString[], sourceStrings: SourceString[]): Promise<string>;
79
+ /**
80
+ * Helper method to add extracted strings to the map, handling duplicates
81
+ * @param extractedMap - Map to store extracted strings
82
+ * @param key - Lowercase key for deduplication
83
+ * @param originalString - Original string with proper casing
84
+ * @param vocabLocation - Metadata about where the string was found
85
+ */
86
+ protected addToExtractedMap(extractedMap: Map<string, ExtractedString>, key: string, originalString: string, vocabLocation: VocabLocation): void;
87
+ /**
88
+ * Generate output path for translated file based on input file extension
89
+ * @param filePath - Original file path
90
+ * @returns Path for the translated output file
91
+ */
92
+ protected generateTranslatedOutputPath(filePath: string): string;
93
+ }
94
+ export { BaseProcessor };
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseProcessor = void 0;
4
+ const treeStructure_1 = require("./treeStructure");
5
+ const stringCasing_1 = require("./stringCasing");
6
+ class BaseProcessor {
7
+ constructor(options = {}) {
8
+ // Default configuration: exclude navigation/system buttons
9
+ this.options = {
10
+ excludeNavigationButtons: true,
11
+ excludeSystemButtons: true,
12
+ preserveAllButtons: false,
13
+ ...options,
14
+ };
15
+ }
16
+ // Helper method to determine if a button should be filtered out
17
+ shouldFilterButton(button) {
18
+ // If preserveAllButtons is true, never filter
19
+ if (this.options.preserveAllButtons) {
20
+ return false;
21
+ }
22
+ // Apply custom filter if provided
23
+ if (this.options.customButtonFilter) {
24
+ return !this.options.customButtonFilter(button);
25
+ }
26
+ // Check semantic action-based filtering
27
+ if (button.semanticAction) {
28
+ const { category, intent } = button.semanticAction;
29
+ // Filter specific navigation intents (toolbar navigation only)
30
+ if (this.options.excludeNavigationButtons) {
31
+ const i = String(intent);
32
+ if (i === 'GO_BACK' || i === 'GO_HOME') {
33
+ return true;
34
+ }
35
+ }
36
+ // Filter system/text editing buttons by category
37
+ if (this.options.excludeSystemButtons && category === treeStructure_1.AACSemanticCategory.TEXT_EDITING) {
38
+ return true;
39
+ }
40
+ // Filter specific system intents
41
+ if (this.options.excludeSystemButtons) {
42
+ const i = String(intent);
43
+ if (i === 'DELETE_WORD' ||
44
+ i === 'DELETE_CHARACTER' ||
45
+ i === 'CLEAR_TEXT' ||
46
+ i === 'COPY_TEXT') {
47
+ return true;
48
+ }
49
+ }
50
+ }
51
+ // Fallback: check button labels for common navigation/system terms
52
+ // Only apply label-based filtering if button doesn't have semantic actions
53
+ if (!button.semanticAction &&
54
+ (this.options.excludeNavigationButtons || this.options.excludeSystemButtons)) {
55
+ const label = button.label?.toLowerCase() || '';
56
+ const message = button.message?.toLowerCase() || '';
57
+ // More conservative navigation terms (exclude "more" since it's often used for legitimate page navigation)
58
+ const navigationTerms = ['back', 'home', 'menu', 'settings'];
59
+ const systemTerms = ['delete', 'clear', 'copy', 'paste', 'undo', 'redo'];
60
+ if (this.options.excludeNavigationButtons &&
61
+ navigationTerms.some((term) => label.includes(term) || message.includes(term))) {
62
+ return true;
63
+ }
64
+ if (this.options.excludeSystemButtons &&
65
+ systemTerms.some((term) => label.includes(term) || message.includes(term))) {
66
+ return true;
67
+ }
68
+ }
69
+ return false;
70
+ }
71
+ // Helper method to filter buttons from a page
72
+ filterPageButtons(buttons) {
73
+ return buttons.filter((button) => !this.shouldFilterButton(button));
74
+ }
75
+ /**
76
+ * Generic implementation for extracting strings with metadata
77
+ * Can be used by any processor that doesn't need format-specific logic
78
+ * @param filePath - Path to the AAC file
79
+ * @returns Promise with extracted strings and metadata
80
+ */
81
+ extractStringsWithMetadataGeneric(filePath) {
82
+ try {
83
+ const tree = this.loadIntoTree(filePath);
84
+ const extractedMap = new Map();
85
+ // Process all pages and buttons
86
+ Object.values(tree.pages).forEach((page) => {
87
+ // Process page names
88
+ if (page.name && page.name.trim().length > 1 && !(0, stringCasing_1.isNumericOrEmpty)(page.name)) {
89
+ const key = page.name.trim().toLowerCase();
90
+ const vocabLocation = {
91
+ table: 'pages',
92
+ id: parseInt(page.id) || 0,
93
+ column: 'NAME',
94
+ casing: (0, stringCasing_1.detectCasing)(page.name),
95
+ };
96
+ this.addToExtractedMap(extractedMap, key, page.name.trim(), vocabLocation);
97
+ }
98
+ page.buttons.forEach((button) => {
99
+ // Process button labels
100
+ if (button.label && button.label.trim().length > 1 && !(0, stringCasing_1.isNumericOrEmpty)(button.label)) {
101
+ const key = button.label.trim().toLowerCase();
102
+ const vocabLocation = {
103
+ table: 'buttons',
104
+ id: parseInt(button.id) || 0,
105
+ column: 'LABEL',
106
+ casing: (0, stringCasing_1.detectCasing)(button.label),
107
+ };
108
+ this.addToExtractedMap(extractedMap, key, button.label.trim(), vocabLocation);
109
+ }
110
+ // Process button messages (if different from label)
111
+ if (button.message &&
112
+ button.message !== button.label &&
113
+ button.message.trim().length > 1 &&
114
+ !(0, stringCasing_1.isNumericOrEmpty)(button.message)) {
115
+ const key = button.message.trim().toLowerCase();
116
+ const vocabLocation = {
117
+ table: 'buttons',
118
+ id: parseInt(button.id) || 0,
119
+ column: 'MESSAGE',
120
+ casing: (0, stringCasing_1.detectCasing)(button.message),
121
+ };
122
+ this.addToExtractedMap(extractedMap, key, button.message.trim(), vocabLocation);
123
+ }
124
+ });
125
+ });
126
+ const extractedStrings = Array.from(extractedMap.values());
127
+ return Promise.resolve({ errors: [], extractedStrings });
128
+ }
129
+ catch (error) {
130
+ return Promise.resolve({
131
+ errors: [
132
+ {
133
+ message: error instanceof Error ? error.message : 'Unknown extraction error',
134
+ step: 'EXTRACT',
135
+ },
136
+ ],
137
+ extractedStrings: [],
138
+ });
139
+ }
140
+ }
141
+ /**
142
+ * Generic implementation for generating translated downloads
143
+ * Can be used by any processor that doesn't need format-specific logic
144
+ * @param filePath - Path to the original AAC file
145
+ * @param translatedStrings - Array of translated string data
146
+ * @param sourceStrings - Array of source string data
147
+ * @returns Promise with path to the generated translated file
148
+ */
149
+ generateTranslatedDownloadGeneric(filePath, translatedStrings, sourceStrings) {
150
+ try {
151
+ // Build translation map from the provided data
152
+ const translations = new Map();
153
+ sourceStrings.forEach((sourceString) => {
154
+ const translated = translatedStrings.find((ts) => ts.sourcestringid.toString() === sourceString.id.toString());
155
+ if (translated) {
156
+ const translatedText = translated.overridestring.length > 0
157
+ ? translated.overridestring
158
+ : translated.translatedstring;
159
+ translations.set(sourceString.sourcestring, translatedText);
160
+ }
161
+ });
162
+ // Generate output path based on file extension
163
+ const outputPath = this.generateTranslatedOutputPath(filePath);
164
+ // Use existing processTexts method
165
+ this.processTexts(filePath, translations, outputPath);
166
+ return Promise.resolve(outputPath);
167
+ }
168
+ catch (error) {
169
+ return Promise.reject(new Error(`Failed to generate translated download: ${error instanceof Error ? error.message : 'Unknown error'}`));
170
+ }
171
+ }
172
+ /**
173
+ * Helper method to add extracted strings to the map, handling duplicates
174
+ * @param extractedMap - Map to store extracted strings
175
+ * @param key - Lowercase key for deduplication
176
+ * @param originalString - Original string with proper casing
177
+ * @param vocabLocation - Metadata about where the string was found
178
+ */
179
+ addToExtractedMap(extractedMap, key, originalString, vocabLocation) {
180
+ const existing = extractedMap.get(key);
181
+ if (existing) {
182
+ existing.vocabPlacementMeta.vocabLocations.push(vocabLocation);
183
+ }
184
+ else {
185
+ extractedMap.set(key, {
186
+ string: originalString, // Use original casing for the string value
187
+ vocabPlacementMeta: {
188
+ vocabLocations: [vocabLocation],
189
+ },
190
+ });
191
+ }
192
+ }
193
+ /**
194
+ * Generate output path for translated file based on input file extension
195
+ * @param filePath - Original file path
196
+ * @returns Path for the translated output file
197
+ */
198
+ generateTranslatedOutputPath(filePath) {
199
+ const lastDotIndex = filePath.lastIndexOf('.');
200
+ if (lastDotIndex === -1) {
201
+ return filePath + '_translated';
202
+ }
203
+ const basePath = filePath.substring(0, lastDotIndex);
204
+ const extension = filePath.substring(lastDotIndex);
205
+ return `${basePath}_translated${extension}`;
206
+ }
207
+ }
208
+ exports.BaseProcessor = BaseProcessor;
@@ -0,0 +1,7 @@
1
+ type FileFormat = 'gridset' | 'coughdrop' | 'touchchat' | 'snap' | 'dot' | 'opml' | 'excel' | 'unknown';
2
+ declare class FileProcessor {
3
+ static readFile(filePath: string): Buffer;
4
+ static writeFile(filePath: string, data: Buffer | string): void;
5
+ static detectFormat(filePathOrBuffer: string | Buffer): FileFormat;
6
+ }
7
+ export default FileProcessor;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ class FileProcessor {
9
+ // Read a file and return its contents as a Buffer
10
+ static readFile(filePath) {
11
+ return fs_1.default.readFileSync(filePath);
12
+ }
13
+ // Write data (Buffer or string) to a file
14
+ static writeFile(filePath, data) {
15
+ fs_1.default.writeFileSync(filePath, data);
16
+ }
17
+ // Detect file format based on extension or magic bytes
18
+ static detectFormat(filePathOrBuffer) {
19
+ if (typeof filePathOrBuffer === 'string') {
20
+ const ext = path_1.default.extname(filePathOrBuffer).toLowerCase();
21
+ switch (ext) {
22
+ case '.gridset':
23
+ return 'gridset';
24
+ case '.obf':
25
+ case '.obz':
26
+ return 'coughdrop';
27
+ case '.ce':
28
+ case '.wfl':
29
+ case '.touchchat':
30
+ return 'touchchat';
31
+ case '.sps':
32
+ case '.spb':
33
+ return 'snap';
34
+ case '.dot':
35
+ return 'dot';
36
+ case '.opml':
37
+ return 'opml';
38
+ case '.xlsx':
39
+ return 'excel';
40
+ default:
41
+ return 'unknown';
42
+ }
43
+ }
44
+ else if (Buffer.isBuffer(filePathOrBuffer)) {
45
+ // Optionally: inspect magic bytes here
46
+ return 'unknown';
47
+ }
48
+ return 'unknown';
49
+ }
50
+ }
51
+ exports.default = FileProcessor;