@willwade/aac-processors 0.2.10 → 0.2.12
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 +4 -1
- package/dist/browser/processors/gridset/cellHelpers.js +97 -0
- package/dist/browser/processors/gridset/gridCalculations.js +61 -0
- package/dist/browser/processors/gridset/helpers.js +4 -1
- package/dist/browser/processors/gridset/xmlFormatter.js +84 -0
- package/dist/browser/processors/gridsetProcessor.js +68 -92
- package/dist/browser/utils/sqlite.js +6 -2
- package/dist/processors/gridset/cellHelpers.d.ts +66 -0
- package/dist/processors/gridset/cellHelpers.js +102 -0
- package/dist/processors/gridset/gridCalculations.d.ts +45 -0
- package/dist/processors/gridset/gridCalculations.js +65 -0
- package/dist/processors/gridset/helpers.js +4 -1
- package/dist/processors/gridset/wordlistHelpers.js +9 -7
- package/dist/processors/gridset/xmlFormatter.d.ts +43 -0
- package/dist/processors/gridset/xmlFormatter.js +89 -0
- package/dist/processors/gridsetProcessor.js +68 -92
- package/dist/utils/sqlite.js +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ npm install @willwade/aac-processors
|
|
|
13
13
|
## Dual Build Targets
|
|
14
14
|
|
|
15
15
|
### Node.js (default)
|
|
16
|
+
|
|
16
17
|
Full feature set, including filesystem access, SQLite-backed formats, and
|
|
17
18
|
ZIP/encrypted formats.
|
|
18
19
|
|
|
@@ -27,6 +28,7 @@ const texts = await snap.extractTexts('board.sps');
|
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
### Browser
|
|
31
|
+
|
|
30
32
|
Browser-safe entry that avoids Node-only dependencies. It expects `Buffer`,
|
|
31
33
|
`Uint8Array`, or `ArrayBuffer` inputs rather than file paths.
|
|
32
34
|
|
|
@@ -82,7 +84,8 @@ const translations = new Map([
|
|
|
82
84
|
|
|
83
85
|
await processor.processTexts('board.dot', translations, 'board-es.dot');
|
|
84
86
|
```
|
|
85
|
-
|
|
87
|
+
|
|
88
|
+
NB: Please use [https://aactools.co.uk](https://aactools.co.uk) for a far more comphrensive translation logic - where we do far far more than this...
|
|
86
89
|
|
|
87
90
|
## Documentation
|
|
88
91
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid3 Cell Helpers
|
|
3
|
+
*
|
|
4
|
+
* Utilities for working with Grid 3 cells, including finding button positions
|
|
5
|
+
* and calculating cell spans in grid layouts.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Find button position with span information
|
|
9
|
+
*
|
|
10
|
+
* Searches the page's grid layout for a button and calculates its position
|
|
11
|
+
* and span (how many columns/rows it occupies).
|
|
12
|
+
*
|
|
13
|
+
* @param page - The AAC page containing the button
|
|
14
|
+
* @param button - The button to locate
|
|
15
|
+
* @param fallbackIndex - Index to use if button not found in grid
|
|
16
|
+
* @returns Position and span information for the button
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const position = findButtonPosition(page, button, 0);
|
|
20
|
+
* console.log(`Button at ${position.x},${position.y} spans ${position.columnSpan}x${position.rowSpan}`);
|
|
21
|
+
*/
|
|
22
|
+
export function findButtonPosition(page, button, fallbackIndex) {
|
|
23
|
+
if (page.grid && page.grid.length > 0) {
|
|
24
|
+
// Search for button in grid layout and calculate span
|
|
25
|
+
for (let y = 0; y < page.grid.length; y++) {
|
|
26
|
+
for (let x = 0; x < page.grid[y].length; x++) {
|
|
27
|
+
const current = page.grid[y][x];
|
|
28
|
+
if (current && current.id === button.id) {
|
|
29
|
+
// Calculate span by checking how far the same button extends
|
|
30
|
+
let columnSpan = 1;
|
|
31
|
+
let rowSpan = 1;
|
|
32
|
+
// Check column span (rightward)
|
|
33
|
+
while (x + columnSpan < page.grid[y].length) {
|
|
34
|
+
const right = page.grid[y][x + columnSpan];
|
|
35
|
+
if (right && right.id === button.id) {
|
|
36
|
+
columnSpan++;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Check row span (downward)
|
|
43
|
+
while (y + rowSpan < page.grid.length) {
|
|
44
|
+
const below = page.grid[y + rowSpan][x];
|
|
45
|
+
if (below && below.id === button.id) {
|
|
46
|
+
rowSpan++;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { x, y, columnSpan, rowSpan };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Fallback positioning
|
|
58
|
+
const gridCols = page.grid?.[0]?.length || Math.ceil(Math.sqrt(page.buttons.length));
|
|
59
|
+
return {
|
|
60
|
+
x: fallbackIndex % gridCols,
|
|
61
|
+
y: Math.floor(fallbackIndex / gridCols),
|
|
62
|
+
columnSpan: 1,
|
|
63
|
+
rowSpan: 1,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Calculate cell position key for Maps and Sets
|
|
68
|
+
*
|
|
69
|
+
* Creates a string key from X and Y coordinates for use as a Map key or Set entry.
|
|
70
|
+
*
|
|
71
|
+
* @param x - X coordinate
|
|
72
|
+
* @param y - Y coordinate
|
|
73
|
+
* @returns String key in format "x,y"
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const key = cellPositionKey(5, 3);
|
|
77
|
+
* console.log(key); // "5,3"
|
|
78
|
+
*/
|
|
79
|
+
export function cellPositionKey(x, y) {
|
|
80
|
+
return `${x},${y}`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Parse cell position key
|
|
84
|
+
*
|
|
85
|
+
* Extracts X and Y coordinates from a position key string.
|
|
86
|
+
*
|
|
87
|
+
* @param key - Position key in format "x,y"
|
|
88
|
+
* @returns Object with x and y properties
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* const pos = parseCellPositionKey("5,3");
|
|
92
|
+
* console.log(pos); // { x: 5, y: 3 }
|
|
93
|
+
*/
|
|
94
|
+
export function parseCellPositionKey(key) {
|
|
95
|
+
const [x, y] = key.split(',').map(Number);
|
|
96
|
+
return { x, y };
|
|
97
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid3 Grid Calculations
|
|
3
|
+
*
|
|
4
|
+
* Utilities for calculating grid dimensions and definitions
|
|
5
|
+
* based on page layout and button count.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Calculate column definitions based on page layout
|
|
9
|
+
*
|
|
10
|
+
* Analyzes the page's grid structure to determine the number of columns.
|
|
11
|
+
* If no grid exists, estimates from button count.
|
|
12
|
+
*
|
|
13
|
+
* @param page - The AAC page to analyze
|
|
14
|
+
* @returns Column definitions object for Grid 3 XML
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const columns = calculateColumnDefinitions(page);
|
|
18
|
+
* // Returns: { ColumnDefinition: [{}, {}, {}, {}] } for 4 columns
|
|
19
|
+
*/
|
|
20
|
+
export function calculateColumnDefinitions(page) {
|
|
21
|
+
let maxCols = 4; // Default minimum
|
|
22
|
+
if (page.grid && page.grid.length > 0) {
|
|
23
|
+
maxCols = Math.max(maxCols, page.grid[0]?.length || 0);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// Fallback: estimate from button count
|
|
27
|
+
maxCols = Math.max(4, Math.ceil(Math.sqrt(page.buttons.length)));
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
ColumnDefinition: Array(maxCols).fill({}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Calculate row definitions based on page layout
|
|
35
|
+
*
|
|
36
|
+
* Analyzes the page's grid structure to determine the number of rows.
|
|
37
|
+
* If no grid exists, estimates from button count.
|
|
38
|
+
*
|
|
39
|
+
* @param page - The AAC page to analyze
|
|
40
|
+
* @param addWorkspaceOffset - Whether to add 1 row for workspace (default: false)
|
|
41
|
+
* @returns Row definitions object for Grid 3 XML
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const rows = calculateRowDefinitions(page, false);
|
|
45
|
+
* // Returns: { RowDefinition: [{}, {}, {}, {}] } for 4 rows
|
|
46
|
+
*/
|
|
47
|
+
export function calculateRowDefinitions(page, addWorkspaceOffset = false) {
|
|
48
|
+
let maxRows = 4; // Default minimum
|
|
49
|
+
const offset = addWorkspaceOffset ? 1 : 0;
|
|
50
|
+
if (page.grid && page.grid.length > 0) {
|
|
51
|
+
maxRows = Math.max(maxRows, page.grid.length + offset);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Fallback: estimate from button count
|
|
55
|
+
const estimatedCols = Math.ceil(Math.sqrt(page.buttons.length));
|
|
56
|
+
maxRows = Math.max(4, Math.ceil(page.buttons.length / estimatedCols)) + offset;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
RowDefinition: Array(maxRows).fill({}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -149,7 +149,10 @@ export function getCommonDocumentsPath() {
|
|
|
149
149
|
// Query registry for Common Documents path
|
|
150
150
|
const child_process = getNodeRequire()('child_process');
|
|
151
151
|
const command = 'REG.EXE QUERY "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders" /V "Common Documents"';
|
|
152
|
-
const output = child_process.execSync(command, {
|
|
152
|
+
const output = child_process.execSync(command, {
|
|
153
|
+
encoding: 'utf-8',
|
|
154
|
+
windowsHide: true,
|
|
155
|
+
});
|
|
153
156
|
// Parse the output to extract the path
|
|
154
157
|
const match = output.match(/Common Documents\s+REG_SZ\s+(.+)/);
|
|
155
158
|
if (match && match[1]) {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid3 XML Formatter
|
|
3
|
+
*
|
|
4
|
+
* Utilities for formatting XML to match Grid 3's specific requirements.
|
|
5
|
+
* Grid 3 has strict formatting requirements including line endings, self-closing
|
|
6
|
+
* tag spacing, and specific tag expansion rules.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Tags that Grid 3 requires in full opening/closing format instead of self-closing
|
|
10
|
+
* Grid 3 cannot parse <AudioDescription /> - it requires <AudioDescription></AudioDescription>
|
|
11
|
+
*/
|
|
12
|
+
const TAGS_NEEDING_EXPANSION = ['AudioDescription', 'VideoDescription'];
|
|
13
|
+
/**
|
|
14
|
+
* Format XML string to match Grid 3's requirements
|
|
15
|
+
*
|
|
16
|
+
* Grid 3 requires specific formatting:
|
|
17
|
+
* - Windows line endings (\r\n)
|
|
18
|
+
* - Space before /> in self-closing tags: <Element /> not <Element/>
|
|
19
|
+
* - Plain apostrophes instead of '
|
|
20
|
+
* - Specific tags expanded to full opening/closing format
|
|
21
|
+
* - CDATA for empty/whitespace captions and <r> tags
|
|
22
|
+
*
|
|
23
|
+
* @param xml - The XML string to format
|
|
24
|
+
* @returns Formatted XML string compatible with Grid 3
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const formatted = formatGrid3Xml('<Grid><Cell X="0"/></Grid>');
|
|
28
|
+
* // Returns: '<Grid>\r\n<Cell X="0" />\r\n</Grid>'
|
|
29
|
+
*/
|
|
30
|
+
export function formatGrid3Xml(xml) {
|
|
31
|
+
let formatted = xml;
|
|
32
|
+
// Convert Unix line endings to Windows (\r\n) for Grid 3 compatibility
|
|
33
|
+
formatted = formatted.replace(/\n/g, '\r\n');
|
|
34
|
+
// Add space before /> in self-closing tags to match Grid 3's expected format
|
|
35
|
+
// Grid 3 original files use <Element /> not <Element/>
|
|
36
|
+
formatted = formatted.replace(/<(\w+)([^>]*)\/>/g, '<$1$2 />');
|
|
37
|
+
// Decode XML entities back to plain text to match Grid 3's expected format
|
|
38
|
+
// Grid 3 expects plain apostrophes, not '
|
|
39
|
+
formatted = formatted.replace(/'/g, "'");
|
|
40
|
+
formatted = formatted.replace(/"/g, '"');
|
|
41
|
+
formatted = formatted.replace(/</g, '<');
|
|
42
|
+
formatted = formatted.replace(/>/g, '>');
|
|
43
|
+
// Expand only specific self-closing tags that Grid 3 requires in full opening/closing format
|
|
44
|
+
// This must be done AFTER adding spaces, so we need to match the format with spaces
|
|
45
|
+
for (const tag of TAGS_NEEDING_EXPANSION) {
|
|
46
|
+
formatted = formatted.replace(new RegExp(`<${tag}(\\s+[^>]*)? />`, 'g'), `<${tag}$1></${tag}>`);
|
|
47
|
+
}
|
|
48
|
+
return formatted;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Format empty/whitespace captions with CDATA for Grid 3 compatibility
|
|
52
|
+
*
|
|
53
|
+
* Grid 3 requires <![CDATA[ ]]> for empty captions, not plain text.
|
|
54
|
+
* Also handles <r> tags which need CDATA for spaces to prevent stripping.
|
|
55
|
+
*
|
|
56
|
+
* @param xml - The XML string to format
|
|
57
|
+
* @returns XML string with CDATA-wrapped empty content
|
|
58
|
+
*/
|
|
59
|
+
export function formatEmptyCaptionsWithCdata(xml) {
|
|
60
|
+
let formatted = xml;
|
|
61
|
+
// Convert empty/whitespace captions to CDATA format for Grid 3 compatibility
|
|
62
|
+
// Grid 3 requires <![CDATA[ ]]> for empty captions, not plain text
|
|
63
|
+
formatted = formatted.replace(/<Caption><\/Caption>/g, '<Caption><![CDATA[ ]]></Caption>');
|
|
64
|
+
formatted = formatted.replace(/<Caption> <\/Caption>/g, '<Caption><![CDATA[ ]]></Caption>');
|
|
65
|
+
formatted = formatted.replace(/<Caption> {2}<\/Caption>/g, '<Caption><![CDATA[ ]]></Caption>');
|
|
66
|
+
// Preserve CDATA in <r> tags for text parameters
|
|
67
|
+
// Spaces in <r> tags must use CDATA or they get stripped during rendering
|
|
68
|
+
// e.g., <r> </r> becomes <r><![CDATA[ ]]></r>
|
|
69
|
+
formatted = formatted.replace(/<r> <\/r>/g, '<r><![CDATA[ ]]></r>');
|
|
70
|
+
formatted = formatted.replace(/<r> {2}<\/r>/g, '<r><![CDATA[ ]]></r>');
|
|
71
|
+
return formatted;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Complete XML formatting for Grid 3 compatibility
|
|
75
|
+
* Combines all Grid 3 XML formatting requirements
|
|
76
|
+
*
|
|
77
|
+
* @param xml - The XML string to format
|
|
78
|
+
* @returns Fully formatted XML string compatible with Grid 3
|
|
79
|
+
*/
|
|
80
|
+
export function formatGrid3XmlComplete(xml) {
|
|
81
|
+
let formatted = formatGrid3Xml(xml);
|
|
82
|
+
formatted = formatEmptyCaptionsWithCdata(formatted);
|
|
83
|
+
return formatted;
|
|
84
|
+
}
|
|
@@ -5,6 +5,9 @@ import { resolveGrid3CellImage } from './gridset/resolver';
|
|
|
5
5
|
import { extractAllButtonsForTranslation, validateTranslationResults, } from '../utilities/translation/translationProcessor';
|
|
6
6
|
import { getZipEntriesFromAdapter, resolveGridsetPassword, } from './gridset/password';
|
|
7
7
|
import { decryptGridsetEntry } from './gridset/crypto';
|
|
8
|
+
import { formatGrid3XmlComplete } from './gridset/xmlFormatter';
|
|
9
|
+
import { calculateColumnDefinitions as calcColumnDefs, calculateRowDefinitions as calcRowDefs, } from './gridset/gridCalculations';
|
|
10
|
+
import { findButtonPosition as findButtonPos } from './gridset/cellHelpers';
|
|
8
11
|
import { GridsetValidator } from '../validation/gridsetValidator';
|
|
9
12
|
// New imports for enhanced Grid 3 support
|
|
10
13
|
import { detectPluginCellType, Grid3CellType } from './gridset/pluginTypes';
|
|
@@ -2195,33 +2198,11 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
2195
2198
|
}
|
|
2196
2199
|
// Helper method to calculate column definitions based on page layout
|
|
2197
2200
|
calculateColumnDefinitions(page) {
|
|
2198
|
-
|
|
2199
|
-
if (page.grid && page.grid.length > 0) {
|
|
2200
|
-
maxCols = Math.max(maxCols, page.grid[0]?.length || 0);
|
|
2201
|
-
}
|
|
2202
|
-
else {
|
|
2203
|
-
// Fallback: estimate from button count
|
|
2204
|
-
maxCols = Math.max(4, Math.ceil(Math.sqrt(page.buttons.length)));
|
|
2205
|
-
}
|
|
2206
|
-
return {
|
|
2207
|
-
ColumnDefinition: Array(maxCols).fill({}),
|
|
2208
|
-
};
|
|
2201
|
+
return calcColumnDefs(page);
|
|
2209
2202
|
}
|
|
2210
2203
|
// Helper method to calculate row definitions based on page layout
|
|
2211
2204
|
calculateRowDefinitions(page, addWorkspaceOffset = false) {
|
|
2212
|
-
|
|
2213
|
-
const offset = addWorkspaceOffset ? 1 : 0;
|
|
2214
|
-
if (page.grid && page.grid.length > 0) {
|
|
2215
|
-
maxRows = Math.max(maxRows, page.grid.length + offset);
|
|
2216
|
-
}
|
|
2217
|
-
else {
|
|
2218
|
-
// Fallback: estimate from button count
|
|
2219
|
-
const estimatedCols = Math.ceil(Math.sqrt(page.buttons.length));
|
|
2220
|
-
maxRows = Math.max(4, Math.ceil(page.buttons.length / estimatedCols)) + offset;
|
|
2221
|
-
}
|
|
2222
|
-
return {
|
|
2223
|
-
RowDefinition: Array(maxRows).fill({}),
|
|
2224
|
-
};
|
|
2205
|
+
return calcRowDefs(page, addWorkspaceOffset);
|
|
2225
2206
|
}
|
|
2226
2207
|
/**
|
|
2227
2208
|
* Save a modified tree while preserving all original files (settings, images, assets)
|
|
@@ -2367,6 +2348,9 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
2367
2348
|
}
|
|
2368
2349
|
}
|
|
2369
2350
|
}
|
|
2351
|
+
// DO NOT create new cells - the system should only modify existing content
|
|
2352
|
+
// Personalized vocabulary is added to WordList cells via the WordList.Items array
|
|
2353
|
+
// Creating new cells would corrupt the grid structure
|
|
2370
2354
|
// Update the page's WordList with new words from modified buttons
|
|
2371
2355
|
// Collect all modified buttons that should be added to the WordList
|
|
2372
2356
|
const newWordListItems = [];
|
|
@@ -2379,9 +2363,18 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
2379
2363
|
? [originalGrid.Grid.Cells.Cell]
|
|
2380
2364
|
: [];
|
|
2381
2365
|
const cell = cellArray.find((c) => {
|
|
2382
|
-
const
|
|
2383
|
-
|
|
2384
|
-
|
|
2366
|
+
const cellY = parseInt(String(c['@_Y'] || c['@_Row'] || '0'), 10);
|
|
2367
|
+
// Check Y position first
|
|
2368
|
+
if (cellY !== pos.y) {
|
|
2369
|
+
return false;
|
|
2370
|
+
}
|
|
2371
|
+
const cellX = c['@_X'] !== undefined ? parseInt(String(c['@_X']), 10) : undefined;
|
|
2372
|
+
// If cell has no X attribute (full-width cell), it matches any button at this Y
|
|
2373
|
+
if (cellX === undefined) {
|
|
2374
|
+
return true;
|
|
2375
|
+
}
|
|
2376
|
+
// Otherwise, check exact X match
|
|
2377
|
+
return cellX === pos.x;
|
|
2385
2378
|
});
|
|
2386
2379
|
if (cell) {
|
|
2387
2380
|
const contentType = cell.Content?.ContentType || cell.Content?.contentType;
|
|
@@ -2390,11 +2383,14 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
2390
2383
|
// Note: Prediction cells are already skipped earlier, so they won't reach here
|
|
2391
2384
|
if (isWordListCell) {
|
|
2392
2385
|
// Add this button to the WordList with proper Grid 3 format
|
|
2393
|
-
// Format: <Text><s><r>label</r></s></Text>
|
|
2386
|
+
// Format: <Text><p><s><r>label</r></s></p></Text>
|
|
2387
|
+
// Note: <p> wrapper is required by Grid 3's WordList format
|
|
2394
2388
|
newWordListItems.push({
|
|
2395
2389
|
Text: {
|
|
2396
|
-
|
|
2397
|
-
|
|
2390
|
+
p: {
|
|
2391
|
+
s: {
|
|
2392
|
+
r: button.label,
|
|
2393
|
+
},
|
|
2398
2394
|
},
|
|
2399
2395
|
},
|
|
2400
2396
|
Image: '', // No image for user-added words
|
|
@@ -2421,23 +2417,46 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
2421
2417
|
originalGrid.Grid.WordList.Items.WordListItem = allItems;
|
|
2422
2418
|
}
|
|
2423
2419
|
}
|
|
2424
|
-
//
|
|
2420
|
+
// Process WordList items attached to the page (from personalisation)
|
|
2421
|
+
// These are tracked separately and shouldn't create new cells
|
|
2422
|
+
// Use a known symbol key to check for WordList items
|
|
2423
|
+
const WORDLIST_ITEMS_KEY = 'wordListItems';
|
|
2424
|
+
const wordListItems = page[WORDLIST_ITEMS_KEY];
|
|
2425
|
+
if (wordListItems && wordListItems.length > 0) {
|
|
2426
|
+
// Ensure WordList structure exists
|
|
2427
|
+
if (!originalGrid.Grid) {
|
|
2428
|
+
originalGrid.Grid = {};
|
|
2429
|
+
}
|
|
2430
|
+
if (!originalGrid.Grid.WordList) {
|
|
2431
|
+
originalGrid.Grid.WordList = {};
|
|
2432
|
+
}
|
|
2433
|
+
if (!originalGrid.Grid.WordList.Items) {
|
|
2434
|
+
originalGrid.Grid.WordList.Items = {};
|
|
2435
|
+
}
|
|
2436
|
+
const existingItems = originalGrid.Grid.WordList.Items.WordListItem ||
|
|
2437
|
+
originalGrid.Grid.WordList.Items.wordlistitem ||
|
|
2438
|
+
[];
|
|
2439
|
+
const itemsArray = Array.isArray(existingItems) ? existingItems : [existingItems];
|
|
2440
|
+
// Add new WordList items with proper Grid 3 format
|
|
2441
|
+
for (const item of wordListItems) {
|
|
2442
|
+
itemsArray.push({
|
|
2443
|
+
Text: {
|
|
2444
|
+
p: {
|
|
2445
|
+
s: {
|
|
2446
|
+
r: item.label,
|
|
2447
|
+
},
|
|
2448
|
+
},
|
|
2449
|
+
},
|
|
2450
|
+
Image: '',
|
|
2451
|
+
PartOfSpeech: 'Unknown',
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
// Update the WordList
|
|
2455
|
+
originalGrid.Grid.WordList.Items.WordListItem = itemsArray;
|
|
2456
|
+
}
|
|
2457
|
+
// Build the updated grid XML and format for Grid 3 compatibility
|
|
2425
2458
|
let builtXml = gridBuilder.build(originalGrid);
|
|
2426
|
-
|
|
2427
|
-
builtXml = builtXml.replace(/\n/g, '\r\n');
|
|
2428
|
-
// Expand self-closing tags to full opening/closing tags for Grid 3 compatibility
|
|
2429
|
-
// Grid 3 cannot parse <AudioDescription /> - it requires <AudioDescription></AudioDescription>
|
|
2430
|
-
builtXml = builtXml.replace(/<(\w+)(\s+[^>]*)?\s*\/>/g, '<$1$2></$1>');
|
|
2431
|
-
// Convert empty/whitespace captions to CDATA format for Grid 3 compatibility
|
|
2432
|
-
// Grid 3 requires <![CDATA[ ]]> for empty captions, not plain text
|
|
2433
|
-
builtXml = builtXml.replace(/<Caption><\/Caption>/g, '<Caption><![CDATA[ ]]></Caption>');
|
|
2434
|
-
builtXml = builtXml.replace(/<Caption> <\/Caption>/g, '<Caption><![CDATA[ ]]></Caption>');
|
|
2435
|
-
builtXml = builtXml.replace(/<Caption> {2}<\/Caption>/g, '<Caption><![CDATA[ ]]></Caption>');
|
|
2436
|
-
// Preserve CDATA in <r> tags for text parameters
|
|
2437
|
-
// Spaces in <r> tags must use CDATA or they get stripped during rendering
|
|
2438
|
-
// e.g., <r> </r> becomes <r><![CDATA[ ]]></r>
|
|
2439
|
-
builtXml = builtXml.replace(/<r> <\/r>/g, '<r><![CDATA[ ]]></r>');
|
|
2440
|
-
builtXml = builtXml.replace(/<r> {2}<\/r>/g, '<r><![CDATA[ ]]></r>');
|
|
2459
|
+
builtXml = formatGrid3XmlComplete(builtXml);
|
|
2441
2460
|
newGridFiles.set(gridPath, builtXml);
|
|
2442
2461
|
}
|
|
2443
2462
|
// Copy all files from original zip, replacing modified grid files
|
|
@@ -2506,57 +2525,14 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
2506
2525
|
// Preserve Grid 3 XML formatting requirements
|
|
2507
2526
|
suppressBooleanAttributes: false,
|
|
2508
2527
|
});
|
|
2509
|
-
// Build the grid XML and
|
|
2528
|
+
// Build the grid XML and format for Grid 3 compatibility
|
|
2510
2529
|
let builtXml = gridBuilder.build(gridData);
|
|
2511
|
-
builtXml = builtXml
|
|
2512
|
-
// Expand self-closing tags to full opening/closing tags for Grid 3 compatibility
|
|
2513
|
-
builtXml = builtXml.replace(/<(\w+)(\s+[^>]*)?\s*\/>/g, '<$1$2></$1>');
|
|
2530
|
+
builtXml = formatGrid3XmlComplete(builtXml);
|
|
2514
2531
|
return builtXml;
|
|
2515
2532
|
}
|
|
2516
2533
|
// Helper method to find button position with span information
|
|
2517
2534
|
findButtonPosition(page, button, fallbackIndex) {
|
|
2518
|
-
|
|
2519
|
-
// Search for button in grid layout and calculate span
|
|
2520
|
-
for (let y = 0; y < page.grid.length; y++) {
|
|
2521
|
-
for (let x = 0; x < page.grid[y].length; x++) {
|
|
2522
|
-
const current = page.grid[y][x];
|
|
2523
|
-
if (current && current.id === button.id) {
|
|
2524
|
-
// Calculate span by checking how far the same button extends
|
|
2525
|
-
let columnSpan = 1;
|
|
2526
|
-
let rowSpan = 1;
|
|
2527
|
-
// Check column span (rightward)
|
|
2528
|
-
while (x + columnSpan < page.grid[y].length) {
|
|
2529
|
-
const right = page.grid[y][x + columnSpan];
|
|
2530
|
-
if (right && right.id === button.id) {
|
|
2531
|
-
columnSpan++;
|
|
2532
|
-
}
|
|
2533
|
-
else {
|
|
2534
|
-
break;
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
// Check row span (downward)
|
|
2538
|
-
while (y + rowSpan < page.grid.length) {
|
|
2539
|
-
const below = page.grid[y + rowSpan][x];
|
|
2540
|
-
if (below && below.id === button.id) {
|
|
2541
|
-
rowSpan++;
|
|
2542
|
-
}
|
|
2543
|
-
else {
|
|
2544
|
-
break;
|
|
2545
|
-
}
|
|
2546
|
-
}
|
|
2547
|
-
return { x, y, columnSpan, rowSpan };
|
|
2548
|
-
}
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
}
|
|
2552
|
-
// Fallback positioning
|
|
2553
|
-
const gridCols = page.grid?.[0]?.length || Math.ceil(Math.sqrt(page.buttons.length));
|
|
2554
|
-
return {
|
|
2555
|
-
x: fallbackIndex % gridCols,
|
|
2556
|
-
y: Math.floor(fallbackIndex / gridCols),
|
|
2557
|
-
columnSpan: 1,
|
|
2558
|
-
rowSpan: 1,
|
|
2559
|
-
};
|
|
2535
|
+
return findButtonPos(page, button, fallbackIndex);
|
|
2560
2536
|
}
|
|
2561
2537
|
/**
|
|
2562
2538
|
* Extract strings with metadata for aac-tools-platform compatibility
|
|
@@ -80,7 +80,9 @@ export async function openSqliteDatabase(input, options = {}) {
|
|
|
80
80
|
throw new Error('SQLite file paths are not supported in browser environments.');
|
|
81
81
|
}
|
|
82
82
|
const Database = getBetterSqlite3();
|
|
83
|
-
const db = new Database(input, {
|
|
83
|
+
const db = new Database(input, {
|
|
84
|
+
readonly: options.readonly ?? true,
|
|
85
|
+
});
|
|
84
86
|
return { db };
|
|
85
87
|
}
|
|
86
88
|
const data = await readBinaryFromInput(input);
|
|
@@ -93,7 +95,9 @@ export async function openSqliteDatabase(input, options = {}) {
|
|
|
93
95
|
const dbPath = join(tempDir, 'input.sqlite');
|
|
94
96
|
await writeBinaryToPath(dbPath, data);
|
|
95
97
|
const Database = getBetterSqlite3();
|
|
96
|
-
const db = new Database(dbPath, {
|
|
98
|
+
const db = new Database(dbPath, {
|
|
99
|
+
readonly: options.readonly ?? true,
|
|
100
|
+
});
|
|
97
101
|
const cleanup = async () => {
|
|
98
102
|
try {
|
|
99
103
|
db.close();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid3 Cell Helpers
|
|
3
|
+
*
|
|
4
|
+
* Utilities for working with Grid 3 cells, including finding button positions
|
|
5
|
+
* and calculating cell spans in grid layouts.
|
|
6
|
+
*/
|
|
7
|
+
import type { AACPage, AACButton } from '../../core/treeStructure';
|
|
8
|
+
/**
|
|
9
|
+
* Cell position with span information
|
|
10
|
+
*/
|
|
11
|
+
export interface CellPosition {
|
|
12
|
+
/** X coordinate (column) */
|
|
13
|
+
x: number;
|
|
14
|
+
/** Y coordinate (row) */
|
|
15
|
+
y: number;
|
|
16
|
+
/** Number of columns the cell spans */
|
|
17
|
+
columnSpan: number;
|
|
18
|
+
/** Number of rows the cell spans */
|
|
19
|
+
rowSpan: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Find button position with span information
|
|
23
|
+
*
|
|
24
|
+
* Searches the page's grid layout for a button and calculates its position
|
|
25
|
+
* and span (how many columns/rows it occupies).
|
|
26
|
+
*
|
|
27
|
+
* @param page - The AAC page containing the button
|
|
28
|
+
* @param button - The button to locate
|
|
29
|
+
* @param fallbackIndex - Index to use if button not found in grid
|
|
30
|
+
* @returns Position and span information for the button
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* const position = findButtonPosition(page, button, 0);
|
|
34
|
+
* console.log(`Button at ${position.x},${position.y} spans ${position.columnSpan}x${position.rowSpan}`);
|
|
35
|
+
*/
|
|
36
|
+
export declare function findButtonPosition(page: AACPage, button: AACButton, fallbackIndex: number): CellPosition;
|
|
37
|
+
/**
|
|
38
|
+
* Calculate cell position key for Maps and Sets
|
|
39
|
+
*
|
|
40
|
+
* Creates a string key from X and Y coordinates for use as a Map key or Set entry.
|
|
41
|
+
*
|
|
42
|
+
* @param x - X coordinate
|
|
43
|
+
* @param y - Y coordinate
|
|
44
|
+
* @returns String key in format "x,y"
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* const key = cellPositionKey(5, 3);
|
|
48
|
+
* console.log(key); // "5,3"
|
|
49
|
+
*/
|
|
50
|
+
export declare function cellPositionKey(x: number, y: number): string;
|
|
51
|
+
/**
|
|
52
|
+
* Parse cell position key
|
|
53
|
+
*
|
|
54
|
+
* Extracts X and Y coordinates from a position key string.
|
|
55
|
+
*
|
|
56
|
+
* @param key - Position key in format "x,y"
|
|
57
|
+
* @returns Object with x and y properties
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* const pos = parseCellPositionKey("5,3");
|
|
61
|
+
* console.log(pos); // { x: 5, y: 3 }
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseCellPositionKey(key: string): {
|
|
64
|
+
x: number;
|
|
65
|
+
y: number;
|
|
66
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Grid3 Cell Helpers
|
|
4
|
+
*
|
|
5
|
+
* Utilities for working with Grid 3 cells, including finding button positions
|
|
6
|
+
* and calculating cell spans in grid layouts.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.findButtonPosition = findButtonPosition;
|
|
10
|
+
exports.cellPositionKey = cellPositionKey;
|
|
11
|
+
exports.parseCellPositionKey = parseCellPositionKey;
|
|
12
|
+
/**
|
|
13
|
+
* Find button position with span information
|
|
14
|
+
*
|
|
15
|
+
* Searches the page's grid layout for a button and calculates its position
|
|
16
|
+
* and span (how many columns/rows it occupies).
|
|
17
|
+
*
|
|
18
|
+
* @param page - The AAC page containing the button
|
|
19
|
+
* @param button - The button to locate
|
|
20
|
+
* @param fallbackIndex - Index to use if button not found in grid
|
|
21
|
+
* @returns Position and span information for the button
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const position = findButtonPosition(page, button, 0);
|
|
25
|
+
* console.log(`Button at ${position.x},${position.y} spans ${position.columnSpan}x${position.rowSpan}`);
|
|
26
|
+
*/
|
|
27
|
+
function findButtonPosition(page, button, fallbackIndex) {
|
|
28
|
+
if (page.grid && page.grid.length > 0) {
|
|
29
|
+
// Search for button in grid layout and calculate span
|
|
30
|
+
for (let y = 0; y < page.grid.length; y++) {
|
|
31
|
+
for (let x = 0; x < page.grid[y].length; x++) {
|
|
32
|
+
const current = page.grid[y][x];
|
|
33
|
+
if (current && current.id === button.id) {
|
|
34
|
+
// Calculate span by checking how far the same button extends
|
|
35
|
+
let columnSpan = 1;
|
|
36
|
+
let rowSpan = 1;
|
|
37
|
+
// Check column span (rightward)
|
|
38
|
+
while (x + columnSpan < page.grid[y].length) {
|
|
39
|
+
const right = page.grid[y][x + columnSpan];
|
|
40
|
+
if (right && right.id === button.id) {
|
|
41
|
+
columnSpan++;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Check row span (downward)
|
|
48
|
+
while (y + rowSpan < page.grid.length) {
|
|
49
|
+
const below = page.grid[y + rowSpan][x];
|
|
50
|
+
if (below && below.id === button.id) {
|
|
51
|
+
rowSpan++;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { x, y, columnSpan, rowSpan };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Fallback positioning
|
|
63
|
+
const gridCols = page.grid?.[0]?.length || Math.ceil(Math.sqrt(page.buttons.length));
|
|
64
|
+
return {
|
|
65
|
+
x: fallbackIndex % gridCols,
|
|
66
|
+
y: Math.floor(fallbackIndex / gridCols),
|
|
67
|
+
columnSpan: 1,
|
|
68
|
+
rowSpan: 1,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Calculate cell position key for Maps and Sets
|
|
73
|
+
*
|
|
74
|
+
* Creates a string key from X and Y coordinates for use as a Map key or Set entry.
|
|
75
|
+
*
|
|
76
|
+
* @param x - X coordinate
|
|
77
|
+
* @param y - Y coordinate
|
|
78
|
+
* @returns String key in format "x,y"
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* const key = cellPositionKey(5, 3);
|
|
82
|
+
* console.log(key); // "5,3"
|
|
83
|
+
*/
|
|
84
|
+
function cellPositionKey(x, y) {
|
|
85
|
+
return `${x},${y}`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Parse cell position key
|
|
89
|
+
*
|
|
90
|
+
* Extracts X and Y coordinates from a position key string.
|
|
91
|
+
*
|
|
92
|
+
* @param key - Position key in format "x,y"
|
|
93
|
+
* @returns Object with x and y properties
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* const pos = parseCellPositionKey("5,3");
|
|
97
|
+
* console.log(pos); // { x: 5, y: 3 }
|
|
98
|
+
*/
|
|
99
|
+
function parseCellPositionKey(key) {
|
|
100
|
+
const [x, y] = key.split(',').map(Number);
|
|
101
|
+
return { x, y };
|
|
102
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid3 Grid Calculations
|
|
3
|
+
*
|
|
4
|
+
* Utilities for calculating grid dimensions and definitions
|
|
5
|
+
* based on page layout and button count.
|
|
6
|
+
*/
|
|
7
|
+
import type { AACPage } from '../../core/treeStructure';
|
|
8
|
+
/**
|
|
9
|
+
* Grid definition structure for Grid 3 XML
|
|
10
|
+
*/
|
|
11
|
+
export interface GridDefinitions {
|
|
12
|
+
ColumnDefinition: any[];
|
|
13
|
+
}
|
|
14
|
+
export interface RowDefinitions {
|
|
15
|
+
RowDefinition: any[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Calculate column definitions based on page layout
|
|
19
|
+
*
|
|
20
|
+
* Analyzes the page's grid structure to determine the number of columns.
|
|
21
|
+
* If no grid exists, estimates from button count.
|
|
22
|
+
*
|
|
23
|
+
* @param page - The AAC page to analyze
|
|
24
|
+
* @returns Column definitions object for Grid 3 XML
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const columns = calculateColumnDefinitions(page);
|
|
28
|
+
* // Returns: { ColumnDefinition: [{}, {}, {}, {}] } for 4 columns
|
|
29
|
+
*/
|
|
30
|
+
export declare function calculateColumnDefinitions(page: AACPage): GridDefinitions;
|
|
31
|
+
/**
|
|
32
|
+
* Calculate row definitions based on page layout
|
|
33
|
+
*
|
|
34
|
+
* Analyzes the page's grid structure to determine the number of rows.
|
|
35
|
+
* If no grid exists, estimates from button count.
|
|
36
|
+
*
|
|
37
|
+
* @param page - The AAC page to analyze
|
|
38
|
+
* @param addWorkspaceOffset - Whether to add 1 row for workspace (default: false)
|
|
39
|
+
* @returns Row definitions object for Grid 3 XML
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const rows = calculateRowDefinitions(page, false);
|
|
43
|
+
* // Returns: { RowDefinition: [{}, {}, {}, {}] } for 4 rows
|
|
44
|
+
*/
|
|
45
|
+
export declare function calculateRowDefinitions(page: AACPage, addWorkspaceOffset?: boolean): RowDefinitions;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Grid3 Grid Calculations
|
|
4
|
+
*
|
|
5
|
+
* Utilities for calculating grid dimensions and definitions
|
|
6
|
+
* based on page layout and button count.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.calculateColumnDefinitions = calculateColumnDefinitions;
|
|
10
|
+
exports.calculateRowDefinitions = calculateRowDefinitions;
|
|
11
|
+
/**
|
|
12
|
+
* Calculate column definitions based on page layout
|
|
13
|
+
*
|
|
14
|
+
* Analyzes the page's grid structure to determine the number of columns.
|
|
15
|
+
* If no grid exists, estimates from button count.
|
|
16
|
+
*
|
|
17
|
+
* @param page - The AAC page to analyze
|
|
18
|
+
* @returns Column definitions object for Grid 3 XML
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const columns = calculateColumnDefinitions(page);
|
|
22
|
+
* // Returns: { ColumnDefinition: [{}, {}, {}, {}] } for 4 columns
|
|
23
|
+
*/
|
|
24
|
+
function calculateColumnDefinitions(page) {
|
|
25
|
+
let maxCols = 4; // Default minimum
|
|
26
|
+
if (page.grid && page.grid.length > 0) {
|
|
27
|
+
maxCols = Math.max(maxCols, page.grid[0]?.length || 0);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
// Fallback: estimate from button count
|
|
31
|
+
maxCols = Math.max(4, Math.ceil(Math.sqrt(page.buttons.length)));
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
ColumnDefinition: Array(maxCols).fill({}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Calculate row definitions based on page layout
|
|
39
|
+
*
|
|
40
|
+
* Analyzes the page's grid structure to determine the number of rows.
|
|
41
|
+
* If no grid exists, estimates from button count.
|
|
42
|
+
*
|
|
43
|
+
* @param page - The AAC page to analyze
|
|
44
|
+
* @param addWorkspaceOffset - Whether to add 1 row for workspace (default: false)
|
|
45
|
+
* @returns Row definitions object for Grid 3 XML
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* const rows = calculateRowDefinitions(page, false);
|
|
49
|
+
* // Returns: { RowDefinition: [{}, {}, {}, {}] } for 4 rows
|
|
50
|
+
*/
|
|
51
|
+
function calculateRowDefinitions(page, addWorkspaceOffset = false) {
|
|
52
|
+
let maxRows = 4; // Default minimum
|
|
53
|
+
const offset = addWorkspaceOffset ? 1 : 0;
|
|
54
|
+
if (page.grid && page.grid.length > 0) {
|
|
55
|
+
maxRows = Math.max(maxRows, page.grid.length + offset);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Fallback: estimate from button count
|
|
59
|
+
const estimatedCols = Math.ceil(Math.sqrt(page.buttons.length));
|
|
60
|
+
maxRows = Math.max(4, Math.ceil(page.buttons.length / estimatedCols)) + offset;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
RowDefinition: Array(maxRows).fill({}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -167,7 +167,10 @@ function getCommonDocumentsPath() {
|
|
|
167
167
|
// Query registry for Common Documents path
|
|
168
168
|
const child_process = (0, io_1.getNodeRequire)()('child_process');
|
|
169
169
|
const command = 'REG.EXE QUERY "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders" /V "Common Documents"';
|
|
170
|
-
const output = child_process.execSync(command, {
|
|
170
|
+
const output = child_process.execSync(command, {
|
|
171
|
+
encoding: 'utf-8',
|
|
172
|
+
windowsHide: true,
|
|
173
|
+
});
|
|
171
174
|
// Parse the output to extract the path
|
|
172
175
|
const match = output.match(/Common Documents\s+REG_SZ\s+(.+)/);
|
|
173
176
|
if (match && match[1]) {
|
|
@@ -69,9 +69,10 @@ function wordlistToXml(wordlist) {
|
|
|
69
69
|
const items = wordlist.items.map((item) => ({
|
|
70
70
|
WordListItem: {
|
|
71
71
|
Text: {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
p: {
|
|
73
|
+
s: {
|
|
74
|
+
r: item.text,
|
|
75
|
+
},
|
|
75
76
|
},
|
|
76
77
|
},
|
|
77
78
|
Image: item.image || '',
|
|
@@ -137,7 +138,7 @@ async function extractWordlists(gridsetBuffer, password = (0, password_1.resolve
|
|
|
137
138
|
? [itemsContainer.WordListItem]
|
|
138
139
|
: [];
|
|
139
140
|
const items = itemArray.map((item) => ({
|
|
140
|
-
text: item.Text?.s?.r || item.text?.s?.r || '',
|
|
141
|
+
text: item.Text?.p?.s?.r || item.Text?.s?.r || item.text?.p?.s?.r || item.text?.s?.r || '',
|
|
141
142
|
image: item.Image || item.image || undefined,
|
|
142
143
|
partOfSpeech: item.PartOfSpeech || item.partOfSpeech || 'Unknown',
|
|
143
144
|
}));
|
|
@@ -202,9 +203,10 @@ async function updateWordlist(gridsetBuffer, gridName, wordlist, password = (0,
|
|
|
202
203
|
const items = wordlist.items.map((item) => ({
|
|
203
204
|
WordListItem: {
|
|
204
205
|
Text: {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
p: {
|
|
207
|
+
s: {
|
|
208
|
+
r: item.text,
|
|
209
|
+
},
|
|
208
210
|
},
|
|
209
211
|
},
|
|
210
212
|
Image: item.image || '',
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid3 XML Formatter
|
|
3
|
+
*
|
|
4
|
+
* Utilities for formatting XML to match Grid 3's specific requirements.
|
|
5
|
+
* Grid 3 has strict formatting requirements including line endings, self-closing
|
|
6
|
+
* tag spacing, and specific tag expansion rules.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Format XML string to match Grid 3's requirements
|
|
10
|
+
*
|
|
11
|
+
* Grid 3 requires specific formatting:
|
|
12
|
+
* - Windows line endings (\r\n)
|
|
13
|
+
* - Space before /> in self-closing tags: <Element /> not <Element/>
|
|
14
|
+
* - Plain apostrophes instead of '
|
|
15
|
+
* - Specific tags expanded to full opening/closing format
|
|
16
|
+
* - CDATA for empty/whitespace captions and <r> tags
|
|
17
|
+
*
|
|
18
|
+
* @param xml - The XML string to format
|
|
19
|
+
* @returns Formatted XML string compatible with Grid 3
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const formatted = formatGrid3Xml('<Grid><Cell X="0"/></Grid>');
|
|
23
|
+
* // Returns: '<Grid>\r\n<Cell X="0" />\r\n</Grid>'
|
|
24
|
+
*/
|
|
25
|
+
export declare function formatGrid3Xml(xml: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Format empty/whitespace captions with CDATA for Grid 3 compatibility
|
|
28
|
+
*
|
|
29
|
+
* Grid 3 requires <![CDATA[ ]]> for empty captions, not plain text.
|
|
30
|
+
* Also handles <r> tags which need CDATA for spaces to prevent stripping.
|
|
31
|
+
*
|
|
32
|
+
* @param xml - The XML string to format
|
|
33
|
+
* @returns XML string with CDATA-wrapped empty content
|
|
34
|
+
*/
|
|
35
|
+
export declare function formatEmptyCaptionsWithCdata(xml: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Complete XML formatting for Grid 3 compatibility
|
|
38
|
+
* Combines all Grid 3 XML formatting requirements
|
|
39
|
+
*
|
|
40
|
+
* @param xml - The XML string to format
|
|
41
|
+
* @returns Fully formatted XML string compatible with Grid 3
|
|
42
|
+
*/
|
|
43
|
+
export declare function formatGrid3XmlComplete(xml: string): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Grid3 XML Formatter
|
|
4
|
+
*
|
|
5
|
+
* Utilities for formatting XML to match Grid 3's specific requirements.
|
|
6
|
+
* Grid 3 has strict formatting requirements including line endings, self-closing
|
|
7
|
+
* tag spacing, and specific tag expansion rules.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.formatGrid3Xml = formatGrid3Xml;
|
|
11
|
+
exports.formatEmptyCaptionsWithCdata = formatEmptyCaptionsWithCdata;
|
|
12
|
+
exports.formatGrid3XmlComplete = formatGrid3XmlComplete;
|
|
13
|
+
/**
|
|
14
|
+
* Tags that Grid 3 requires in full opening/closing format instead of self-closing
|
|
15
|
+
* Grid 3 cannot parse <AudioDescription /> - it requires <AudioDescription></AudioDescription>
|
|
16
|
+
*/
|
|
17
|
+
const TAGS_NEEDING_EXPANSION = ['AudioDescription', 'VideoDescription'];
|
|
18
|
+
/**
|
|
19
|
+
* Format XML string to match Grid 3's requirements
|
|
20
|
+
*
|
|
21
|
+
* Grid 3 requires specific formatting:
|
|
22
|
+
* - Windows line endings (\r\n)
|
|
23
|
+
* - Space before /> in self-closing tags: <Element /> not <Element/>
|
|
24
|
+
* - Plain apostrophes instead of '
|
|
25
|
+
* - Specific tags expanded to full opening/closing format
|
|
26
|
+
* - CDATA for empty/whitespace captions and <r> tags
|
|
27
|
+
*
|
|
28
|
+
* @param xml - The XML string to format
|
|
29
|
+
* @returns Formatted XML string compatible with Grid 3
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const formatted = formatGrid3Xml('<Grid><Cell X="0"/></Grid>');
|
|
33
|
+
* // Returns: '<Grid>\r\n<Cell X="0" />\r\n</Grid>'
|
|
34
|
+
*/
|
|
35
|
+
function formatGrid3Xml(xml) {
|
|
36
|
+
let formatted = xml;
|
|
37
|
+
// Convert Unix line endings to Windows (\r\n) for Grid 3 compatibility
|
|
38
|
+
formatted = formatted.replace(/\n/g, '\r\n');
|
|
39
|
+
// Add space before /> in self-closing tags to match Grid 3's expected format
|
|
40
|
+
// Grid 3 original files use <Element /> not <Element/>
|
|
41
|
+
formatted = formatted.replace(/<(\w+)([^>]*)\/>/g, '<$1$2 />');
|
|
42
|
+
// Decode XML entities back to plain text to match Grid 3's expected format
|
|
43
|
+
// Grid 3 expects plain apostrophes, not '
|
|
44
|
+
formatted = formatted.replace(/'/g, "'");
|
|
45
|
+
formatted = formatted.replace(/"/g, '"');
|
|
46
|
+
formatted = formatted.replace(/</g, '<');
|
|
47
|
+
formatted = formatted.replace(/>/g, '>');
|
|
48
|
+
// Expand only specific self-closing tags that Grid 3 requires in full opening/closing format
|
|
49
|
+
// This must be done AFTER adding spaces, so we need to match the format with spaces
|
|
50
|
+
for (const tag of TAGS_NEEDING_EXPANSION) {
|
|
51
|
+
formatted = formatted.replace(new RegExp(`<${tag}(\\s+[^>]*)? />`, 'g'), `<${tag}$1></${tag}>`);
|
|
52
|
+
}
|
|
53
|
+
return formatted;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Format empty/whitespace captions with CDATA for Grid 3 compatibility
|
|
57
|
+
*
|
|
58
|
+
* Grid 3 requires <![CDATA[ ]]> for empty captions, not plain text.
|
|
59
|
+
* Also handles <r> tags which need CDATA for spaces to prevent stripping.
|
|
60
|
+
*
|
|
61
|
+
* @param xml - The XML string to format
|
|
62
|
+
* @returns XML string with CDATA-wrapped empty content
|
|
63
|
+
*/
|
|
64
|
+
function formatEmptyCaptionsWithCdata(xml) {
|
|
65
|
+
let formatted = xml;
|
|
66
|
+
// Convert empty/whitespace captions to CDATA format for Grid 3 compatibility
|
|
67
|
+
// Grid 3 requires <![CDATA[ ]]> for empty captions, not plain text
|
|
68
|
+
formatted = formatted.replace(/<Caption><\/Caption>/g, '<Caption><![CDATA[ ]]></Caption>');
|
|
69
|
+
formatted = formatted.replace(/<Caption> <\/Caption>/g, '<Caption><![CDATA[ ]]></Caption>');
|
|
70
|
+
formatted = formatted.replace(/<Caption> {2}<\/Caption>/g, '<Caption><![CDATA[ ]]></Caption>');
|
|
71
|
+
// Preserve CDATA in <r> tags for text parameters
|
|
72
|
+
// Spaces in <r> tags must use CDATA or they get stripped during rendering
|
|
73
|
+
// e.g., <r> </r> becomes <r><![CDATA[ ]]></r>
|
|
74
|
+
formatted = formatted.replace(/<r> <\/r>/g, '<r><![CDATA[ ]]></r>');
|
|
75
|
+
formatted = formatted.replace(/<r> {2}<\/r>/g, '<r><![CDATA[ ]]></r>');
|
|
76
|
+
return formatted;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Complete XML formatting for Grid 3 compatibility
|
|
80
|
+
* Combines all Grid 3 XML formatting requirements
|
|
81
|
+
*
|
|
82
|
+
* @param xml - The XML string to format
|
|
83
|
+
* @returns Fully formatted XML string compatible with Grid 3
|
|
84
|
+
*/
|
|
85
|
+
function formatGrid3XmlComplete(xml) {
|
|
86
|
+
let formatted = formatGrid3Xml(xml);
|
|
87
|
+
formatted = formatEmptyCaptionsWithCdata(formatted);
|
|
88
|
+
return formatted;
|
|
89
|
+
}
|
|
@@ -31,6 +31,9 @@ const resolver_1 = require("./gridset/resolver");
|
|
|
31
31
|
const translationProcessor_1 = require("../utilities/translation/translationProcessor");
|
|
32
32
|
const password_1 = require("./gridset/password");
|
|
33
33
|
const crypto_1 = require("./gridset/crypto");
|
|
34
|
+
const xmlFormatter_1 = require("./gridset/xmlFormatter");
|
|
35
|
+
const gridCalculations_1 = require("./gridset/gridCalculations");
|
|
36
|
+
const cellHelpers_1 = require("./gridset/cellHelpers");
|
|
34
37
|
const gridsetValidator_1 = require("../validation/gridsetValidator");
|
|
35
38
|
// New imports for enhanced Grid 3 support
|
|
36
39
|
const pluginTypes_1 = require("./gridset/pluginTypes");
|
|
@@ -2221,33 +2224,11 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
2221
2224
|
}
|
|
2222
2225
|
// Helper method to calculate column definitions based on page layout
|
|
2223
2226
|
calculateColumnDefinitions(page) {
|
|
2224
|
-
|
|
2225
|
-
if (page.grid && page.grid.length > 0) {
|
|
2226
|
-
maxCols = Math.max(maxCols, page.grid[0]?.length || 0);
|
|
2227
|
-
}
|
|
2228
|
-
else {
|
|
2229
|
-
// Fallback: estimate from button count
|
|
2230
|
-
maxCols = Math.max(4, Math.ceil(Math.sqrt(page.buttons.length)));
|
|
2231
|
-
}
|
|
2232
|
-
return {
|
|
2233
|
-
ColumnDefinition: Array(maxCols).fill({}),
|
|
2234
|
-
};
|
|
2227
|
+
return (0, gridCalculations_1.calculateColumnDefinitions)(page);
|
|
2235
2228
|
}
|
|
2236
2229
|
// Helper method to calculate row definitions based on page layout
|
|
2237
2230
|
calculateRowDefinitions(page, addWorkspaceOffset = false) {
|
|
2238
|
-
|
|
2239
|
-
const offset = addWorkspaceOffset ? 1 : 0;
|
|
2240
|
-
if (page.grid && page.grid.length > 0) {
|
|
2241
|
-
maxRows = Math.max(maxRows, page.grid.length + offset);
|
|
2242
|
-
}
|
|
2243
|
-
else {
|
|
2244
|
-
// Fallback: estimate from button count
|
|
2245
|
-
const estimatedCols = Math.ceil(Math.sqrt(page.buttons.length));
|
|
2246
|
-
maxRows = Math.max(4, Math.ceil(page.buttons.length / estimatedCols)) + offset;
|
|
2247
|
-
}
|
|
2248
|
-
return {
|
|
2249
|
-
RowDefinition: Array(maxRows).fill({}),
|
|
2250
|
-
};
|
|
2231
|
+
return (0, gridCalculations_1.calculateRowDefinitions)(page, addWorkspaceOffset);
|
|
2251
2232
|
}
|
|
2252
2233
|
/**
|
|
2253
2234
|
* Save a modified tree while preserving all original files (settings, images, assets)
|
|
@@ -2393,6 +2374,9 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
2393
2374
|
}
|
|
2394
2375
|
}
|
|
2395
2376
|
}
|
|
2377
|
+
// DO NOT create new cells - the system should only modify existing content
|
|
2378
|
+
// Personalized vocabulary is added to WordList cells via the WordList.Items array
|
|
2379
|
+
// Creating new cells would corrupt the grid structure
|
|
2396
2380
|
// Update the page's WordList with new words from modified buttons
|
|
2397
2381
|
// Collect all modified buttons that should be added to the WordList
|
|
2398
2382
|
const newWordListItems = [];
|
|
@@ -2405,9 +2389,18 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
2405
2389
|
? [originalGrid.Grid.Cells.Cell]
|
|
2406
2390
|
: [];
|
|
2407
2391
|
const cell = cellArray.find((c) => {
|
|
2408
|
-
const
|
|
2409
|
-
|
|
2410
|
-
|
|
2392
|
+
const cellY = parseInt(String(c['@_Y'] || c['@_Row'] || '0'), 10);
|
|
2393
|
+
// Check Y position first
|
|
2394
|
+
if (cellY !== pos.y) {
|
|
2395
|
+
return false;
|
|
2396
|
+
}
|
|
2397
|
+
const cellX = c['@_X'] !== undefined ? parseInt(String(c['@_X']), 10) : undefined;
|
|
2398
|
+
// If cell has no X attribute (full-width cell), it matches any button at this Y
|
|
2399
|
+
if (cellX === undefined) {
|
|
2400
|
+
return true;
|
|
2401
|
+
}
|
|
2402
|
+
// Otherwise, check exact X match
|
|
2403
|
+
return cellX === pos.x;
|
|
2411
2404
|
});
|
|
2412
2405
|
if (cell) {
|
|
2413
2406
|
const contentType = cell.Content?.ContentType || cell.Content?.contentType;
|
|
@@ -2416,11 +2409,14 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
2416
2409
|
// Note: Prediction cells are already skipped earlier, so they won't reach here
|
|
2417
2410
|
if (isWordListCell) {
|
|
2418
2411
|
// Add this button to the WordList with proper Grid 3 format
|
|
2419
|
-
// Format: <Text><s><r>label</r></s></Text>
|
|
2412
|
+
// Format: <Text><p><s><r>label</r></s></p></Text>
|
|
2413
|
+
// Note: <p> wrapper is required by Grid 3's WordList format
|
|
2420
2414
|
newWordListItems.push({
|
|
2421
2415
|
Text: {
|
|
2422
|
-
|
|
2423
|
-
|
|
2416
|
+
p: {
|
|
2417
|
+
s: {
|
|
2418
|
+
r: button.label,
|
|
2419
|
+
},
|
|
2424
2420
|
},
|
|
2425
2421
|
},
|
|
2426
2422
|
Image: '', // No image for user-added words
|
|
@@ -2447,23 +2443,46 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
2447
2443
|
originalGrid.Grid.WordList.Items.WordListItem = allItems;
|
|
2448
2444
|
}
|
|
2449
2445
|
}
|
|
2450
|
-
//
|
|
2446
|
+
// Process WordList items attached to the page (from personalisation)
|
|
2447
|
+
// These are tracked separately and shouldn't create new cells
|
|
2448
|
+
// Use a known symbol key to check for WordList items
|
|
2449
|
+
const WORDLIST_ITEMS_KEY = 'wordListItems';
|
|
2450
|
+
const wordListItems = page[WORDLIST_ITEMS_KEY];
|
|
2451
|
+
if (wordListItems && wordListItems.length > 0) {
|
|
2452
|
+
// Ensure WordList structure exists
|
|
2453
|
+
if (!originalGrid.Grid) {
|
|
2454
|
+
originalGrid.Grid = {};
|
|
2455
|
+
}
|
|
2456
|
+
if (!originalGrid.Grid.WordList) {
|
|
2457
|
+
originalGrid.Grid.WordList = {};
|
|
2458
|
+
}
|
|
2459
|
+
if (!originalGrid.Grid.WordList.Items) {
|
|
2460
|
+
originalGrid.Grid.WordList.Items = {};
|
|
2461
|
+
}
|
|
2462
|
+
const existingItems = originalGrid.Grid.WordList.Items.WordListItem ||
|
|
2463
|
+
originalGrid.Grid.WordList.Items.wordlistitem ||
|
|
2464
|
+
[];
|
|
2465
|
+
const itemsArray = Array.isArray(existingItems) ? existingItems : [existingItems];
|
|
2466
|
+
// Add new WordList items with proper Grid 3 format
|
|
2467
|
+
for (const item of wordListItems) {
|
|
2468
|
+
itemsArray.push({
|
|
2469
|
+
Text: {
|
|
2470
|
+
p: {
|
|
2471
|
+
s: {
|
|
2472
|
+
r: item.label,
|
|
2473
|
+
},
|
|
2474
|
+
},
|
|
2475
|
+
},
|
|
2476
|
+
Image: '',
|
|
2477
|
+
PartOfSpeech: 'Unknown',
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
// Update the WordList
|
|
2481
|
+
originalGrid.Grid.WordList.Items.WordListItem = itemsArray;
|
|
2482
|
+
}
|
|
2483
|
+
// Build the updated grid XML and format for Grid 3 compatibility
|
|
2451
2484
|
let builtXml = gridBuilder.build(originalGrid);
|
|
2452
|
-
|
|
2453
|
-
builtXml = builtXml.replace(/\n/g, '\r\n');
|
|
2454
|
-
// Expand self-closing tags to full opening/closing tags for Grid 3 compatibility
|
|
2455
|
-
// Grid 3 cannot parse <AudioDescription /> - it requires <AudioDescription></AudioDescription>
|
|
2456
|
-
builtXml = builtXml.replace(/<(\w+)(\s+[^>]*)?\s*\/>/g, '<$1$2></$1>');
|
|
2457
|
-
// Convert empty/whitespace captions to CDATA format for Grid 3 compatibility
|
|
2458
|
-
// Grid 3 requires <![CDATA[ ]]> for empty captions, not plain text
|
|
2459
|
-
builtXml = builtXml.replace(/<Caption><\/Caption>/g, '<Caption><![CDATA[ ]]></Caption>');
|
|
2460
|
-
builtXml = builtXml.replace(/<Caption> <\/Caption>/g, '<Caption><![CDATA[ ]]></Caption>');
|
|
2461
|
-
builtXml = builtXml.replace(/<Caption> {2}<\/Caption>/g, '<Caption><![CDATA[ ]]></Caption>');
|
|
2462
|
-
// Preserve CDATA in <r> tags for text parameters
|
|
2463
|
-
// Spaces in <r> tags must use CDATA or they get stripped during rendering
|
|
2464
|
-
// e.g., <r> </r> becomes <r><![CDATA[ ]]></r>
|
|
2465
|
-
builtXml = builtXml.replace(/<r> <\/r>/g, '<r><![CDATA[ ]]></r>');
|
|
2466
|
-
builtXml = builtXml.replace(/<r> {2}<\/r>/g, '<r><![CDATA[ ]]></r>');
|
|
2485
|
+
builtXml = (0, xmlFormatter_1.formatGrid3XmlComplete)(builtXml);
|
|
2467
2486
|
newGridFiles.set(gridPath, builtXml);
|
|
2468
2487
|
}
|
|
2469
2488
|
// Copy all files from original zip, replacing modified grid files
|
|
@@ -2532,57 +2551,14 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
2532
2551
|
// Preserve Grid 3 XML formatting requirements
|
|
2533
2552
|
suppressBooleanAttributes: false,
|
|
2534
2553
|
});
|
|
2535
|
-
// Build the grid XML and
|
|
2554
|
+
// Build the grid XML and format for Grid 3 compatibility
|
|
2536
2555
|
let builtXml = gridBuilder.build(gridData);
|
|
2537
|
-
builtXml =
|
|
2538
|
-
// Expand self-closing tags to full opening/closing tags for Grid 3 compatibility
|
|
2539
|
-
builtXml = builtXml.replace(/<(\w+)(\s+[^>]*)?\s*\/>/g, '<$1$2></$1>');
|
|
2556
|
+
builtXml = (0, xmlFormatter_1.formatGrid3XmlComplete)(builtXml);
|
|
2540
2557
|
return builtXml;
|
|
2541
2558
|
}
|
|
2542
2559
|
// Helper method to find button position with span information
|
|
2543
2560
|
findButtonPosition(page, button, fallbackIndex) {
|
|
2544
|
-
|
|
2545
|
-
// Search for button in grid layout and calculate span
|
|
2546
|
-
for (let y = 0; y < page.grid.length; y++) {
|
|
2547
|
-
for (let x = 0; x < page.grid[y].length; x++) {
|
|
2548
|
-
const current = page.grid[y][x];
|
|
2549
|
-
if (current && current.id === button.id) {
|
|
2550
|
-
// Calculate span by checking how far the same button extends
|
|
2551
|
-
let columnSpan = 1;
|
|
2552
|
-
let rowSpan = 1;
|
|
2553
|
-
// Check column span (rightward)
|
|
2554
|
-
while (x + columnSpan < page.grid[y].length) {
|
|
2555
|
-
const right = page.grid[y][x + columnSpan];
|
|
2556
|
-
if (right && right.id === button.id) {
|
|
2557
|
-
columnSpan++;
|
|
2558
|
-
}
|
|
2559
|
-
else {
|
|
2560
|
-
break;
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
// Check row span (downward)
|
|
2564
|
-
while (y + rowSpan < page.grid.length) {
|
|
2565
|
-
const below = page.grid[y + rowSpan][x];
|
|
2566
|
-
if (below && below.id === button.id) {
|
|
2567
|
-
rowSpan++;
|
|
2568
|
-
}
|
|
2569
|
-
else {
|
|
2570
|
-
break;
|
|
2571
|
-
}
|
|
2572
|
-
}
|
|
2573
|
-
return { x, y, columnSpan, rowSpan };
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
}
|
|
2577
|
-
}
|
|
2578
|
-
// Fallback positioning
|
|
2579
|
-
const gridCols = page.grid?.[0]?.length || Math.ceil(Math.sqrt(page.buttons.length));
|
|
2580
|
-
return {
|
|
2581
|
-
x: fallbackIndex % gridCols,
|
|
2582
|
-
y: Math.floor(fallbackIndex / gridCols),
|
|
2583
|
-
columnSpan: 1,
|
|
2584
|
-
rowSpan: 1,
|
|
2585
|
-
};
|
|
2561
|
+
return (0, cellHelpers_1.findButtonPosition)(page, button, fallbackIndex);
|
|
2586
2562
|
}
|
|
2587
2563
|
/**
|
|
2588
2564
|
* Extract strings with metadata for aac-tools-platform compatibility
|
package/dist/utils/sqlite.js
CHANGED
|
@@ -85,7 +85,9 @@ async function openSqliteDatabase(input, options = {}) {
|
|
|
85
85
|
throw new Error('SQLite file paths are not supported in browser environments.');
|
|
86
86
|
}
|
|
87
87
|
const Database = getBetterSqlite3();
|
|
88
|
-
const db = new Database(input, {
|
|
88
|
+
const db = new Database(input, {
|
|
89
|
+
readonly: options.readonly ?? true,
|
|
90
|
+
});
|
|
89
91
|
return { db };
|
|
90
92
|
}
|
|
91
93
|
const data = await readBinaryFromInput(input);
|
|
@@ -98,7 +100,9 @@ async function openSqliteDatabase(input, options = {}) {
|
|
|
98
100
|
const dbPath = join(tempDir, 'input.sqlite');
|
|
99
101
|
await writeBinaryToPath(dbPath, data);
|
|
100
102
|
const Database = getBetterSqlite3();
|
|
101
|
-
const db = new Database(dbPath, {
|
|
103
|
+
const db = new Database(dbPath, {
|
|
104
|
+
readonly: options.readonly ?? true,
|
|
105
|
+
});
|
|
102
106
|
const cleanup = async () => {
|
|
103
107
|
try {
|
|
104
108
|
db.close();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@willwade/aac-processors",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
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",
|