@willwade/aac-processors 0.2.20 → 0.3.0
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 +1 -0
- package/dist/browser/index.browser.js +17 -1
- package/dist/browser/processors/gotalkNow/nsKeyed.js +101 -0
- package/dist/browser/processors/gotalkNowProcessor.js +463 -0
- package/dist/browser/processors/obfProcessor.js +6 -2
- package/dist/browser/validation/gridsetValidator.js +54 -198
- package/dist/cli/index.js +3 -0
- package/dist/core/analyze.js +5 -0
- package/dist/core/treeStructure.d.ts +5 -0
- package/dist/gotalkNow.d.ts +6 -0
- package/dist/gotalkNow.js +11 -0
- package/dist/index.browser.d.ts +1 -0
- package/dist/index.browser.js +19 -2
- package/dist/index.node.d.ts +1 -0
- package/dist/index.node.js +6 -1
- package/dist/processors/gotalkNow/nsKeyed.d.ts +27 -0
- package/dist/processors/gotalkNow/nsKeyed.js +105 -0
- package/dist/processors/gotalkNowProcessor.d.ts +51 -0
- package/dist/processors/gotalkNowProcessor.js +469 -0
- package/dist/processors/index.d.ts +1 -0
- package/dist/processors/index.js +3 -1
- package/dist/processors/obfProcessor.js +6 -2
- package/dist/validation/gridsetValidator.d.ts +8 -15
- package/dist/validation/gridsetValidator.js +54 -198
- package/package.json +5 -1
|
@@ -55,12 +55,12 @@ class GridsetValidator extends baseValidator_1.BaseValidator {
|
|
|
55
55
|
if (name.endsWith('.gridset') || name.endsWith('.gridsetx')) {
|
|
56
56
|
return true;
|
|
57
57
|
}
|
|
58
|
-
// Try to parse as XML and check for
|
|
58
|
+
// Try to parse as XML and check for a Grid 3 <Grid> root element
|
|
59
59
|
try {
|
|
60
60
|
const contentStr = typeof content === 'string' ? content : (0, io_1.decodeText)((0, io_1.toUint8Array)(content));
|
|
61
61
|
const parser = new xml2js.Parser();
|
|
62
62
|
const result = await parser.parseStringPromise(contentStr);
|
|
63
|
-
return result && (result.
|
|
63
|
+
return !!(result && (result.Grid || result.grid));
|
|
64
64
|
}
|
|
65
65
|
catch {
|
|
66
66
|
return false;
|
|
@@ -104,7 +104,7 @@ class GridsetValidator extends baseValidator_1.BaseValidator {
|
|
|
104
104
|
return content[0] === 0x50 && content[1] === 0x4b && content[2] === 0x03 && content[3] === 0x04;
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
|
-
* Validate a single
|
|
107
|
+
* Validate a single grid.xml file (exploded/non-zip format)
|
|
108
108
|
*/
|
|
109
109
|
async validateSingleXml(content, filename, _filesize) {
|
|
110
110
|
let xmlObj = null;
|
|
@@ -120,49 +120,57 @@ class GridsetValidator extends baseValidator_1.BaseValidator {
|
|
|
120
120
|
});
|
|
121
121
|
if (!xmlObj)
|
|
122
122
|
return;
|
|
123
|
-
await this.add_check('xml_structure', '
|
|
124
|
-
if (!xmlObj.
|
|
125
|
-
this.err('missing root
|
|
123
|
+
await this.add_check('xml_structure', 'Grid root element', async () => {
|
|
124
|
+
if (!xmlObj.Grid && !xmlObj.grid) {
|
|
125
|
+
this.err('missing root Grid element', true);
|
|
126
126
|
}
|
|
127
127
|
});
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
await this.
|
|
128
|
+
const grid = xmlObj.Grid || xmlObj.grid;
|
|
129
|
+
if (grid) {
|
|
130
|
+
await this.validateGridStructure(grid, filename);
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
/**
|
|
134
134
|
* Validate a ZIP archive (.gridset)
|
|
135
|
+
*
|
|
136
|
+
* Real Grid 3 archives do NOT contain a top-level gridset.xml. The required
|
|
137
|
+
* structure is: Settings0/settings.xml + FileMap.xml + Grids/<name>/grid.xml
|
|
138
|
+
* (+ optional Styles, images, thumbnails).
|
|
135
139
|
*/
|
|
136
|
-
async validateZipArchive(content,
|
|
140
|
+
async validateZipArchive(content, _filename, _filesize) {
|
|
137
141
|
const zip = await this._options.zipAdapter(content);
|
|
138
142
|
const entries = zip.listFiles();
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (
|
|
143
|
-
this.err('
|
|
143
|
+
// --- Required: at least one Grids/<name>/grid.xml ---
|
|
144
|
+
const gridEntries = entries.filter((e) => /^Grids\/.+\/grid\.xml$/i.test(e));
|
|
145
|
+
await this.add_check('grid_xml_presence', 'Grids/*/grid.xml presence', async () => {
|
|
146
|
+
if (gridEntries.length === 0) {
|
|
147
|
+
this.err('No Grids/<name>/grid.xml found in archive', true);
|
|
144
148
|
}
|
|
145
|
-
|
|
149
|
+
});
|
|
150
|
+
// Validate the <Grid> structure of each grid.xml (sample up to 5)
|
|
151
|
+
for (let i = 0; i < Math.min(gridEntries.length, 5); i++) {
|
|
152
|
+
const entry = gridEntries[i];
|
|
153
|
+
await this.add_check(`grid_xml[${entry}]`, `grid structure: ${entry}`, async () => {
|
|
146
154
|
try {
|
|
147
|
-
const
|
|
155
|
+
const gridXml = await zip.readFile(entry);
|
|
148
156
|
const parser = new xml2js.Parser();
|
|
149
|
-
const xmlObj = await parser.parseStringPromise(
|
|
150
|
-
const
|
|
151
|
-
if (!
|
|
152
|
-
this.err(
|
|
157
|
+
const xmlObj = await parser.parseStringPromise(gridXml);
|
|
158
|
+
const grid = xmlObj.Grid || xmlObj.grid;
|
|
159
|
+
if (!grid) {
|
|
160
|
+
this.err(`${entry}: missing root <Grid> element`);
|
|
153
161
|
}
|
|
154
162
|
else {
|
|
155
|
-
await this.
|
|
163
|
+
await this.validateGridStructure(grid, entry);
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
166
|
catch (e) {
|
|
159
|
-
this.err(
|
|
167
|
+
this.err(`${entry}: failed to parse XML: ${e.message}`);
|
|
160
168
|
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
//
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
// --- Recommended: Settings0/settings.xml ---
|
|
164
172
|
await this.add_check('settings_xml_presence', 'settings.xml presence', async () => {
|
|
165
|
-
const settingsEntry = entries.find((e) => e.toLowerCase() === 'settings.xml');
|
|
173
|
+
const settingsEntry = entries.find((e) => e.toLowerCase() === 'settings.xml' || e.toLowerCase().endsWith('/settings.xml'));
|
|
166
174
|
if (!settingsEntry) {
|
|
167
175
|
this.warn('Missing settings.xml in archive (required for full metadata)');
|
|
168
176
|
}
|
|
@@ -187,188 +195,36 @@ class GridsetValidator extends baseValidator_1.BaseValidator {
|
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
197
|
});
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// Check for required elements
|
|
196
|
-
await this.add_check('gridset_id', 'gridset id', async () => {
|
|
197
|
-
const id = gridset.$.id || gridset.$.Id;
|
|
198
|
-
if (!id) {
|
|
199
|
-
this.warn('gridset should have an id attribute');
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
await this.add_check('gridset_name', 'gridset name', async () => {
|
|
203
|
-
const name = gridset.$.name || gridset.$.Name || gridset.name?.[0];
|
|
204
|
-
if (!name) {
|
|
205
|
-
this.warn('gridset should have a name attribute or element');
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
// Check for pages
|
|
209
|
-
await this.add_check('pages', 'pages element', async () => {
|
|
210
|
-
if (!gridset.pages && !gridset.Pages) {
|
|
211
|
-
this.err('gridset must have a pages element');
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
const pages = gridset.pages || gridset.Pages;
|
|
215
|
-
if (!pages[0] || !Array.isArray(pages[0].page)) {
|
|
216
|
-
this.warn('pages should contain at least one page element');
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
// Validate individual pages
|
|
221
|
-
const pages = gridset.pages?.[0] || gridset.Pages?.[0];
|
|
222
|
-
if (pages && Array.isArray(pages.page)) {
|
|
223
|
-
await this.add_check('page_count', 'page count', async () => {
|
|
224
|
-
if (pages.page.length === 0) {
|
|
225
|
-
this.err('gridset must contain at least one page');
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
for (let i = 0; i < Math.min(pages.page.length, 10); i++) {
|
|
229
|
-
// Limit to first 10 pages to avoid excessive validation
|
|
230
|
-
const page = pages.page[i];
|
|
231
|
-
await this.validatePage(page, i);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
// Check for fixedCellSize
|
|
235
|
-
await this.add_check('fixed_cell_size', 'fixedCellSize element', async () => {
|
|
236
|
-
const fixedSize = gridset.fixedCellSize || gridset.FixedCellSize;
|
|
237
|
-
if (!fixedSize) {
|
|
238
|
-
this.warn('gridset should have a fixedCellSize element for consistency');
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
// Validate fixedCellSize structure
|
|
242
|
-
const size = fixedSize[0];
|
|
243
|
-
if (size) {
|
|
244
|
-
const width = size.$.width || size.$.Width;
|
|
245
|
-
const height = size.$.height || size.$.Height;
|
|
246
|
-
if (!width || !height) {
|
|
247
|
-
this.warn('fixedCellSize should have both width and height attributes');
|
|
248
|
-
}
|
|
249
|
-
else if (isNaN(parseInt(width)) || isNaN(parseInt(height))) {
|
|
250
|
-
this.err('fixedCellSize width and height must be valid numbers');
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
// Check for styles
|
|
256
|
-
await this.add_check('styles', 'styles element', async () => {
|
|
257
|
-
const styles = gridset.styles || gridset.Styles;
|
|
258
|
-
if (!styles) {
|
|
259
|
-
this.warn('gridset should have a styles element for consistent formatting');
|
|
198
|
+
// --- Recommended: FileMap.xml (used for image resolution) ---
|
|
199
|
+
await this.add_check('filemap_presence', 'FileMap.xml presence', async () => {
|
|
200
|
+
const filemapEntry = entries.find((e) => e.toLowerCase() === 'filemap.xml');
|
|
201
|
+
if (!filemapEntry) {
|
|
202
|
+
this.warn('Missing FileMap.xml in archive (used for image resolution)');
|
|
260
203
|
}
|
|
261
204
|
});
|
|
262
205
|
}
|
|
263
206
|
/**
|
|
264
|
-
* Validate a
|
|
207
|
+
* Validate a real Grid 3 <Grid> element (from grid.xml).
|
|
208
|
+
* Checks for GridGuid, Cells, and basic cell structure.
|
|
265
209
|
*/
|
|
266
|
-
async
|
|
267
|
-
await this.add_check(
|
|
268
|
-
const
|
|
269
|
-
if (!
|
|
270
|
-
this.
|
|
210
|
+
async validateGridStructure(grid, label) {
|
|
211
|
+
await this.add_check(`${label}_gridguid`, 'GridGuid', async () => {
|
|
212
|
+
const guid = grid.GridGuid || grid.gridGuid;
|
|
213
|
+
if (!guid) {
|
|
214
|
+
this.warn(`${label}: missing <GridGuid> element`);
|
|
271
215
|
}
|
|
272
216
|
});
|
|
273
|
-
await this.add_check(
|
|
274
|
-
const
|
|
275
|
-
if (!name) {
|
|
276
|
-
this.warn(`page ${index} should have a name`);
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
// Check for cells
|
|
280
|
-
await this.add_check(`page[${index}]_cells`, `page ${index} cells`, async () => {
|
|
281
|
-
const cells = page.cells || page.Cells;
|
|
217
|
+
await this.add_check(`${label}_cells`, 'Cells element', async () => {
|
|
218
|
+
const cells = grid.Cells || grid.cells;
|
|
282
219
|
if (!cells) {
|
|
283
|
-
this.warn(
|
|
220
|
+
this.warn(`${label}: missing <Cells> element`);
|
|
221
|
+
return;
|
|
284
222
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
this.warn(`page ${index} should contain at least one cell`);
|
|
289
|
-
}
|
|
223
|
+
const cellArray = cells[0]?.Cell || cells[0]?.cell;
|
|
224
|
+
if (!cellArray || !Array.isArray(cellArray) || cellArray.length === 0) {
|
|
225
|
+
this.warn(`${label}: <Cells> should contain at least one <Cell>`);
|
|
290
226
|
}
|
|
291
227
|
});
|
|
292
|
-
// Validate cells if present
|
|
293
|
-
const cells = page.cells?.[0] || page.Cells?.[0];
|
|
294
|
-
if (cells) {
|
|
295
|
-
const cellArray = cells.cell || cells.Cell;
|
|
296
|
-
if (Array.isArray(cellArray) && cellArray.length > 0) {
|
|
297
|
-
// Sample a few cells to validate
|
|
298
|
-
const sampleSize = Math.min(cellArray.length, 5);
|
|
299
|
-
for (let i = 0; i < sampleSize; i++) {
|
|
300
|
-
await this.validateCell(cellArray[i], index, i);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Validate a single cell
|
|
307
|
-
*/
|
|
308
|
-
async validateCell(cell, pageIdx, cellIdx) {
|
|
309
|
-
await this.add_check(`page[${pageIdx}]_cell[${cellIdx}]_id`, `cell id`, async () => {
|
|
310
|
-
const id = cell.$.id || cell.$.Id;
|
|
311
|
-
if (!id) {
|
|
312
|
-
this.warn(`cell ${cellIdx} on page ${pageIdx} is missing id attribute`);
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
await this.add_check(`page[${pageIdx}]_cell[${cellIdx}]_content`, `cell content`, async () => {
|
|
316
|
-
const label = cell.$.label || cell.$.Label;
|
|
317
|
-
const image = cell.$.image || cell.$.Image;
|
|
318
|
-
if (!label && !image) {
|
|
319
|
-
this.warn(`cell ${cellIdx} on page ${pageIdx} should have a label or image`);
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
// Validate scan block number (Grid 3 attribute)
|
|
323
|
-
await this.add_check(`page[${pageIdx}]_cell[${cellIdx}]_scanblock`, `cell scan block`, async () => {
|
|
324
|
-
const scanBlock = cell.$.scanBlock || cell.$.ScanBlock;
|
|
325
|
-
if (scanBlock !== undefined) {
|
|
326
|
-
const blockNum = parseInt(scanBlock, 10);
|
|
327
|
-
if (isNaN(blockNum) || blockNum < 1 || blockNum > 8) {
|
|
328
|
-
this.err(`cell ${cellIdx} on page ${pageIdx} has invalid scanBlock value: ${scanBlock} (must be 1-8)`, false);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
// Check for color attributes
|
|
333
|
-
const backgroundColor = cell.$.backgroundColor || cell.$.BackgroundColor;
|
|
334
|
-
const _color = cell.$.color || cell.$.Color;
|
|
335
|
-
if (backgroundColor) {
|
|
336
|
-
await this.add_check(`page[${pageIdx}]_cell[${cellIdx}]_bg_color`, `cell background color`, async () => {
|
|
337
|
-
// Grid3 colors can be in various formats: named colors, hex, ARGB
|
|
338
|
-
// We just check it's not empty
|
|
339
|
-
if (backgroundColor.length === 0) {
|
|
340
|
-
this.warn(`cell ${cellIdx} has empty background color`);
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
// Check for valid jump references
|
|
345
|
-
const jump = cell.$.jump || cell.$.Jump;
|
|
346
|
-
if (jump) {
|
|
347
|
-
await this.add_check(`page[${pageIdx}]_cell[${cellIdx}]_jump`, `cell jump reference`, async () => {
|
|
348
|
-
if (typeof jump !== 'string' || jump.length === 0) {
|
|
349
|
-
this.warn(`cell ${cellIdx} has invalid jump reference`);
|
|
350
|
-
}
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* Validate color format (Grid3 uses ARGB format)
|
|
356
|
-
*/
|
|
357
|
-
isValidGrid3Color(color) {
|
|
358
|
-
if (!color || color.length === 0)
|
|
359
|
-
return false;
|
|
360
|
-
// Named colors are valid
|
|
361
|
-
if (/^[a-zA-Z]+$/.test(color))
|
|
362
|
-
return true;
|
|
363
|
-
// ARGB format: #AARRGGBB or #RRGGBB
|
|
364
|
-
if (color.startsWith('#')) {
|
|
365
|
-
return color.length === 7 || color.length === 9;
|
|
366
|
-
}
|
|
367
|
-
// RGB format: rgb(r,g,b) or rgba(r,g,b,a)
|
|
368
|
-
if (color.startsWith('rgb')) {
|
|
369
|
-
return true; // Simplified check
|
|
370
|
-
}
|
|
371
|
-
return false;
|
|
372
228
|
}
|
|
373
229
|
}
|
|
374
230
|
exports.GridsetValidator = GridsetValidator;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@willwade/aac-processors",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A comprehensive TypeScript library for processing AAC (Augmentative and Alternative Communication) file formats with translation support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"browser": "dist/browser/index.browser.js",
|
|
@@ -60,6 +60,10 @@
|
|
|
60
60
|
"types": "./dist/astericsGrid.d.ts",
|
|
61
61
|
"default": "./dist/astericsGrid.js"
|
|
62
62
|
},
|
|
63
|
+
"./gotalkNow": {
|
|
64
|
+
"types": "./dist/gotalkNow.d.ts",
|
|
65
|
+
"default": "./dist/gotalkNow.js"
|
|
66
|
+
},
|
|
63
67
|
"./translation": {
|
|
64
68
|
"types": "./dist/translation.d.ts",
|
|
65
69
|
"default": "./dist/translation.js"
|