@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.
- package/dist/browser/core/baseProcessor.js +4 -0
- package/dist/browser/processors/applePanelsProcessor.js +24 -31
- package/dist/browser/processors/astericsGridProcessor.js +10 -3
- package/dist/browser/processors/dotProcessor.js +5 -2
- package/dist/browser/processors/gridset/colorUtils.js +354 -0
- package/dist/browser/processors/gridset/helpers.js +49 -45
- package/dist/browser/processors/gridset/index.js +61 -0
- package/dist/browser/processors/gridset/styleHelpers.js +205 -0
- package/dist/browser/processors/gridset/symbolExtractor.js +331 -0
- package/dist/browser/processors/gridset/symbolSearch.js +248 -0
- package/dist/browser/processors/gridset/symbols.js +35 -68
- package/dist/browser/processors/gridsetProcessor.js +32 -41
- package/dist/browser/processors/obfProcessor.js +53 -45
- package/dist/browser/processors/opmlProcessor.js +5 -2
- package/dist/browser/processors/snap/helpers.js +49 -45
- package/dist/browser/processors/snapProcessor.js +67 -31
- package/dist/browser/processors/touchchatProcessor.js +54 -45
- package/dist/browser/utilities/analytics/reference/index.js +27 -19
- package/dist/browser/utils/io.js +67 -14
- package/dist/browser/utils/sqlite.js +6 -8
- package/dist/browser/utils/zip.js +45 -43
- package/dist/browser/validation/baseValidator.js +5 -0
- package/dist/browser/validation/gridsetValidator.js +12 -20
- package/dist/browser/validation/obfValidator.js +5 -4
- package/dist/browser/validation/snapValidator.js +9 -5
- package/dist/browser/validation/touchChatValidator.js +21 -11
- package/dist/cli/index.js +10 -15
- package/dist/core/baseProcessor.d.ts +7 -7
- package/dist/core/baseProcessor.js +4 -0
- package/dist/processors/applePanelsProcessor.js +29 -36
- package/dist/processors/astericsGridProcessor.js +20 -13
- package/dist/processors/dotProcessor.js +10 -7
- package/dist/processors/excelProcessor.js +9 -12
- package/dist/processors/gridset/helpers.d.ts +9 -11
- package/dist/processors/gridset/helpers.js +49 -71
- package/dist/processors/gridset/imageDebug.d.ts +3 -5
- package/dist/processors/gridset/imageDebug.js +4 -4
- package/dist/processors/gridset/password.d.ts +1 -1
- package/dist/processors/gridset/symbolExtractor.d.ts +5 -3
- package/dist/processors/gridset/symbolExtractor.js +15 -38
- package/dist/processors/gridset/symbolSearch.d.ts +3 -2
- package/dist/processors/gridset/symbolSearch.js +12 -34
- package/dist/processors/gridset/symbols.d.ts +8 -6
- package/dist/processors/gridset/symbols.js +34 -67
- package/dist/processors/gridset/wordlistHelpers.d.ts +4 -6
- package/dist/processors/gridset/wordlistHelpers.js +15 -74
- package/dist/processors/gridsetProcessor.js +36 -68
- package/dist/processors/obfProcessor.js +58 -73
- package/dist/processors/obfsetProcessor.js +2 -2
- package/dist/processors/opmlProcessor.js +10 -7
- package/dist/processors/snap/helpers.d.ts +8 -8
- package/dist/processors/snap/helpers.js +50 -72
- package/dist/processors/snapProcessor.js +66 -30
- package/dist/processors/touchchatProcessor.js +54 -45
- package/dist/utilities/analytics/index.d.ts +3 -2
- package/dist/utilities/analytics/index.js +8 -10
- package/dist/utilities/analytics/reference/index.d.ts +5 -3
- package/dist/utilities/analytics/reference/index.js +26 -18
- package/dist/utilities/symbolTools.d.ts +4 -2
- package/dist/utilities/symbolTools.js +16 -15
- package/dist/utils/io.d.ts +24 -6
- package/dist/utils/io.js +64 -14
- package/dist/utils/sqlite.d.ts +2 -0
- package/dist/utils/sqlite.js +6 -8
- package/dist/utils/zip.d.ts +7 -3
- package/dist/utils/zip.js +45 -43
- package/dist/validation/applePanelsValidator.d.ts +2 -1
- package/dist/validation/applePanelsValidator.js +9 -11
- package/dist/validation/astericsValidator.d.ts +2 -1
- package/dist/validation/astericsValidator.js +5 -4
- package/dist/validation/baseValidator.d.ts +2 -2
- package/dist/validation/baseValidator.js +5 -0
- package/dist/validation/dotValidator.d.ts +2 -1
- package/dist/validation/dotValidator.js +5 -4
- package/dist/validation/excelValidator.d.ts +2 -1
- package/dist/validation/excelValidator.js +5 -4
- package/dist/validation/gridsetValidator.d.ts +2 -1
- package/dist/validation/gridsetValidator.js +11 -22
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +5 -4
- package/dist/validation/obfValidator.d.ts +2 -1
- package/dist/validation/obfValidator.js +5 -4
- package/dist/validation/obfsetValidator.d.ts +2 -1
- package/dist/validation/obfsetValidator.js +5 -4
- package/dist/validation/opmlValidator.d.ts +2 -1
- package/dist/validation/opmlValidator.js +5 -4
- package/dist/validation/snapValidator.d.ts +2 -1
- package/dist/validation/snapValidator.js +9 -5
- package/dist/validation/touchChatValidator.d.ts +4 -6
- package/dist/validation/touchChatValidator.js +21 -11
- package/dist/validation/validationTypes.d.ts +8 -1
- package/package.json +1 -1
- package/dist/core/fileProcessor.d.ts +0 -7
- 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 {
|
|
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 =
|
|
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
|
|
25
|
-
const
|
|
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
|
|
33
|
-
const
|
|
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
|
|
41
|
-
const
|
|
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
|
|
49
|
-
const
|
|
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
|
|
57
|
-
const
|
|
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
|
|
76
|
-
const
|
|
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(
|
|
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) =>
|
|
136
|
+
return requiredFiles.every((file) => pathExists(join(dataPath, file)));
|
|
129
137
|
}
|
package/dist/browser/utils/io.js
CHANGED
|
@@ -20,7 +20,7 @@ export function getNodeRequire() {
|
|
|
20
20
|
}
|
|
21
21
|
return cachedRequire;
|
|
22
22
|
}
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
136
|
+
function readTextFromInput(input, encoding = 'utf8') {
|
|
131
137
|
if (typeof input === 'string') {
|
|
132
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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 {
|
|
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
|
|
88
|
-
const
|
|
89
|
-
|
|
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
|
-
|
|
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,
|
|
2
|
-
export async function
|
|
3
|
-
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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,
|
|
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
|
|
21
|
-
const
|
|
22
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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,
|
|
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
|
|
25
|
-
return validator.validate(content, getBasename(filePath),
|
|
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 {
|
|
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
|
|
23
|
-
return validator.validate(content, getBasename(filePath),
|
|
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, {
|
|
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,
|
|
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
|
|
25
|
-
return validator.validate(content, getBasename(filePath),
|
|
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
|
|
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
|
|
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
|
|
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, {
|
|
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
|