@willwade/aac-processors 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/browser/core/baseProcessor.js +4 -0
  2. package/dist/browser/processors/applePanelsProcessor.js +24 -31
  3. package/dist/browser/processors/astericsGridProcessor.js +10 -3
  4. package/dist/browser/processors/dotProcessor.js +5 -2
  5. package/dist/browser/processors/gridset/colorUtils.js +354 -0
  6. package/dist/browser/processors/gridset/helpers.js +49 -45
  7. package/dist/browser/processors/gridset/index.js +61 -0
  8. package/dist/browser/processors/gridset/styleHelpers.js +205 -0
  9. package/dist/browser/processors/gridset/symbolExtractor.js +331 -0
  10. package/dist/browser/processors/gridset/symbolSearch.js +248 -0
  11. package/dist/browser/processors/gridset/symbols.js +35 -68
  12. package/dist/browser/processors/gridsetProcessor.js +32 -41
  13. package/dist/browser/processors/obfProcessor.js +53 -45
  14. package/dist/browser/processors/opmlProcessor.js +5 -2
  15. package/dist/browser/processors/snap/helpers.js +49 -45
  16. package/dist/browser/processors/snapProcessor.js +67 -31
  17. package/dist/browser/processors/touchchatProcessor.js +54 -45
  18. package/dist/browser/utilities/analytics/reference/index.js +27 -19
  19. package/dist/browser/utils/io.js +67 -14
  20. package/dist/browser/utils/sqlite.js +6 -8
  21. package/dist/browser/utils/zip.js +45 -43
  22. package/dist/browser/validation/baseValidator.js +5 -0
  23. package/dist/browser/validation/gridsetValidator.js +12 -20
  24. package/dist/browser/validation/obfValidator.js +5 -4
  25. package/dist/browser/validation/snapValidator.js +9 -5
  26. package/dist/browser/validation/touchChatValidator.js +21 -11
  27. package/dist/cli/index.js +10 -15
  28. package/dist/core/baseProcessor.d.ts +7 -7
  29. package/dist/core/baseProcessor.js +4 -0
  30. package/dist/processors/applePanelsProcessor.js +29 -36
  31. package/dist/processors/astericsGridProcessor.js +20 -13
  32. package/dist/processors/dotProcessor.js +10 -7
  33. package/dist/processors/excelProcessor.js +9 -12
  34. package/dist/processors/gridset/helpers.d.ts +9 -11
  35. package/dist/processors/gridset/helpers.js +49 -71
  36. package/dist/processors/gridset/imageDebug.d.ts +3 -5
  37. package/dist/processors/gridset/imageDebug.js +4 -4
  38. package/dist/processors/gridset/password.d.ts +1 -1
  39. package/dist/processors/gridset/symbolExtractor.d.ts +5 -3
  40. package/dist/processors/gridset/symbolExtractor.js +15 -38
  41. package/dist/processors/gridset/symbolSearch.d.ts +3 -2
  42. package/dist/processors/gridset/symbolSearch.js +12 -34
  43. package/dist/processors/gridset/symbols.d.ts +8 -6
  44. package/dist/processors/gridset/symbols.js +34 -67
  45. package/dist/processors/gridset/wordlistHelpers.d.ts +4 -6
  46. package/dist/processors/gridset/wordlistHelpers.js +15 -74
  47. package/dist/processors/gridsetProcessor.js +36 -68
  48. package/dist/processors/obfProcessor.js +58 -73
  49. package/dist/processors/obfsetProcessor.js +2 -2
  50. package/dist/processors/opmlProcessor.js +10 -7
  51. package/dist/processors/snap/helpers.d.ts +8 -8
  52. package/dist/processors/snap/helpers.js +50 -72
  53. package/dist/processors/snapProcessor.js +66 -30
  54. package/dist/processors/touchchatProcessor.js +54 -45
  55. package/dist/utilities/analytics/index.d.ts +3 -2
  56. package/dist/utilities/analytics/index.js +8 -10
  57. package/dist/utilities/analytics/reference/index.d.ts +5 -3
  58. package/dist/utilities/analytics/reference/index.js +26 -18
  59. package/dist/utilities/symbolTools.d.ts +4 -2
  60. package/dist/utilities/symbolTools.js +16 -15
  61. package/dist/utils/io.d.ts +24 -6
  62. package/dist/utils/io.js +64 -14
  63. package/dist/utils/sqlite.d.ts +2 -0
  64. package/dist/utils/sqlite.js +6 -8
  65. package/dist/utils/zip.d.ts +7 -3
  66. package/dist/utils/zip.js +45 -43
  67. package/dist/validation/applePanelsValidator.d.ts +2 -1
  68. package/dist/validation/applePanelsValidator.js +9 -11
  69. package/dist/validation/astericsValidator.d.ts +2 -1
  70. package/dist/validation/astericsValidator.js +5 -4
  71. package/dist/validation/baseValidator.d.ts +2 -2
  72. package/dist/validation/baseValidator.js +5 -0
  73. package/dist/validation/dotValidator.d.ts +2 -1
  74. package/dist/validation/dotValidator.js +5 -4
  75. package/dist/validation/excelValidator.d.ts +2 -1
  76. package/dist/validation/excelValidator.js +5 -4
  77. package/dist/validation/gridsetValidator.d.ts +2 -1
  78. package/dist/validation/gridsetValidator.js +11 -22
  79. package/dist/validation/index.d.ts +2 -2
  80. package/dist/validation/index.js +5 -4
  81. package/dist/validation/obfValidator.d.ts +2 -1
  82. package/dist/validation/obfValidator.js +5 -4
  83. package/dist/validation/obfsetValidator.d.ts +2 -1
  84. package/dist/validation/obfsetValidator.js +5 -4
  85. package/dist/validation/opmlValidator.d.ts +2 -1
  86. package/dist/validation/opmlValidator.js +5 -4
  87. package/dist/validation/snapValidator.d.ts +2 -1
  88. package/dist/validation/snapValidator.js +9 -5
  89. package/dist/validation/touchChatValidator.d.ts +4 -6
  90. package/dist/validation/touchChatValidator.js +21 -11
  91. package/dist/validation/validationTypes.d.ts +8 -1
  92. package/package.json +1 -1
  93. package/dist/core/fileProcessor.d.ts +0 -7
  94. package/dist/core/fileProcessor.js +0 -52
@@ -4,57 +4,63 @@
4
4
  * Loads reference vocabulary lists, core lists, and sentences
5
5
  * for AAC metrics analysis.
6
6
  */
7
- import { getFs, getPath } from '../../../utils/io';
7
+ import { defaultFileAdapter } from '../../../utils/io';
8
8
  export class ReferenceLoader {
9
- constructor(dataDir, locale = 'en') {
9
+ constructor(dataDir, locale = 'en', fileAdapter = defaultFileAdapter) {
10
10
  this.locale = locale;
11
+ this.fileAdapter = fileAdapter;
11
12
  if (dataDir) {
12
13
  this.dataDir = dataDir;
13
14
  }
14
15
  else {
15
16
  // Resolve the data directory relative to this file's location
16
17
  // Use __dirname which works correctly after compilation
17
- this.dataDir = getPath().join(__dirname, 'data');
18
+ this.dataDir = this.fileAdapter.join(__dirname, 'data');
18
19
  }
19
20
  }
20
21
  /**
21
22
  * Load core vocabulary lists
22
23
  */
23
24
  loadCoreLists() {
24
- const filePath = getPath().join(this.dataDir, `core_lists.${this.locale}.json`);
25
- const content = getFs().readFileSync(filePath, 'utf-8');
25
+ const { readTextFromInput } = this.fileAdapter;
26
+ const filePath = this.fileAdapter.join(this.dataDir, `core_lists.${this.locale}.json`);
27
+ const content = readTextFromInput(filePath);
26
28
  return JSON.parse(String(content));
27
29
  }
28
30
  /**
29
31
  * Load common words with baseline effort scores
30
32
  */
31
33
  loadCommonWords() {
32
- const filePath = getPath().join(this.dataDir, `common_words.${this.locale}.json`);
33
- const content = getFs().readFileSync(filePath, 'utf-8');
34
+ const { readTextFromInput, join } = this.fileAdapter;
35
+ const filePath = join(this.dataDir, `common_words.${this.locale}.json`);
36
+ const content = readTextFromInput(filePath);
34
37
  return JSON.parse(String(content));
35
38
  }
36
39
  /**
37
40
  * Load synonym mappings
38
41
  */
39
42
  loadSynonyms() {
40
- const filePath = getPath().join(this.dataDir, `synonyms.${this.locale}.json`);
41
- const content = getFs().readFileSync(filePath, 'utf-8');
43
+ const { readTextFromInput, join } = this.fileAdapter;
44
+ const filePath = join(this.dataDir, `synonyms.${this.locale}.json`);
45
+ const content = readTextFromInput(filePath);
42
46
  return JSON.parse(String(content));
43
47
  }
44
48
  /**
45
49
  * Load test sentences
46
50
  */
47
51
  loadSentences() {
48
- const filePath = getPath().join(this.dataDir, `sentences.${this.locale}.json`);
49
- const content = getFs().readFileSync(filePath, 'utf-8');
52
+ const { readTextFromInput, join } = this.fileAdapter;
53
+ const filePath = join(this.dataDir, `sentences.${this.locale}.json`);
54
+ const content = readTextFromInput(filePath);
50
55
  return JSON.parse(String(content));
51
56
  }
52
57
  /**
53
58
  * Load fringe vocabulary
54
59
  */
55
60
  loadFringe() {
56
- const filePath = getPath().join(this.dataDir, `fringe.${this.locale}.json`);
57
- const content = getFs().readFileSync(filePath, 'utf-8');
61
+ const { readTextFromInput, join } = this.fileAdapter;
62
+ const filePath = join(this.dataDir, `fringe.${this.locale}.json`);
63
+ const content = readTextFromInput(filePath);
58
64
  const data = JSON.parse(String(content));
59
65
  // Flatten nested category words if needed
60
66
  if (Array.isArray(data) && data.length > 0 && data[0].categories) {
@@ -72,8 +78,9 @@ export class ReferenceLoader {
72
78
  * Load base words hash map
73
79
  */
74
80
  loadBaseWords() {
75
- const filePath = getPath().join(this.dataDir, `base_words.${this.locale}.json`);
76
- const content = getFs().readFileSync(filePath, 'utf-8');
81
+ const { readTextFromInput, join } = this.fileAdapter;
82
+ const filePath = join(this.dataDir, `base_words.${this.locale}.json`);
83
+ const content = readTextFromInput(filePath);
77
84
  return JSON.parse(String(content));
78
85
  }
79
86
  /**
@@ -110,13 +117,14 @@ export class ReferenceLoader {
110
117
  /**
111
118
  * Get the default reference data path
112
119
  */
113
- export function getReferenceDataPath() {
114
- return String(getPath().join(__dirname, 'data'));
120
+ export function getReferenceDataPath(fileAdapter = defaultFileAdapter) {
121
+ return String(fileAdapter.join(__dirname, 'data'));
115
122
  }
116
123
  /**
117
124
  * Check if reference data files exist
118
125
  */
119
- export function hasReferenceData() {
126
+ export function hasReferenceData(fileAdapter = defaultFileAdapter) {
127
+ const { pathExists, join } = fileAdapter;
120
128
  const dataPath = getReferenceDataPath();
121
129
  const requiredFiles = [
122
130
  'core_lists.en.json',
@@ -125,5 +133,5 @@ export function hasReferenceData() {
125
133
  'synonyms.en.json',
126
134
  'fringe.en.json',
127
135
  ];
128
- return requiredFiles.every((file) => getFs().existsSync(getPath().join(dataPath, file)));
136
+ return requiredFiles.every((file) => pathExists(join(dataPath, file)));
129
137
  }
@@ -20,7 +20,7 @@ export function getNodeRequire() {
20
20
  }
21
21
  return cachedRequire;
22
22
  }
23
- export function getFs() {
23
+ function getFs() {
24
24
  if (!cachedFs) {
25
25
  try {
26
26
  const nodeRequire = getNodeRequire();
@@ -36,7 +36,7 @@ export function getFs() {
36
36
  }
37
37
  return cachedFs;
38
38
  }
39
- export function getPath() {
39
+ function getPath() {
40
40
  if (!cachedPath) {
41
41
  try {
42
42
  const nodeRequire = getNodeRequire();
@@ -114,10 +114,16 @@ export function encodeText(text) {
114
114
  }
115
115
  return new TextEncoder().encode(text);
116
116
  }
117
- export function readBinaryFromInput(input) {
117
+ // extname algorithm from node:path
118
+ const splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; //eslint-disable-line
119
+ const splitTailRe = /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/; //eslint-disable-line
120
+ export function extname(path) {
121
+ const tail = splitDeviceRe.exec(path)?.at(3) ?? '';
122
+ return splitTailRe.exec(tail)?.at(3) ?? '';
123
+ }
124
+ function readBinaryFromInput(input) {
118
125
  if (typeof input === 'string') {
119
- const fs = getFs();
120
- return fs.readFileSync(input);
126
+ return getFs().readFileSync(input);
121
127
  }
122
128
  if (typeof Buffer !== 'undefined' && Buffer.isBuffer(input)) {
123
129
  return input;
@@ -127,10 +133,9 @@ export function readBinaryFromInput(input) {
127
133
  }
128
134
  return input;
129
135
  }
130
- export function readTextFromInput(input, encoding = 'utf8') {
136
+ function readTextFromInput(input, encoding = 'utf8') {
131
137
  if (typeof input === 'string') {
132
- const fs = getFs();
133
- return fs.readFileSync(input, encoding);
138
+ return getFs().readFileSync(input, encoding);
134
139
  }
135
140
  if (typeof Buffer !== 'undefined' && Buffer.isBuffer(input)) {
136
141
  return input.toString(encoding);
@@ -140,11 +145,59 @@ export function readTextFromInput(input, encoding = 'utf8') {
140
145
  }
141
146
  return decodeText(input);
142
147
  }
143
- export function writeBinaryToPath(outputPath, data) {
144
- const fs = getFs();
145
- fs.writeFileSync(outputPath, data);
148
+ function writeBinaryToPath(outputPath, data) {
149
+ getFs().writeFileSync(outputPath, data);
150
+ }
151
+ function writeTextToPath(outputPath, text) {
152
+ getFs().writeFileSync(outputPath, text, 'utf8');
153
+ }
154
+ function pathExists(path) {
155
+ return getFs().existsSync(path);
156
+ }
157
+ function isDirectory(path) {
158
+ return getFs().statSync(path).isDirectory();
159
+ }
160
+ function getFileSize(path) {
161
+ return getFs().statSync(path).size;
162
+ }
163
+ function mkDir(path, options) {
164
+ getFs().mkdirSync(path, options);
165
+ }
166
+ function listDir(path) {
167
+ return getFs().readdirSync(path);
168
+ }
169
+ function removePath(path, options) {
170
+ getFs().rmSync(path, options);
171
+ }
172
+ function mkTempDir(prefix) {
173
+ const path = join(getOs().tmpdir(), prefix);
174
+ return getFs().mkdtempSync(path);
175
+ }
176
+ function join(...pathParts) {
177
+ return getPath().join(...pathParts);
178
+ }
179
+ export function joinWin32(...pathParts) {
180
+ return getPath().win32.join(...pathParts);
181
+ }
182
+ function dirname(path) {
183
+ return getPath().dirname(path);
146
184
  }
147
- export function writeTextToPath(outputPath, text) {
148
- const fs = getFs();
149
- fs.writeFileSync(outputPath, text, 'utf8');
185
+ function basename(path, suffix) {
186
+ return getPath().basename(path, suffix);
150
187
  }
188
+ export const defaultFileAdapter = {
189
+ readBinaryFromInput,
190
+ readTextFromInput,
191
+ writeBinaryToPath,
192
+ writeTextToPath,
193
+ pathExists,
194
+ isDirectory,
195
+ getFileSize,
196
+ mkDir,
197
+ listDir,
198
+ removePath,
199
+ mkTempDir,
200
+ join,
201
+ dirname,
202
+ basename,
203
+ };
@@ -1,4 +1,4 @@
1
- import { getFs, getNodeRequire, getOs, getPath, isNodeRuntime, readBinaryFromInput } from './io';
1
+ import { defaultFileAdapter, getNodeRequire, isNodeRuntime } from './io';
2
2
  let sqlJsConfig = null;
3
3
  let sqlJsPromise = null;
4
4
  export function configureSqlJs(config) {
@@ -70,6 +70,7 @@ export function requireBetterSqlite3() {
70
70
  return getBetterSqlite3();
71
71
  }
72
72
  export async function openSqliteDatabase(input, options = {}) {
73
+ const { readBinaryFromInput, mkTempDir, writeBinaryToPath, removePath, join } = options.fileAdapter ?? defaultFileAdapter;
73
74
  if (typeof input === 'string') {
74
75
  if (!isNodeRuntime()) {
75
76
  throw new Error('SQLite file paths are not supported in browser environments.');
@@ -84,12 +85,9 @@ export async function openSqliteDatabase(input, options = {}) {
84
85
  const db = new SQL.Database(data);
85
86
  return { db: createSqlJsAdapter(db) };
86
87
  }
87
- const fs = getFs();
88
- const path = getPath();
89
- const os = getOs();
90
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aac-sqlite-'));
91
- const dbPath = path.join(tempDir, 'input.sqlite');
92
- fs.writeFileSync(dbPath, data);
88
+ const tempDir = mkTempDir('aac-sqlite-');
89
+ const dbPath = join(tempDir, 'input.sqlite');
90
+ writeBinaryToPath(dbPath, data);
93
91
  const Database = getBetterSqlite3();
94
92
  const db = new Database(dbPath, { readonly: options.readonly ?? true });
95
93
  const cleanup = () => {
@@ -98,7 +96,7 @@ export async function openSqliteDatabase(input, options = {}) {
98
96
  }
99
97
  finally {
100
98
  try {
101
- fs.rmSync(tempDir, { recursive: true, force: true });
99
+ removePath(tempDir, { recursive: true, force: true });
102
100
  }
103
101
  catch (error) {
104
102
  console.warn('Failed to clean up temporary SQLite files:', error);
@@ -1,54 +1,56 @@
1
- import { isNodeRuntime, readBinaryFromInput, getNodeRequire } from './io';
2
- export async function openZipFromInput(input) {
3
- if (typeof input === 'string') {
4
- if (!isNodeRuntime()) {
5
- throw new Error('Zip file paths are not supported in browser environments.');
6
- }
7
- const AdmZip = getNodeRequire()('adm-zip');
8
- const admZip = new AdmZip(input);
9
- return {
10
- zip: {
11
- listFiles: () => admZip.getEntries().map((entry) => entry.entryName),
12
- readFile: (name) => {
13
- const entry = admZip.getEntry(name);
14
- if (!entry) {
15
- throw new Error(`Zip entry not found: ${name}`);
16
- }
17
- return Promise.resolve(entry.getData());
18
- },
19
- },
20
- };
21
- }
22
- const data = readBinaryFromInput(input);
1
+ import { isNodeRuntime, getNodeRequire, defaultFileAdapter, } from './io';
2
+ export async function getZipAdapter(input, fileAdapter) {
3
+ const adapter = fileAdapter ?? defaultFileAdapter;
23
4
  if (isNodeRuntime()) {
24
5
  const AdmZip = getNodeRequire()('adm-zip');
25
- const admZip = new AdmZip(Buffer.from(data));
6
+ const zip = input === undefined
7
+ ? new AdmZip(input)
8
+ : typeof input === 'string'
9
+ ? new AdmZip(input)
10
+ : new AdmZip(Buffer.from(adapter.readBinaryFromInput(input)));
26
11
  return {
27
- zip: {
28
- listFiles: () => admZip.getEntries().map((entry) => entry.entryName),
29
- readFile: (name) => {
30
- const entry = admZip.getEntry(name);
31
- if (!entry) {
32
- throw new Error(`Zip entry not found: ${name}`);
33
- }
34
- return Promise.resolve(entry.getData());
35
- },
12
+ listFiles: () => {
13
+ return zip
14
+ .getEntries()
15
+ .filter((entry) => !entry.isDirectory)
16
+ .map((entry) => entry.entryName);
17
+ },
18
+ readFile: (name) => {
19
+ const entry = zip.getEntry(name);
20
+ if (!entry)
21
+ throw new Error(`Zip entry not found: ${name}`);
22
+ return Promise.resolve(entry.getData());
23
+ },
24
+ writeFiles: (files) => {
25
+ files.forEach((file) => {
26
+ zip.addFile(file.name, Buffer.from(file.data));
27
+ });
28
+ return Promise.resolve(zip.toBuffer());
36
29
  },
37
30
  };
38
31
  }
39
32
  const module = await import('jszip');
40
- const init = module.default || module;
41
- const zip = await init.loadAsync(data);
33
+ const JSZip = module.default || module;
34
+ if (input !== undefined && typeof input === 'string')
35
+ throw new Error('Zip file paths are not supported in browser environments.');
36
+ const zip = input ? await JSZip.loadAsync(adapter.readBinaryFromInput(input)) : new JSZip();
42
37
  return {
43
- zip: {
44
- listFiles: () => Object.keys(zip.files),
45
- readFile: async (name) => {
46
- const file = zip.file(name);
47
- if (!file) {
48
- throw new Error(`Zip entry not found: ${name}`);
49
- }
50
- return file.async('uint8array');
51
- },
38
+ listFiles: () => {
39
+ return Object.entries(zip.files)
40
+ .filter(([_, entry]) => !entry.dir)
41
+ .map(([name, _]) => name);
42
+ },
43
+ readFile: async (name) => {
44
+ const file = zip.file(name);
45
+ if (!file)
46
+ throw new Error(`Zip entry not found: ${name}`);
47
+ return file.async('uint8array');
48
+ },
49
+ writeFiles: async (files) => {
50
+ files.forEach((file) => {
51
+ zip.file(file.name, file.data);
52
+ });
53
+ return await zip.generateAsync({ type: 'uint8array' });
52
54
  },
53
55
  };
54
56
  }
@@ -1,3 +1,5 @@
1
+ import { defaultFileAdapter } from '../utils/io';
2
+ import { getZipAdapter } from '../utils/zip';
1
3
  import { ValidationError, } from './validationTypes';
2
4
  /**
3
5
  * Base class for all format validators
@@ -14,6 +16,9 @@ export class BaseValidator {
14
16
  includeWarnings: options.includeWarnings ?? true,
15
17
  stopOnBlocker: options.stopOnBlocker ?? true,
16
18
  customRules: options.customRules || [],
19
+ fileAdapter: defaultFileAdapter,
20
+ zipAdapter: getZipAdapter,
21
+ ...options,
17
22
  };
18
23
  this.reset();
19
24
  }
@@ -1,10 +1,9 @@
1
1
  /* eslint-disable @typescript-eslint/require-await */
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-argument */
3
3
  /* eslint-disable @typescript-eslint/no-unsafe-return */
4
- import JSZip from 'jszip';
5
4
  import * as xml2js from 'xml2js';
6
5
  import { BaseValidator } from './baseValidator';
7
- import { decodeText, getBasename, getFs, toUint8Array } from '../utils/io';
6
+ import { decodeText, defaultFileAdapter, getBasename, toUint8Array, } from '../utils/io';
8
7
  /**
9
8
  * Validator for Grid3/Smartbox Gridset files (.gridset, .gridsetx)
10
9
  */
@@ -15,12 +14,12 @@ export class GridsetValidator extends BaseValidator {
15
14
  /**
16
15
  * Validate a Gridset file from disk
17
16
  */
18
- static async validateFile(filePath) {
17
+ static async validateFile(filePath, fileAdapter) {
18
+ const { readBinaryFromInput, getFileSize } = fileAdapter ?? defaultFileAdapter;
19
19
  const validator = new GridsetValidator();
20
- const fs = getFs();
21
- const content = fs.readFileSync(filePath);
22
- const stats = fs.statSync(filePath);
23
- return validator.validate(content, getBasename(filePath), stats.size);
20
+ const content = readBinaryFromInput(filePath);
21
+ const size = getFileSize(filePath);
22
+ return validator.validate(content, getBasename(filePath), size);
24
23
  }
25
24
  /**
26
25
  * Check if content is Gridset format
@@ -109,24 +108,17 @@ export class GridsetValidator extends BaseValidator {
109
108
  * Validate a ZIP archive (.gridset)
110
109
  */
111
110
  async validateZipArchive(content, filename, _filesize) {
112
- let zip;
113
- try {
114
- zip = await JSZip.loadAsync(toUint8Array(content));
115
- }
116
- catch (e) {
117
- this.err(`Failed to open ZIP archive: ${e.message}`, true);
118
- return;
119
- }
120
- const entries = Object.values(zip.files).filter((entry) => !entry.dir);
111
+ const zip = await this._options.zipAdapter(content);
112
+ const entries = zip.listFiles();
121
113
  // Check for gridset.xml (required)
122
114
  await this.add_check('gridset_xml_presence', 'gridset.xml presence', async () => {
123
- const gridsetEntry = entries.find((e) => e.name.toLowerCase() === 'gridset.xml');
115
+ const gridsetEntry = entries.find((e) => e.toLowerCase() === 'gridset.xml');
124
116
  if (!gridsetEntry) {
125
117
  this.err('Missing gridset.xml in archive', true);
126
118
  }
127
119
  else {
128
120
  try {
129
- const gridsetXml = await gridsetEntry.async('string');
121
+ const gridsetXml = await zip.readFile(gridsetEntry);
130
122
  const parser = new xml2js.Parser();
131
123
  const xmlObj = await parser.parseStringPromise(gridsetXml);
132
124
  const gridset = xmlObj.gridset || xmlObj.Gridset;
@@ -144,13 +136,13 @@ export class GridsetValidator extends BaseValidator {
144
136
  });
145
137
  // Check for settings.xml (highly recommended/required for metadata)
146
138
  await this.add_check('settings_xml_presence', 'settings.xml presence', async () => {
147
- const settingsEntry = entries.find((e) => e.name.toLowerCase() === 'settings.xml');
139
+ const settingsEntry = entries.find((e) => e.toLowerCase() === 'settings.xml');
148
140
  if (!settingsEntry) {
149
141
  this.warn('Missing settings.xml in archive (required for full metadata)');
150
142
  }
151
143
  else {
152
144
  try {
153
- const settingsXml = await settingsEntry.async('string');
145
+ const settingsXml = await zip.readFile(settingsEntry);
154
146
  const parser = new xml2js.Parser();
155
147
  const xmlObj = await parser.parseStringPromise(settingsXml);
156
148
  const settings = xmlObj.GridSetSettings || xmlObj.gridSetSettings || xmlObj.GridsetSettings;
@@ -5,7 +5,7 @@
5
5
  /* eslint-disable @typescript-eslint/restrict-template-expressions */
6
6
  import JSZip from 'jszip';
7
7
  import { BaseValidator } from './baseValidator';
8
- import { decodeText, getBasename, getFs, readBinaryFromInput, toUint8Array } from '../utils/io';
8
+ import { decodeText, defaultFileAdapter, getBasename, toUint8Array, } from '../utils/io';
9
9
  const OBF_FORMAT = 'open-board-0.1';
10
10
  const OBF_FORMAT_CURRENT_VERSION = 0.1;
11
11
  /**
@@ -18,11 +18,12 @@ export class ObfValidator extends BaseValidator {
18
18
  /**
19
19
  * Validate an OBF file from disk
20
20
  */
21
- static async validateFile(filePath) {
21
+ static async validateFile(filePath, fileAdapter) {
22
+ const { readBinaryFromInput, getFileSize } = fileAdapter ?? defaultFileAdapter;
22
23
  const validator = new ObfValidator();
23
24
  const content = readBinaryFromInput(filePath);
24
- const stats = getFs().statSync(filePath);
25
- return validator.validate(content, getBasename(filePath), stats.size);
25
+ const size = getFileSize(filePath);
26
+ return validator.validate(content, getBasename(filePath), size);
26
27
  }
27
28
  /**
28
29
  * Check if content is OBF format
@@ -3,7 +3,7 @@
3
3
  import * as xml2js from 'xml2js';
4
4
  import JSZip from 'jszip';
5
5
  import { BaseValidator } from './baseValidator';
6
- import { getBasename, getFs, readBinaryFromInput, toUint8Array } from '../utils/io';
6
+ import { defaultFileAdapter, getBasename, toUint8Array } from '../utils/io';
7
7
  import { openSqliteDatabase } from '../utils/sqlite';
8
8
  /**
9
9
  * Validator for Snap files (.spb, .sps)
@@ -16,11 +16,12 @@ export class SnapValidator extends BaseValidator {
16
16
  /**
17
17
  * Validate a Snap file from disk
18
18
  */
19
- static async validateFile(filePath) {
19
+ static async validateFile(filePath, fileAdapter) {
20
+ const { readBinaryFromInput, getFileSize } = fileAdapter ?? defaultFileAdapter;
20
21
  const validator = new SnapValidator();
21
22
  const content = readBinaryFromInput(filePath);
22
- const stats = getFs().statSync(filePath);
23
- return validator.validate(content, getBasename(filePath), stats.size);
23
+ const size = getFileSize(filePath);
24
+ return validator.validate(content, getBasename(filePath), size);
24
25
  }
25
26
  /**
26
27
  * Check if content is Snap format
@@ -158,7 +159,10 @@ export class SnapValidator extends BaseValidator {
158
159
  await this.add_check('sqlite', 'valid SQLite database', async () => {
159
160
  let cleanup;
160
161
  try {
161
- const result = await openSqliteDatabase(content, { readonly: true });
162
+ const result = await openSqliteDatabase(content, {
163
+ readonly: true,
164
+ fileAdapter: this._options.fileAdapter,
165
+ });
162
166
  const db = result.db;
163
167
  cleanup = result.cleanup;
164
168
  const tableRows = db
@@ -3,9 +3,9 @@
3
3
  /* eslint-disable @typescript-eslint/no-unsafe-return */
4
4
  import * as xml2js from 'xml2js';
5
5
  import { BaseValidator } from './baseValidator';
6
- import { decodeText, getBasename, getFs, readBinaryFromInput, toUint8Array, } from '../utils/io';
7
- import { openZipFromInput } from '../utils/zip';
6
+ import { decodeText, defaultFileAdapter, getBasename, toUint8Array, } from '../utils/io';
8
7
  import { openSqliteDatabase } from '../utils/sqlite';
8
+ import { getZipAdapter } from '../utils/zip';
9
9
  /**
10
10
  * Validator for TouchChat files (.ce)
11
11
  * TouchChat files are ZIP archives that contain a .c4v SQLite database.
@@ -18,23 +18,26 @@ export class TouchChatValidator extends BaseValidator {
18
18
  /**
19
19
  * Validate a TouchChat file from disk
20
20
  */
21
- static async validateFile(filePath) {
21
+ static async validateFile(filePath, fileAdapter) {
22
+ const { readBinaryFromInput, getFileSize } = fileAdapter ?? defaultFileAdapter;
22
23
  const validator = new TouchChatValidator();
23
24
  const content = readBinaryFromInput(filePath);
24
- const stats = getFs().statSync(filePath);
25
- return validator.validate(content, getBasename(filePath), stats.size);
25
+ const size = getFileSize(filePath);
26
+ return validator.validate(content, getBasename(filePath), size);
26
27
  }
27
28
  /**
28
29
  * Check if content is TouchChat format
29
30
  */
30
- static async identifyFormat(content, filename, zipAdapter) {
31
+ static async identifyFormat(content, filename, fileAdapter = defaultFileAdapter, zipAdapter) {
31
32
  const name = filename.toLowerCase();
32
33
  if (name.endsWith('.ce')) {
33
34
  return true;
34
35
  }
35
36
  // Try to parse as ZIP and check for .c4v database
36
37
  try {
37
- const { zip } = zipAdapter ? await zipAdapter(content) : await openZipFromInput(content);
38
+ const zip = zipAdapter
39
+ ? await zipAdapter(content)
40
+ : await getZipAdapter(content, fileAdapter);
38
41
  const entries = zip.listFiles();
39
42
  if (entries.some((entry) => entry.toLowerCase().endsWith('.c4v'))) {
40
43
  return true;
@@ -66,7 +69,9 @@ export class TouchChatValidator extends BaseValidator {
66
69
  }
67
70
  });
68
71
  const looksLikeXml = this.isXmlBuffer(content);
69
- const zipped = looksLikeXml ? false : await this.tryValidateZipSqlite(content);
72
+ const zipped = looksLikeXml
73
+ ? false
74
+ : await this.tryValidateZipSqlite(content, this._options.fileAdapter, this._options.zipAdapter);
70
75
  if (!zipped) {
71
76
  let xmlObj = null;
72
77
  await this.add_check('xml_parse', 'valid XML', async () => {
@@ -246,11 +251,13 @@ export class TouchChatValidator extends BaseValidator {
246
251
  }
247
252
  return bytes[start] === 0x3c; // '<'
248
253
  }
249
- async tryValidateZipSqlite(content) {
254
+ async tryValidateZipSqlite(content, fileAdapter = defaultFileAdapter, zipAdapter) {
250
255
  let usedZip = false;
251
256
  await this.add_check('zip', 'TouchChat ZIP package', async () => {
252
257
  try {
253
- const { zip } = await openZipFromInput(content);
258
+ const zip = zipAdapter
259
+ ? await zipAdapter(content)
260
+ : await getZipAdapter(content, fileAdapter);
254
261
  const entries = zip.listFiles();
255
262
  const vocabEntry = entries.find((name) => name.toLowerCase().endsWith('.c4v'));
256
263
  if (!vocabEntry) {
@@ -275,7 +282,10 @@ export class TouchChatValidator extends BaseValidator {
275
282
  await this.add_check('sqlite', 'valid TouchChat SQLite database', async () => {
276
283
  let cleanup;
277
284
  try {
278
- const result = await openSqliteDatabase(content, { readonly: true });
285
+ const result = await openSqliteDatabase(content, {
286
+ readonly: true,
287
+ fileAdapter: this._options.fileAdapter,
288
+ });
279
289
  const db = result.db;
280
290
  cleanup = result.cleanup;
281
291
  const tableRows = db