Skip to content

Commit ee8d1e8

Browse files
committed
add helpers for paths
1 parent 6c111ad commit ee8d1e8

18 files changed

Lines changed: 967 additions & 67 deletions

CLA.md

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ Thank you for contributing to aacprocessors-node (“Project”).
66
This Contributor License Agreement (“Agreement”) clarifies how we can use your contributions while you keep your rights.
77

88
1. Definitions
9-
“You” means the individual or legal entity submitting a Contribution.
10-
“Contribution” means any original work of authorship (code, docs, tests, sample board files, format-specific fixtures, issues, examples, data, etc.) that you submit to this Project.
11-
“Maintainers” means the Project owners and authorized reviewers.
9+
“You” means the individual or legal entity submitting a Contribution.
10+
“Contribution” means any original work of authorship (code, docs, tests, sample board files, format-specific fixtures, issues, examples, data, etc.) that you submit to this Project.
11+
“Maintainers” means the Project owners and authorized reviewers.
1212

1313
2. Copyright license
1414

@@ -21,12 +21,7 @@ If you (or your agent) initiate patent litigation alleging that the Project or a
2121

2222
4. Your representations
2323

24-
By submitting a Contribution, you represent that:
25-
1. Originality / rights. The Contribution is your original work, or you have sufficient rights to submit it and to grant the licenses above.
26-
2. Third-party material. If the Contribution includes third-party content (code, data, audio, symbols, images), you have identified its source and license, and it is compatible with the Project’s license (currently MIT; see LICENSE). Specifically, Contributions must not include any proprietary, non-redistributable symbol sets (e.g., Boardmaker PCS®) or other media unless you provide explicit proof of a compatible license (e.g., Creative Commons, public domain, or a specific grant of rights).
27-
3. No Protected Health Information or Personal Data. The Contribution does not include confidential information, personal data, or Protected Health Information (PHI) you lack the explicit right to share. You represent that any data resembling clinical or user-generated content (including voice recordings, text, or board layouts) is synthetic, fully anonymized, and carries no risk of re-identification.
28-
4. No additional restrictions. You will not impose further terms or restrictions on the Contribution (e.g., no additional EULAs or field-of-use limits).
29-
5. Compliance. You will follow applicable laws and the Project’s Code of Conduct.
24+
By submitting a Contribution, you represent that: 1. Originality / rights. The Contribution is your original work, or you have sufficient rights to submit it and to grant the licenses above. 2. Third-party material. If the Contribution includes third-party content (code, data, audio, symbols, images), you have identified its source and license, and it is compatible with the Project’s license (currently MIT; see LICENSE). Specifically, Contributions must not include any proprietary, non-redistributable symbol sets (e.g., Boardmaker PCS®) or other media unless you provide explicit proof of a compatible license (e.g., Creative Commons, public domain, or a specific grant of rights). 3. No Protected Health Information or Personal Data. The Contribution does not include confidential information, personal data, or Protected Health Information (PHI) you lack the explicit right to share. You represent that any data resembling clinical or user-generated content (including voice recordings, text, or board layouts) is synthetic, fully anonymized, and carries no risk of re-identification. 4. No additional restrictions. You will not impose further terms or restrictions on the Contribution (e.g., no additional EULAs or field-of-use limits). 5. Compliance. You will follow applicable laws and the Project’s Code of Conduct.
3025

3126
5. Moral rights
3227

@@ -51,9 +46,9 @@ TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT WILL YOU OR THE MAINTAINERS
5146
10. How you agree to this CLA
5247

5348
You agree to this CLA by any of the following:
54-
Submitting a pull request that includes a DCO-style sign-off line in each commit message, or
55-
Checking the CLA/DCO box presented by any automated check on your pull request, or
56-
Completing and submitting the optional signature block below in your PR.
49+
Submitting a pull request that includes a DCO-style sign-off line in each commit message, or
50+
Checking the CLA/DCO box presented by any automated check on your pull request, or
51+
Completing and submitting the optional signature block below in your PR.
5752

5853
DCO Sign-off format (required if DCO is enforced):
5954

@@ -72,22 +67,22 @@ If any provision of this Agreement is unenforceable, the remaining provisions re
7267
(Optional) Signature Block
7368

7469
If your organization requires a signed record, include this in your PR (as cla-signature.txt or in the PR description).
75-
Contributor Name: ______________________________
76-
GitHub Username: ______________________________
77-
Email: _______________________________________
78-
Employer/Entity (if any): _____________________
79-
I have authority to bind the entity (Y/N): _____
80-
Date: _________________________________________
81-
Signature: ____________________________________
70+
Contributor Name: **************\_\_**************
71+
GitHub Username: **************\_\_**************
72+
Email: ******************\_\_\_******************
73+
Employer/Entity (if any): **********\_**********
74+
I have authority to bind the entity (Y/N): **\_**
75+
Date: ********************\_********************
76+
Signature: ****************\_\_\_\_****************
8277

8378
8479

8580
FAQ (project-specific)
86-
Can I include real AAC data?
81+
Can I include real AAC data?
8782
Please do not. Use synthetic or fully anonymized fixtures. Do not upload identifiable voice/audio or clinical details.
88-
What licenses are acceptable for third-party assets?
83+
What licenses are acceptable for third-party assets?
8984
MIT/Apache-2.0/BSD-style are preferred for code; ensure data/symbol assets permit redistribution in an open-source project.
90-
Patents?
85+
Patents?
9186
By contributing, you grant the patent license above. If you file a patent suit alleging infringement by this Project, your patent grant here ends.
9287

93-
This CLA is a community-friendly, Apache-style CLA adapted for AAC libraries. It is not legal advice.
88+
This CLA is a community-friendly, Apache-style CLA adapted for AAC libraries. It is not legal advice.

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,8 @@ processor.saveFromTree(tree, "styled-board.spb");
404404
| **Grid3** | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Style references |
405405
| **Asterics Grid** | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Metadata-based |
406406
| **Apple Panels** | ✅ Yes | ✅ Size only | ❌ No | ✅ Display weight |
407-
| **Dot** | ❌No | ❌ Yes | ❌ No | ❌ Basic only |
408-
| **OPML** | ❌No | ❌ Yes | ❌ No | ❌ Basic only |
407+
| **Dot** | ❌No | ❌ Yes | ❌ No | ❌ Basic only |
408+
| **OPML** | ❌No | ❌ Yes | ❌ No | ❌ Basic only |
409409
| **Excel** | ✅ Yes | ✅ Size only | ❌ No | ✅ Display weight |
410410

411411
#### Cross-Format Styling Conversion
@@ -710,7 +710,6 @@ Inspired by the Python AACProcessors project
710710
- [ ] **Add Symbol Tools coverage** (currently 0%) - Implement tests for PCS and ARASAAC symbol lookups to reach >70% coverage
711711
- [ ] **Fix property-based test failures** - Resolve TypeScript interface compatibility issues in edge case generators
712712

713-
714713
### Medium Priority
715714

716715
- [ ] **Performance optimization** - Optimize memory usage for very large communication boards (1000+ buttons)

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
"test": "test"
1818
},
1919
"scripts": {
20-
"build": "rimraf dist && mkdir dist && ./node_modules/typescript/bin/tsc",
20+
"build": "rimraf dist && mkdir dist && tsc",
2121
"build:watch": "tsc --watch",
2222
"clean": "rimraf dist coverage",
23-
"lint": "eslint 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'",
24-
"lint:fix": "eslint 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' --fix",
25-
"format": "prettier --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' '*.{js,ts,json,md}'",
26-
"format:check": "prettier --check 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' '*.{js,ts,json,md}'",
23+
"lint": "eslint \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\"",
24+
"lint:fix": "eslint \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" --fix",
25+
"format": "prettier --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" \"*.{js,ts,json,md}\"",
26+
"format:check": "prettier --check \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" \"*.{js,ts,json,md}\"",
2727
"test": "npm run build && jest",
2828
"test:watch": "npm run build && jest --watch",
2929
"test:coverage": "npm run build && jest --coverage",

src/processors/gridset/helpers.ts

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import AdmZip from 'adm-zip';
22
import { XMLBuilder } from 'fast-xml-parser';
33
import { AACTree, AACPage, AACButton } from '../../core/treeStructure';
4+
import * as fs from 'fs';
5+
import * as path from 'path';
6+
import { execSync } from 'child_process';
47

58
function normalizeZipPath(p: string): string {
69
const unified = p.replace(/\\/g, '/');
@@ -125,3 +128,189 @@ export function createFileMapXml(
125128

126129
return builder.build(fileMapData);
127130
}
131+
132+
/**
133+
* Grid3 user data path information
134+
*/
135+
export interface Grid3UserPath {
136+
userName: string;
137+
langCode: string;
138+
basePath: string;
139+
historyDbPath: string;
140+
}
141+
142+
export interface Grid3VocabularyPath {
143+
userName: string;
144+
gridsetPath: string;
145+
}
146+
147+
/**
148+
* Get the Windows Common Documents folder path from registry
149+
* Falls back to default path if registry access fails
150+
* @returns Path to Common Documents folder
151+
*/
152+
export function getCommonDocumentsPath(): string {
153+
// Only works on Windows
154+
if (process.platform !== 'win32') {
155+
return '';
156+
}
157+
158+
try {
159+
// Query registry for Common Documents path
160+
const command =
161+
'REG.EXE QUERY "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders" /V "Common Documents"';
162+
const output = execSync(command, { encoding: 'utf-8', windowsHide: true });
163+
164+
// Parse the output to extract the path
165+
const match = output.match(/Common Documents\s+REG_SZ\s+(.+)/);
166+
if (match && match[1]) {
167+
return match[1].trim();
168+
}
169+
} catch (error) {
170+
// Registry access failed, fall back to default
171+
}
172+
173+
// Default fallback path
174+
return 'C:\\Users\\Public\\Documents';
175+
}
176+
177+
/**
178+
* Find all Grid3 user data paths
179+
* Searches for users and language codes in the Grid3 directory structure
180+
* C:\Users\Public\Documents\Smartbox\Grid 3\Users\{UserName}\{langCode}\Phrases\history.sqlite
181+
* @returns Array of Grid3 user path information
182+
*/
183+
export function findGrid3UserPaths(): Grid3UserPath[] {
184+
const results: Grid3UserPath[] = [];
185+
186+
// Only works on Windows
187+
if (process.platform !== 'win32') {
188+
return results;
189+
}
190+
191+
try {
192+
const commonDocs = getCommonDocumentsPath();
193+
const grid3BasePath = path.join(commonDocs, 'Smartbox', 'Grid 3', 'Users');
194+
195+
// Check if Grid3 Users directory exists
196+
if (!fs.existsSync(grid3BasePath)) {
197+
return results;
198+
}
199+
200+
// Enumerate users
201+
const users = fs.readdirSync(grid3BasePath, { withFileTypes: true });
202+
203+
for (const userDir of users) {
204+
if (!userDir.isDirectory()) continue;
205+
206+
const userName = userDir.name;
207+
const userPath = path.join(grid3BasePath, userName);
208+
209+
// Enumerate language codes
210+
const langDirs = fs.readdirSync(userPath, { withFileTypes: true });
211+
212+
for (const langDir of langDirs) {
213+
if (!langDir.isDirectory()) continue;
214+
215+
const langCode = langDir.name;
216+
const basePath = path.join(userPath, langCode);
217+
const historyDbPath = path.join(basePath, 'Phrases', 'history.sqlite');
218+
219+
// Only include if history database exists
220+
if (fs.existsSync(historyDbPath)) {
221+
results.push({
222+
userName,
223+
langCode,
224+
basePath,
225+
historyDbPath,
226+
});
227+
}
228+
}
229+
}
230+
} catch (error) {
231+
// Silently fail if directory access fails
232+
}
233+
234+
return results;
235+
}
236+
237+
/**
238+
* Find all Grid3 history database paths
239+
* Convenience method that returns just the database file paths
240+
* @returns Array of paths to history.sqlite files
241+
*/
242+
export function findGrid3HistoryDatabases(): string[] {
243+
return findGrid3UserPaths().map((userPath) => userPath.historyDbPath);
244+
}
245+
246+
/**
247+
* Get Grid 3 users (alias of findGrid3UserPaths for clarity)
248+
*/
249+
export function findGrid3Users(): Grid3UserPath[] {
250+
return findGrid3UserPaths();
251+
}
252+
253+
/**
254+
* Find Grid 3 gridset/vocabulary files for each user
255+
* @param userName Optional user filter; matches case-insensitively
256+
* @returns Array of user/gridset path pairs
257+
*/
258+
export function findGrid3Vocabularies(userName?: string): Grid3VocabularyPath[] {
259+
const results: Grid3VocabularyPath[] = [];
260+
261+
if (process.platform !== 'win32') {
262+
return results;
263+
}
264+
265+
const commonDocs = getCommonDocumentsPath();
266+
const grid3BasePath = path.join(commonDocs, 'Smartbox', 'Grid 3', 'Users');
267+
268+
if (!fs.existsSync(grid3BasePath)) {
269+
return results;
270+
}
271+
272+
const normalizedUser = userName?.toLowerCase();
273+
const users = fs.readdirSync(grid3BasePath, { withFileTypes: true });
274+
275+
for (const userDir of users) {
276+
if (!userDir.isDirectory()) continue;
277+
if (normalizedUser && userDir.name.toLowerCase() !== normalizedUser) continue;
278+
279+
const userRoot = path.join(grid3BasePath, userDir.name);
280+
const gridSetsDir = path.join(userRoot, 'Grid Sets');
281+
if (!fs.existsSync(gridSetsDir)) continue;
282+
283+
const entries = fs.readdirSync(gridSetsDir, { withFileTypes: true });
284+
for (const entry of entries) {
285+
if (!entry.isFile()) continue;
286+
const ext = path.extname(entry.name).toLowerCase();
287+
if (ext === '.gridset' || ext === '.grd' || ext === '.grdl') {
288+
results.push({
289+
userName: userDir.name,
290+
gridsetPath: path.join(gridSetsDir, entry.name),
291+
});
292+
}
293+
}
294+
}
295+
296+
return results;
297+
}
298+
299+
/**
300+
* Find a specific user's Grid 3 history database
301+
* @param userName User name to search for (case-insensitive)
302+
* @param langCode Optional language code filter (case-insensitive)
303+
* @returns Path to history.sqlite or null if not found
304+
*/
305+
export function findGrid3UserHistory(userName: string, langCode?: string): string | null {
306+
const normalizedUser = userName.toLowerCase();
307+
const normalizedLang = langCode?.toLowerCase();
308+
309+
const match = findGrid3UserPaths().find(
310+
(u) =>
311+
u.userName.toLowerCase() === normalizedUser &&
312+
(!normalizedLang || u.langCode.toLowerCase() === normalizedLang)
313+
);
314+
315+
return match?.historyDbPath ?? null;
316+
}

src/processors/index.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ export {
1616
generateGrid3Guid,
1717
createSettingsXml,
1818
createFileMapXml,
19+
getCommonDocumentsPath,
20+
findGrid3UserPaths,
21+
findGrid3HistoryDatabases,
22+
findGrid3Users,
23+
findGrid3Vocabularies,
24+
findGrid3UserHistory,
25+
type Grid3UserPath,
26+
type Grid3VocabularyPath,
1927
} from './gridset/helpers';
2028
export {
2129
getPageTokenImageMap as getGridsetPageTokenImageMap,
@@ -24,6 +32,12 @@ export {
2432
generateGrid3Guid as generateGridsetGuid,
2533
createSettingsXml as createGridsetSettingsXml,
2634
createFileMapXml as createGridsetFileMapXml,
35+
getCommonDocumentsPath as getGridsetCommonDocumentsPath,
36+
findGrid3UserPaths as findGridsetUserPaths,
37+
findGrid3HistoryDatabases as findGridsetHistoryDatabases,
38+
findGrid3Users as findGridsetUsers,
39+
findGrid3Vocabularies as findGridsetVocabularies,
40+
findGrid3UserHistory as findGridsetUserHistory,
2741
} from './gridset/helpers';
2842
export { resolveGrid3CellImage } from './gridset/resolver';
2943

@@ -61,11 +75,18 @@ export {
6175
// Re-export ensureAlphaChannel from styleHelpers for backward compatibility
6276
export { ensureAlphaChannel as ensureAlphaChannelFromStyles } from './gridset/styleHelpers';
6377

64-
// Snap helpers (stubs)
78+
// Snap helpers
6579
export {
6680
getPageTokenImageMap as getSnapPageTokenImageMap,
6781
getAllowedImageEntries as getSnapAllowedImageEntries,
6882
openImage as openSnapImage,
83+
findSnapPackages,
84+
findSnapPackagePath,
85+
findSnapUsers,
86+
findSnapUserVocabularies,
87+
findSnapUserHistory,
88+
type SnapPackagePath,
89+
type SnapUserInfo,
6990
} from './snap/helpers';
7091

7192
// TouchChat helpers (stubs)

src/processors/obfProcessor.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,12 @@ class ObfProcessor extends BaseProcessor {
297297
buttonPositions.set(id, index);
298298
return id;
299299
});
300-
return { rows: 1, columns: fallbackRow.length, order: [fallbackRow], buttonPositions };
300+
return {
301+
rows: 1,
302+
columns: fallbackRow.length,
303+
order: [fallbackRow],
304+
buttonPositions,
305+
};
301306
}
302307

303308
const order: (string | null)[][] = [];

0 commit comments

Comments
 (0)