@willwade/aac-processors 0.1.5 → 0.1.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 +14 -0
- package/dist/browser/index.browser.js +15 -1
- package/dist/browser/processors/gridset/password.js +11 -0
- package/dist/browser/processors/gridsetProcessor.js +42 -46
- package/dist/browser/processors/obfProcessor.js +47 -63
- package/dist/browser/processors/snapProcessor.js +1031 -0
- package/dist/browser/processors/touchchatProcessor.js +1004 -0
- package/dist/browser/utils/io.js +36 -2
- package/dist/browser/utils/sqlite.js +109 -0
- package/dist/browser/utils/zip.js +54 -0
- package/dist/browser/validation/gridsetValidator.js +7 -27
- package/dist/browser/validation/obfValidator.js +9 -4
- package/dist/browser/validation/snapValidator.js +197 -0
- package/dist/browser/validation/touchChatValidator.js +201 -0
- package/dist/index.browser.d.ts +7 -0
- package/dist/index.browser.js +19 -2
- package/dist/processors/gridset/helpers.js +3 -4
- package/dist/processors/gridset/index.d.ts +1 -1
- package/dist/processors/gridset/index.js +3 -2
- package/dist/processors/gridset/password.d.ts +3 -2
- package/dist/processors/gridset/password.js +12 -0
- package/dist/processors/gridset/wordlistHelpers.js +107 -51
- package/dist/processors/gridsetProcessor.js +40 -44
- package/dist/processors/obfProcessor.js +46 -62
- package/dist/processors/snapProcessor.js +60 -54
- package/dist/processors/touchchatProcessor.js +38 -36
- package/dist/utils/io.d.ts +4 -0
- package/dist/utils/io.js +40 -2
- package/dist/utils/sqlite.d.ts +21 -0
- package/dist/utils/sqlite.js +137 -0
- package/dist/utils/zip.d.ts +7 -0
- package/dist/utils/zip.js +80 -0
- package/dist/validation/applePanelsValidator.js +11 -28
- package/dist/validation/astericsValidator.js +11 -30
- package/dist/validation/dotValidator.js +11 -30
- package/dist/validation/excelValidator.js +5 -6
- package/dist/validation/gridsetValidator.js +29 -26
- package/dist/validation/index.d.ts +2 -1
- package/dist/validation/index.js +9 -32
- package/dist/validation/obfValidator.js +8 -3
- package/dist/validation/obfsetValidator.js +11 -30
- package/dist/validation/opmlValidator.js +11 -30
- package/dist/validation/snapValidator.js +6 -9
- package/dist/validation/touchChatValidator.js +6 -7
- package/docs/BROWSER_USAGE.md +2 -10
- package/examples/README.md +3 -75
- package/examples/vitedemo/README.md +13 -7
- package/examples/vitedemo/index.html +51 -2
- package/examples/vitedemo/package-lock.json +9 -0
- package/examples/vitedemo/package.json +1 -0
- package/examples/vitedemo/src/main.ts +132 -2
- package/examples/vitedemo/src/vite-env.d.ts +1 -0
- package/examples/vitedemo/vite.config.ts +26 -7
- package/package.json +3 -1
- package/examples/browser-test-server.js +0 -81
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
4
|
+
import * as xml2js from 'xml2js';
|
|
5
|
+
import { BaseValidator } from './baseValidator';
|
|
6
|
+
import { decodeText, getBasename, getFs, readBinaryFromInput, toUint8Array } from '../utils/io';
|
|
7
|
+
/**
|
|
8
|
+
* Validator for TouchChat files (.ce)
|
|
9
|
+
* TouchChat files are XML-based
|
|
10
|
+
*/
|
|
11
|
+
export class TouchChatValidator extends BaseValidator {
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Validate a TouchChat file from disk
|
|
17
|
+
*/
|
|
18
|
+
static async validateFile(filePath) {
|
|
19
|
+
const validator = new TouchChatValidator();
|
|
20
|
+
const content = readBinaryFromInput(filePath);
|
|
21
|
+
const stats = getFs().statSync(filePath);
|
|
22
|
+
return validator.validate(content, getBasename(filePath), stats.size);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if content is TouchChat format
|
|
26
|
+
*/
|
|
27
|
+
static async identifyFormat(content, filename) {
|
|
28
|
+
const name = filename.toLowerCase();
|
|
29
|
+
if (name.endsWith('.ce')) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
// Try to parse as XML and check for TouchChat structure
|
|
33
|
+
try {
|
|
34
|
+
const contentStr = typeof content === 'string' ? content : decodeText(toUint8Array(content));
|
|
35
|
+
const parser = new xml2js.Parser();
|
|
36
|
+
const result = await parser.parseStringPromise(contentStr);
|
|
37
|
+
// TouchChat files typically have specific structure
|
|
38
|
+
return result && (result.PageSet || result.Pageset || result.page || result.Page);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Main validation method
|
|
46
|
+
*/
|
|
47
|
+
async validate(content, filename, filesize) {
|
|
48
|
+
this.reset();
|
|
49
|
+
await this.add_check('filename', 'file extension', async () => {
|
|
50
|
+
if (!filename.match(/\.ce$/i)) {
|
|
51
|
+
this.warn('filename should end with .ce');
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
let xmlObj = null;
|
|
55
|
+
await this.add_check('xml_parse', 'valid XML', async () => {
|
|
56
|
+
try {
|
|
57
|
+
const parser = new xml2js.Parser();
|
|
58
|
+
const contentStr = decodeText(content);
|
|
59
|
+
xmlObj = await parser.parseStringPromise(contentStr);
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
this.err(`Failed to parse XML: ${e.message}`, true);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
if (!xmlObj) {
|
|
66
|
+
return this.buildResult(filename, filesize, 'touchchat');
|
|
67
|
+
}
|
|
68
|
+
await this.add_check('xml_structure', 'TouchChat root element', async () => {
|
|
69
|
+
// TouchChat can have different root elements
|
|
70
|
+
const hasValidRoot = xmlObj.PageSet ||
|
|
71
|
+
xmlObj.Pageset ||
|
|
72
|
+
xmlObj.page ||
|
|
73
|
+
xmlObj.Page ||
|
|
74
|
+
xmlObj.pages ||
|
|
75
|
+
xmlObj.Pages;
|
|
76
|
+
if (!hasValidRoot) {
|
|
77
|
+
this.err('file does not contain a recognized TouchChat structure');
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const root = xmlObj.PageSet ||
|
|
81
|
+
xmlObj.Pageset ||
|
|
82
|
+
xmlObj.page ||
|
|
83
|
+
xmlObj.Page ||
|
|
84
|
+
xmlObj.pages ||
|
|
85
|
+
xmlObj.Pages;
|
|
86
|
+
if (root) {
|
|
87
|
+
await this.validateTouchChatStructure(root);
|
|
88
|
+
}
|
|
89
|
+
return this.buildResult(filename, filesize, 'touchchat');
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Validate TouchChat structure
|
|
93
|
+
*/
|
|
94
|
+
async validateTouchChatStructure(root) {
|
|
95
|
+
// Check for ID
|
|
96
|
+
await this.add_check('root_id', 'root element ID', async () => {
|
|
97
|
+
const id = root.$?.id || root.$?.Id;
|
|
98
|
+
if (!id) {
|
|
99
|
+
this.warn('root element should have an id attribute');
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
// Check for name
|
|
103
|
+
await this.add_check('root_name', 'root element name', async () => {
|
|
104
|
+
const name = root.$?.name || root.$?.Name || root.name?.[0];
|
|
105
|
+
if (!name) {
|
|
106
|
+
this.warn('root element should have a name');
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// Check for pages
|
|
110
|
+
await this.add_check('pages', 'pages collection', async () => {
|
|
111
|
+
const pages = root.page || root.Page || root.pages || root.Pages;
|
|
112
|
+
if (!pages) {
|
|
113
|
+
this.err('TouchChat file must contain pages');
|
|
114
|
+
}
|
|
115
|
+
else if (!Array.isArray(pages) || pages.length === 0) {
|
|
116
|
+
this.err('TouchChat file must contain at least one page');
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
// Validate individual pages
|
|
120
|
+
const pages = root.page || root.Page || root.pages || root.Pages;
|
|
121
|
+
if (pages && Array.isArray(pages)) {
|
|
122
|
+
await this.add_check('page_count', 'page count', async () => {
|
|
123
|
+
if (pages.length === 0) {
|
|
124
|
+
this.err('Must contain at least one page');
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
// Sample first few pages
|
|
128
|
+
const sampleSize = Math.min(pages.length, 5);
|
|
129
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
130
|
+
await this.validatePage(pages[i], i);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Validate a single page
|
|
136
|
+
*/
|
|
137
|
+
async validatePage(page, index) {
|
|
138
|
+
await this.add_check(`page[${index}]_id`, `page ${index} ID`, async () => {
|
|
139
|
+
const id = page.$?.id || page.$?.Id;
|
|
140
|
+
if (!id) {
|
|
141
|
+
this.warn(`page ${index} is missing an id attribute`);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
await this.add_check(`page[${index}]_name`, `page ${index} name`, async () => {
|
|
145
|
+
const name = page.$?.name || page.$?.Name || page.name?.[0];
|
|
146
|
+
if (!name) {
|
|
147
|
+
this.warn(`page ${index} should have a name`);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
// Check for buttons/items
|
|
151
|
+
await this.add_check(`page[${index}]_buttons`, `page ${index} buttons`, async () => {
|
|
152
|
+
const buttons = page.button || page.Button || page.item || page.Item;
|
|
153
|
+
if (!buttons) {
|
|
154
|
+
this.warn(`page ${index} has no buttons/items`);
|
|
155
|
+
}
|
|
156
|
+
else if (Array.isArray(buttons) && buttons.length === 0) {
|
|
157
|
+
this.warn(`page ${index} should contain at least one button`);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
// Validate button references
|
|
161
|
+
const buttons = page.button || page.Button || page.item || page.Item;
|
|
162
|
+
if (buttons && Array.isArray(buttons)) {
|
|
163
|
+
const sampleSize = Math.min(buttons.length, 3);
|
|
164
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
165
|
+
await this.validateButton(buttons[i], index, i);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Validate a single button
|
|
171
|
+
*/
|
|
172
|
+
async validateButton(button, pageIdx, buttonIdx) {
|
|
173
|
+
await this.add_check(`page[${pageIdx}]_button[${buttonIdx}]_label`, `button label`, async () => {
|
|
174
|
+
const label = button.$?.label || button.$?.Label || button.label?.[0];
|
|
175
|
+
if (!label) {
|
|
176
|
+
this.warn(`button ${buttonIdx} on page ${pageIdx} should have a label`);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
await this.add_check(`page[${pageIdx}]_button[${buttonIdx}]_vocalization`, `button vocalization`, async () => {
|
|
180
|
+
const vocalization = button.$?.vocalization || button.$?.Vocalization || button.vocalization?.[0];
|
|
181
|
+
if (!vocalization) {
|
|
182
|
+
// Vocalization is optional, so just info
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
// Check for image reference
|
|
186
|
+
await this.add_check(`page[${pageIdx}]_button[${buttonIdx}]_image`, `button image`, async () => {
|
|
187
|
+
const image = button.$?.image || button.$?.Image || button.img?.[0];
|
|
188
|
+
if (!image) {
|
|
189
|
+
this.warn(`button ${buttonIdx} on page ${pageIdx} should have an image reference`);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
// Check for link/action
|
|
193
|
+
await this.add_check(`page[${pageIdx}]_button[${buttonIdx}]_action`, `button action`, async () => {
|
|
194
|
+
const link = button.$?.link || button.$?.Link;
|
|
195
|
+
const action = button.$?.action || button.$?.Action;
|
|
196
|
+
if (!link && !action) {
|
|
197
|
+
// Not all buttons need actions, they can just speak
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
package/dist/index.browser.d.ts
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
* **NOTE: Gridset .gridsetx files**
|
|
7
7
|
* GridsetProcessor supports regular `.gridset` files in browser.
|
|
8
8
|
* Encrypted `.gridsetx` files require Node.js for crypto operations and are not supported in browser.
|
|
9
|
+
*
|
|
10
|
+
* **NOTE: SQLite-backed formats**
|
|
11
|
+
* Snap (.sps/.spb) and TouchChat (.ce) require a WASM-backed SQLite engine.
|
|
12
|
+
* Configure `sql.js` in browser builds via `configureSqlJs()` before loading these formats.
|
|
9
13
|
*/
|
|
10
14
|
export * from './core/treeStructure';
|
|
11
15
|
export * from './core/baseProcessor';
|
|
@@ -14,9 +18,12 @@ export { DotProcessor } from './processors/dotProcessor';
|
|
|
14
18
|
export { OpmlProcessor } from './processors/opmlProcessor';
|
|
15
19
|
export { ObfProcessor } from './processors/obfProcessor';
|
|
16
20
|
export { GridsetProcessor } from './processors/gridsetProcessor';
|
|
21
|
+
export { SnapProcessor } from './processors/snapProcessor';
|
|
22
|
+
export { TouchChatProcessor } from './processors/touchchatProcessor';
|
|
17
23
|
export { ApplePanelsProcessor } from './processors/applePanelsProcessor';
|
|
18
24
|
export { AstericsGridProcessor } from './processors/astericsGridProcessor';
|
|
19
25
|
import { BaseProcessor } from './core/baseProcessor';
|
|
26
|
+
export { configureSqlJs } from './utils/sqlite';
|
|
20
27
|
/**
|
|
21
28
|
* Factory function to get the appropriate processor for a file extension
|
|
22
29
|
* @param filePathOrExtension - File path or extension (e.g., '.dot', '/path/to/file.obf')
|
package/dist/index.browser.js
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* **NOTE: Gridset .gridsetx files**
|
|
8
8
|
* GridsetProcessor supports regular `.gridset` files in browser.
|
|
9
9
|
* Encrypted `.gridsetx` files require Node.js for crypto operations and are not supported in browser.
|
|
10
|
+
*
|
|
11
|
+
* **NOTE: SQLite-backed formats**
|
|
12
|
+
* Snap (.sps/.spb) and TouchChat (.ce) require a WASM-backed SQLite engine.
|
|
13
|
+
* Configure `sql.js` in browser builds via `configureSqlJs()` before loading these formats.
|
|
10
14
|
*/
|
|
11
15
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
16
|
if (k2 === undefined) k2 = k;
|
|
@@ -23,7 +27,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
23
27
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
24
28
|
};
|
|
25
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.AstericsGridProcessor = exports.ApplePanelsProcessor = exports.GridsetProcessor = exports.ObfProcessor = exports.OpmlProcessor = exports.DotProcessor = void 0;
|
|
30
|
+
exports.configureSqlJs = exports.AstericsGridProcessor = exports.ApplePanelsProcessor = exports.TouchChatProcessor = exports.SnapProcessor = exports.GridsetProcessor = exports.ObfProcessor = exports.OpmlProcessor = exports.DotProcessor = void 0;
|
|
27
31
|
exports.getProcessor = getProcessor;
|
|
28
32
|
exports.getSupportedExtensions = getSupportedExtensions;
|
|
29
33
|
exports.isExtensionSupported = isExtensionSupported;
|
|
@@ -44,6 +48,10 @@ var obfProcessor_1 = require("./processors/obfProcessor");
|
|
|
44
48
|
Object.defineProperty(exports, "ObfProcessor", { enumerable: true, get: function () { return obfProcessor_1.ObfProcessor; } });
|
|
45
49
|
var gridsetProcessor_1 = require("./processors/gridsetProcessor");
|
|
46
50
|
Object.defineProperty(exports, "GridsetProcessor", { enumerable: true, get: function () { return gridsetProcessor_1.GridsetProcessor; } });
|
|
51
|
+
var snapProcessor_1 = require("./processors/snapProcessor");
|
|
52
|
+
Object.defineProperty(exports, "SnapProcessor", { enumerable: true, get: function () { return snapProcessor_1.SnapProcessor; } });
|
|
53
|
+
var touchchatProcessor_1 = require("./processors/touchchatProcessor");
|
|
54
|
+
Object.defineProperty(exports, "TouchChatProcessor", { enumerable: true, get: function () { return touchchatProcessor_1.TouchChatProcessor; } });
|
|
47
55
|
var applePanelsProcessor_1 = require("./processors/applePanelsProcessor");
|
|
48
56
|
Object.defineProperty(exports, "ApplePanelsProcessor", { enumerable: true, get: function () { return applePanelsProcessor_1.ApplePanelsProcessor; } });
|
|
49
57
|
var astericsGridProcessor_1 = require("./processors/astericsGridProcessor");
|
|
@@ -52,8 +60,12 @@ const dotProcessor_2 = require("./processors/dotProcessor");
|
|
|
52
60
|
const opmlProcessor_2 = require("./processors/opmlProcessor");
|
|
53
61
|
const obfProcessor_2 = require("./processors/obfProcessor");
|
|
54
62
|
const gridsetProcessor_2 = require("./processors/gridsetProcessor");
|
|
63
|
+
const snapProcessor_2 = require("./processors/snapProcessor");
|
|
64
|
+
const touchchatProcessor_2 = require("./processors/touchchatProcessor");
|
|
55
65
|
const applePanelsProcessor_2 = require("./processors/applePanelsProcessor");
|
|
56
66
|
const astericsGridProcessor_2 = require("./processors/astericsGridProcessor");
|
|
67
|
+
var sqlite_1 = require("./utils/sqlite");
|
|
68
|
+
Object.defineProperty(exports, "configureSqlJs", { enumerable: true, get: function () { return sqlite_1.configureSqlJs; } });
|
|
57
69
|
/**
|
|
58
70
|
* Factory function to get the appropriate processor for a file extension
|
|
59
71
|
* @param filePathOrExtension - File path or extension (e.g., '.dot', '/path/to/file.obf')
|
|
@@ -74,6 +86,11 @@ function getProcessor(filePathOrExtension) {
|
|
|
74
86
|
return new obfProcessor_2.ObfProcessor();
|
|
75
87
|
case '.gridset':
|
|
76
88
|
return new gridsetProcessor_2.GridsetProcessor();
|
|
89
|
+
case '.spb':
|
|
90
|
+
case '.sps':
|
|
91
|
+
return new snapProcessor_2.SnapProcessor();
|
|
92
|
+
case '.ce':
|
|
93
|
+
return new touchchatProcessor_2.TouchChatProcessor();
|
|
77
94
|
case '.plist':
|
|
78
95
|
return new applePanelsProcessor_2.ApplePanelsProcessor();
|
|
79
96
|
case '.grd':
|
|
@@ -87,7 +104,7 @@ function getProcessor(filePathOrExtension) {
|
|
|
87
104
|
* @returns Array of supported file extensions
|
|
88
105
|
*/
|
|
89
106
|
function getSupportedExtensions() {
|
|
90
|
-
return ['.dot', '.opml', '.obf', '.obz', '.gridset', '.plist', '.grd'];
|
|
107
|
+
return ['.dot', '.opml', '.obf', '.obz', '.gridset', '.spb', '.sps', '.ce', '.plist', '.grd'];
|
|
91
108
|
}
|
|
92
109
|
/**
|
|
93
110
|
* Check if a file extension is supported
|
|
@@ -50,6 +50,7 @@ const child_process_1 = require("child_process");
|
|
|
50
50
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
51
51
|
const dotnetTicks_1 = require("../../utils/dotnetTicks");
|
|
52
52
|
const password_1 = require("./password");
|
|
53
|
+
const zip_1 = require("../../utils/zip");
|
|
53
54
|
function normalizeZipPath(p) {
|
|
54
55
|
const unified = p.replace(/\\/g, '/');
|
|
55
56
|
try {
|
|
@@ -97,10 +98,8 @@ function getAllowedImageEntries(tree) {
|
|
|
97
98
|
*/
|
|
98
99
|
async function openImage(gridsetBuffer, entryPath, password = (0, password_1.resolveGridsetPasswordFromEnv)()) {
|
|
99
100
|
try {
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
const zip = await JSZip.loadAsync(gridsetBuffer);
|
|
103
|
-
const entries = (0, password_1.getZipEntriesWithPassword)(zip, password);
|
|
101
|
+
const { zip } = await (0, zip_1.openZipFromInput)(gridsetBuffer);
|
|
102
|
+
const entries = (0, password_1.getZipEntriesFromAdapter)(zip, password);
|
|
104
103
|
const want = normalizeZipPath(entryPath);
|
|
105
104
|
const entry = entries.find((e) => normalizeZipPath(e.entryName) === want);
|
|
106
105
|
if (!entry)
|
|
@@ -15,7 +15,7 @@ import { CellBackgroundShape } from './styleHelpers';
|
|
|
15
15
|
import { Grid3CellType } from './pluginTypes';
|
|
16
16
|
import { Grid3CommandCategory } from './commands';
|
|
17
17
|
export { ensureAlphaChannel, darkenColor, lightenColor, hexToRgba, rgbaToHex } from './colorUtils';
|
|
18
|
-
export { resolveGridsetPassword, getZipEntriesWithPassword, resolveGridsetPasswordFromEnv, } from './password';
|
|
18
|
+
export { resolveGridsetPassword, getZipEntriesWithPassword, getZipEntriesFromAdapter, resolveGridsetPasswordFromEnv, } from './password';
|
|
19
19
|
export { getPageTokenImageMap, getAllowedImageEntries, openImage, generateGrid3Guid, createSettingsXml, createFileMapXml, getCommonDocumentsPath, findGrid3UserPaths, findGrid3HistoryDatabases, findGrid3Vocabularies, findGrid3UserHistory, findGrid3Users, isGrid3Installed, readGrid3History, readGrid3HistoryForUser, readAllGrid3History, type Grid3UserPath, type Grid3VocabularyPath, type Grid3HistoryEntry, } from './helpers';
|
|
20
20
|
export { parseSymbolReference, isSymbolReference, resolveSymbolReference, getAvailableSymbolLibraries, getSymbolLibraryInfo, extractSymbolReferences, analyzeSymbolUsage, createSymbolReference, getSymbolLibraryName, getSymbolPath, isKnownSymbolLibrary, getSymbolLibraryDisplayName, getDefaultGrid3Path, getSymbolLibrariesDir, getSymbolSearchIndexesDir, symbolReferenceToFilename, SYMBOL_LIBRARIES, type SymbolReference, type SymbolLibraryInfo, type SymbolResolutionOptions, type SymbolResolutionResult, type SymbolUsageStats, type SymbolLibraryName, } from './symbols';
|
|
21
21
|
export { getSymbolsDir, getSymbolSearchDir } from './symbols';
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* - Image resolution helpers
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.
|
|
14
|
-
exports.GRID3_COMMAND_CATEGORIES = exports.GRID3_CELL_TYPES = exports.GRID3_CELL_SHAPES = exports.GRID3_PLUGIN_IDS = exports.GRID3_COMMAND_IDS = exports.getSymbolSearchStats = exports.countLibrarySymbols = exports.getSearchSuggestions = exports.getAllSearchTerms = exports.getSymbolDisplayName = exports.getSymbolFilename = exports.searchSymbolsWithReferences = exports.searchSymbols = exports.loadSearchIndexes = exports.parsePixFile = exports.createSymbolManifest = exports.exportSymbolReferencesToCsv = exports.suggestExtractionStrategy = exports.analyzeSymbolExtraction = exports.convertToAstericsImage = exports.extractSymbolLibraryImage = exports.extractButtonImage = exports.parseImageSymbolReference = exports.isSymbolLibraryReference = exports.resolveGrid3CellImage = exports.getSymbolSearchDir = exports.getSymbolsDir = exports.SYMBOL_LIBRARIES = exports.symbolReferenceToFilename = exports.getSymbolSearchIndexesDir = exports.getSymbolLibrariesDir = exports.getDefaultGrid3Path = exports.getSymbolLibraryDisplayName = exports.isKnownSymbolLibrary = exports.getSymbolPath = exports.getSymbolLibraryName = exports.createSymbolReference = exports.analyzeSymbolUsage = exports.extractSymbolReferences = exports.getSymbolLibraryInfo = exports.getAvailableSymbolLibraries = exports.resolveSymbolReference = exports.isSymbolReference = void 0;
|
|
13
|
+
exports.readAllGrid3History = exports.readGrid3HistoryForUser = exports.readGrid3History = exports.isGrid3Installed = exports.findGrid3Users = exports.findGrid3UserHistory = exports.findGrid3Vocabularies = exports.findGrid3HistoryDatabases = exports.findGrid3UserPaths = exports.getCommonDocumentsPath = exports.createFileMapXml = exports.createSettingsXml = exports.generateGrid3Guid = exports.openImage = exports.getAllowedImageEntries = exports.getPageTokenImageMap = exports.resolveGridsetPasswordFromEnv = exports.getZipEntriesFromAdapter = exports.getZipEntriesWithPassword = exports.resolveGridsetPassword = exports.rgbaToHex = exports.hexToRgba = exports.lightenColor = exports.darkenColor = exports.ensureAlphaChannel = exports.Grid3CommandCategory = exports.GRID3_COMMANDS = exports.extractCommandParameters = exports.getAllPluginIds = exports.getAllCommandIds = exports.getCommandsByCategory = exports.getCommandsByPlugin = exports.getCommandDefinition = exports.detectCommand = exports.isRegularCell = exports.isAutoContentCell = exports.isLiveCell = exports.isWorkspaceCell = exports.getCellTypeDisplayName = exports.AUTOCONTENT_TYPES = exports.LIVECELL_TYPES = exports.WORKSPACE_TYPES = exports.Grid3CellType = exports.detectPluginCellType = exports.createCategoryStyle = exports.createDefaultStylesXml = exports.CATEGORY_STYLES = exports.DEFAULT_GRID3_STYLES = exports.SHAPE_NAMES = exports.CellBackgroundShape = void 0;
|
|
14
|
+
exports.GRID3_COMMAND_CATEGORIES = exports.GRID3_CELL_TYPES = exports.GRID3_CELL_SHAPES = exports.GRID3_PLUGIN_IDS = exports.GRID3_COMMAND_IDS = exports.getSymbolSearchStats = exports.countLibrarySymbols = exports.getSearchSuggestions = exports.getAllSearchTerms = exports.getSymbolDisplayName = exports.getSymbolFilename = exports.searchSymbolsWithReferences = exports.searchSymbols = exports.loadSearchIndexes = exports.parsePixFile = exports.createSymbolManifest = exports.exportSymbolReferencesToCsv = exports.suggestExtractionStrategy = exports.analyzeSymbolExtraction = exports.convertToAstericsImage = exports.extractSymbolLibraryImage = exports.extractButtonImage = exports.parseImageSymbolReference = exports.isSymbolLibraryReference = exports.resolveGrid3CellImage = exports.getSymbolSearchDir = exports.getSymbolsDir = exports.SYMBOL_LIBRARIES = exports.symbolReferenceToFilename = exports.getSymbolSearchIndexesDir = exports.getSymbolLibrariesDir = exports.getDefaultGrid3Path = exports.getSymbolLibraryDisplayName = exports.isKnownSymbolLibrary = exports.getSymbolPath = exports.getSymbolLibraryName = exports.createSymbolReference = exports.analyzeSymbolUsage = exports.extractSymbolReferences = exports.getSymbolLibraryInfo = exports.getAvailableSymbolLibraries = exports.resolveSymbolReference = exports.isSymbolReference = exports.parseSymbolReference = void 0;
|
|
15
15
|
// Style helpers
|
|
16
16
|
var styleHelpers_1 = require("./styleHelpers");
|
|
17
17
|
Object.defineProperty(exports, "CellBackgroundShape", { enumerable: true, get: function () { return styleHelpers_1.CellBackgroundShape; } });
|
|
@@ -59,6 +59,7 @@ Object.defineProperty(exports, "rgbaToHex", { enumerable: true, get: function ()
|
|
|
59
59
|
var password_1 = require("./password");
|
|
60
60
|
Object.defineProperty(exports, "resolveGridsetPassword", { enumerable: true, get: function () { return password_1.resolveGridsetPassword; } });
|
|
61
61
|
Object.defineProperty(exports, "getZipEntriesWithPassword", { enumerable: true, get: function () { return password_1.getZipEntriesWithPassword; } });
|
|
62
|
+
Object.defineProperty(exports, "getZipEntriesFromAdapter", { enumerable: true, get: function () { return password_1.getZipEntriesFromAdapter; } });
|
|
62
63
|
Object.defineProperty(exports, "resolveGridsetPasswordFromEnv", { enumerable: true, get: function () { return password_1.resolveGridsetPasswordFromEnv; } });
|
|
63
64
|
// Helper functions
|
|
64
65
|
var helpers_1 = require("./helpers");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type JSZip from 'jszip';
|
|
2
2
|
import { ProcessorOptions } from '../../core/baseProcessor';
|
|
3
3
|
import { ProcessorInput } from '../../utils/io';
|
|
4
|
+
import { type ZipAdapter } from '../../utils/zip';
|
|
4
5
|
/**
|
|
5
6
|
* Resolve the password to use for Grid3 archives.
|
|
6
7
|
* Preference order:
|
|
@@ -18,11 +19,11 @@ export declare function resolveGridsetPasswordFromEnv(): string | undefined;
|
|
|
18
19
|
* @param password - Optional password (kept for API compatibility, not used with JSZip)
|
|
19
20
|
* @returns Array of entry objects with name and data
|
|
20
21
|
*/
|
|
21
|
-
type ZipEntry = {
|
|
22
|
+
export type ZipEntry = {
|
|
22
23
|
name: string;
|
|
23
24
|
entryName: string;
|
|
24
25
|
dir: boolean;
|
|
25
26
|
getData: () => Promise<Uint8Array>;
|
|
26
27
|
};
|
|
27
28
|
export declare function getZipEntriesWithPassword(zip: JSZip, password?: string): ZipEntry[];
|
|
28
|
-
export
|
|
29
|
+
export declare function getZipEntriesFromAdapter(zip: ZipAdapter, password?: string): ZipEntry[];
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.resolveGridsetPassword = resolveGridsetPassword;
|
|
4
4
|
exports.resolveGridsetPasswordFromEnv = resolveGridsetPasswordFromEnv;
|
|
5
5
|
exports.getZipEntriesWithPassword = getZipEntriesWithPassword;
|
|
6
|
+
exports.getZipEntriesFromAdapter = getZipEntriesFromAdapter;
|
|
6
7
|
function getExtension(source) {
|
|
7
8
|
const index = source.lastIndexOf('.');
|
|
8
9
|
if (index === -1)
|
|
@@ -52,3 +53,14 @@ function getZipEntriesWithPassword(zip, password) {
|
|
|
52
53
|
});
|
|
53
54
|
return entries;
|
|
54
55
|
}
|
|
56
|
+
function getZipEntriesFromAdapter(zip, password) {
|
|
57
|
+
if (password) {
|
|
58
|
+
console.warn('Zip password support is handled at the archive level for .gridsetx files.');
|
|
59
|
+
}
|
|
60
|
+
return zip.listFiles().map((entryName) => ({
|
|
61
|
+
name: entryName,
|
|
62
|
+
entryName,
|
|
63
|
+
dir: false,
|
|
64
|
+
getData: () => zip.readFile(entryName),
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
@@ -9,6 +9,29 @@
|
|
|
9
9
|
* Note: Wordlists are only supported in Grid3 format. Other AAC formats
|
|
10
10
|
* do not have equivalent wordlist functionality.
|
|
11
11
|
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
12
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
36
|
exports.createWordlist = createWordlist;
|
|
14
37
|
exports.wordlistToXml = wordlistToXml;
|
|
@@ -16,7 +39,9 @@ exports.extractWordlists = extractWordlists;
|
|
|
16
39
|
exports.updateWordlist = updateWordlist;
|
|
17
40
|
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
18
41
|
const password_1 = require("./password");
|
|
42
|
+
const zip_1 = require("../../utils/zip");
|
|
19
43
|
const io_1 = require("../../utils/io");
|
|
44
|
+
const io_2 = require("../../utils/io");
|
|
20
45
|
/**
|
|
21
46
|
* Creates a WordList object from an array of words/phrases or a dictionary
|
|
22
47
|
*
|
|
@@ -105,55 +130,52 @@ function wordlistToXml(wordlist) {
|
|
|
105
130
|
async function extractWordlists(gridsetBuffer, password = (0, password_1.resolveGridsetPasswordFromEnv)()) {
|
|
106
131
|
const wordlists = new Map();
|
|
107
132
|
const parser = new fast_xml_parser_1.XMLParser();
|
|
108
|
-
let zip;
|
|
109
133
|
try {
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
const { zip } = await (0, zip_1.openZipFromInput)(gridsetBuffer);
|
|
135
|
+
const entries = (0, password_1.getZipEntriesFromAdapter)(zip, password);
|
|
136
|
+
// Process each grid file
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
if (entry.entryName.startsWith('Grids/') && entry.entryName.endsWith('grid.xml')) {
|
|
139
|
+
try {
|
|
140
|
+
const xmlContent = (0, io_2.decodeText)(await entry.getData());
|
|
141
|
+
const data = parser.parse(xmlContent);
|
|
142
|
+
const grid = data.Grid || data.grid;
|
|
143
|
+
if (!grid || !grid.WordList) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
// Extract grid name from path (e.g., "Grids/MyGrid/grid.xml" -> "MyGrid")
|
|
147
|
+
const match = entry.entryName.match(/^Grids\/([^/]+)\//);
|
|
148
|
+
const gridName = match ? match[1] : entry.entryName;
|
|
149
|
+
// Parse wordlist items
|
|
150
|
+
const wordlistData = grid.WordList;
|
|
151
|
+
const itemsContainer = wordlistData.Items || wordlistData.items;
|
|
152
|
+
if (!itemsContainer) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const itemArray = Array.isArray(itemsContainer.WordListItem)
|
|
156
|
+
? itemsContainer.WordListItem
|
|
157
|
+
: itemsContainer.WordListItem
|
|
158
|
+
? [itemsContainer.WordListItem]
|
|
159
|
+
: [];
|
|
160
|
+
const items = itemArray.map((item) => ({
|
|
161
|
+
text: item.Text?.s?.r || item.text?.s?.r || '',
|
|
162
|
+
image: item.Image || item.image || undefined,
|
|
163
|
+
partOfSpeech: item.PartOfSpeech || item.partOfSpeech || 'Unknown',
|
|
164
|
+
}));
|
|
165
|
+
if (items.length > 0) {
|
|
166
|
+
wordlists.set(gridName, { items });
|
|
167
|
+
}
|
|
136
168
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
? [itemsContainer.WordListItem]
|
|
141
|
-
: [];
|
|
142
|
-
const items = itemArray.map((item) => ({
|
|
143
|
-
text: item.Text?.s?.r || item.text?.s?.r || '',
|
|
144
|
-
image: item.Image || item.image || undefined,
|
|
145
|
-
partOfSpeech: item.PartOfSpeech || item.partOfSpeech || 'Unknown',
|
|
146
|
-
}));
|
|
147
|
-
if (items.length > 0) {
|
|
148
|
-
wordlists.set(gridName, { items });
|
|
169
|
+
catch (error) {
|
|
170
|
+
// Skip grids with parsing errors
|
|
171
|
+
console.warn(`Failed to extract wordlist from ${entry.entryName}:`, error);
|
|
149
172
|
}
|
|
150
173
|
}
|
|
151
|
-
catch (error) {
|
|
152
|
-
// Skip grids with parsing errors
|
|
153
|
-
console.warn(`Failed to extract wordlist from ${entry.entryName}:`, error);
|
|
154
|
-
}
|
|
155
174
|
}
|
|
156
175
|
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
throw new Error(`Invalid gridset buffer: ${error.message}`);
|
|
178
|
+
}
|
|
157
179
|
return wordlists;
|
|
158
180
|
}
|
|
159
181
|
/**
|
|
@@ -178,16 +200,45 @@ async function updateWordlist(gridsetBuffer, gridName, wordlist, password = (0,
|
|
|
178
200
|
indentBy: ' ',
|
|
179
201
|
suppressEmptyNode: false,
|
|
180
202
|
});
|
|
181
|
-
let
|
|
203
|
+
let entries;
|
|
204
|
+
let saveZip = null;
|
|
205
|
+
let updateEntry = null;
|
|
182
206
|
try {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
207
|
+
if ((0, io_1.isNodeRuntime)()) {
|
|
208
|
+
const AdmZip = (0, io_1.getNodeRequire)()('adm-zip');
|
|
209
|
+
const zip = new AdmZip(Buffer.from(gridsetBuffer));
|
|
210
|
+
entries = zip.getEntries().map((entry) => ({
|
|
211
|
+
entryName: entry.entryName,
|
|
212
|
+
getData: () => Promise.resolve(entry.getData()),
|
|
213
|
+
}));
|
|
214
|
+
updateEntry = (entryName, xml) => {
|
|
215
|
+
zip.addFile(entryName, Buffer.from(xml, 'utf8'));
|
|
216
|
+
};
|
|
217
|
+
saveZip = () => Promise.resolve(zip.toBuffer());
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
const module = await Promise.resolve().then(() => __importStar(require('jszip')));
|
|
221
|
+
const JSZip = module.default || module;
|
|
222
|
+
const zip = await JSZip.loadAsync(gridsetBuffer);
|
|
223
|
+
entries = (0, password_1.getZipEntriesFromAdapter)({
|
|
224
|
+
listFiles: () => Object.keys(zip.files),
|
|
225
|
+
readFile: async (name) => {
|
|
226
|
+
const file = zip.file(name);
|
|
227
|
+
if (!file) {
|
|
228
|
+
throw new Error(`Zip entry not found: ${name}`);
|
|
229
|
+
}
|
|
230
|
+
return file.async('uint8array');
|
|
231
|
+
},
|
|
232
|
+
}, password);
|
|
233
|
+
updateEntry = (entryName, xml) => {
|
|
234
|
+
zip.file(entryName, xml, { binary: false });
|
|
235
|
+
};
|
|
236
|
+
saveZip = async () => zip.generateAsync({ type: 'uint8array' });
|
|
237
|
+
}
|
|
186
238
|
}
|
|
187
239
|
catch (error) {
|
|
188
240
|
throw new Error(`Invalid gridset buffer: ${error.message}`);
|
|
189
241
|
}
|
|
190
|
-
const entries = (0, password_1.getZipEntriesWithPassword)(zip, password);
|
|
191
242
|
let found = false;
|
|
192
243
|
// Find and update the grid
|
|
193
244
|
for (const entry of entries) {
|
|
@@ -196,7 +247,7 @@ async function updateWordlist(gridsetBuffer, gridName, wordlist, password = (0,
|
|
|
196
247
|
const currentGridName = match ? match[1] : null;
|
|
197
248
|
if (currentGridName === gridName) {
|
|
198
249
|
try {
|
|
199
|
-
const xmlContent = (0,
|
|
250
|
+
const xmlContent = (0, io_2.decodeText)(await entry.getData());
|
|
200
251
|
const data = parser.parse(xmlContent);
|
|
201
252
|
const grid = data.Grid || data.grid;
|
|
202
253
|
if (!grid) {
|
|
@@ -222,7 +273,9 @@ async function updateWordlist(gridsetBuffer, gridName, wordlist, password = (0,
|
|
|
222
273
|
};
|
|
223
274
|
// Rebuild the XML
|
|
224
275
|
const updatedXml = builder.build(data);
|
|
225
|
-
|
|
276
|
+
if (updateEntry) {
|
|
277
|
+
updateEntry(entry.entryName, updatedXml);
|
|
278
|
+
}
|
|
226
279
|
found = true;
|
|
227
280
|
}
|
|
228
281
|
catch (error) {
|
|
@@ -235,5 +288,8 @@ async function updateWordlist(gridsetBuffer, gridName, wordlist, password = (0,
|
|
|
235
288
|
if (!found) {
|
|
236
289
|
throw new Error(`Grid "${gridName}" not found in gridset`);
|
|
237
290
|
}
|
|
238
|
-
|
|
291
|
+
if (!saveZip) {
|
|
292
|
+
throw new Error('Failed to serialize updated gridset.');
|
|
293
|
+
}
|
|
294
|
+
return await saveZip();
|
|
239
295
|
}
|