@willwade/aac-processors 0.0.22 → 0.0.23
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 +19 -27
- package/dist/processors/applePanelsProcessor.js +166 -123
- package/dist/processors/astericsGridProcessor.js +121 -105
- package/dist/processors/dotProcessor.js +83 -65
- package/dist/processors/opmlProcessor.js +82 -44
- package/dist/validation/applePanelsValidator.d.ts +10 -0
- package/dist/validation/applePanelsValidator.js +124 -0
- package/dist/validation/astericsValidator.d.ts +16 -0
- package/dist/validation/astericsValidator.js +115 -0
- package/dist/validation/dotValidator.d.ts +10 -0
- package/dist/validation/dotValidator.js +113 -0
- package/dist/validation/excelValidator.d.ts +10 -0
- package/dist/validation/excelValidator.js +89 -0
- package/dist/validation/index.d.ts +14 -1
- package/dist/validation/index.js +104 -1
- package/dist/validation/obfsetValidator.d.ts +10 -0
- package/dist/validation/obfsetValidator.js +103 -0
- package/dist/validation/opmlValidator.d.ts +10 -0
- package/dist/validation/opmlValidator.js +107 -0
- package/dist/validation/validationTypes.d.ts +22 -0
- package/dist/validation/validationTypes.js +38 -1
- package/dist/validation.d.ts +8 -2
- package/dist/validation.js +16 -1
- package/package.json +1 -1
|
@@ -8,6 +8,8 @@ const baseProcessor_1 = require("../core/baseProcessor");
|
|
|
8
8
|
const treeStructure_1 = require("../core/treeStructure");
|
|
9
9
|
// Removed unused import: FileProcessor
|
|
10
10
|
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const validation_1 = require("../validation");
|
|
11
13
|
class DotProcessor extends baseProcessor_1.BaseProcessor {
|
|
12
14
|
constructor(options) {
|
|
13
15
|
super(options);
|
|
@@ -72,78 +74,94 @@ class DotProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
72
74
|
return texts;
|
|
73
75
|
}
|
|
74
76
|
loadIntoTree(filePathOrBuffer) {
|
|
75
|
-
|
|
77
|
+
const filename = typeof filePathOrBuffer === 'string' ? path_1.default.basename(filePathOrBuffer) : 'upload.dot';
|
|
78
|
+
const buffer = Buffer.isBuffer(filePathOrBuffer)
|
|
79
|
+
? filePathOrBuffer
|
|
80
|
+
: fs_1.default.readFileSync(filePathOrBuffer);
|
|
81
|
+
const filesize = buffer.byteLength;
|
|
76
82
|
try {
|
|
77
|
-
content =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
const content = buffer.toString('utf8');
|
|
84
|
+
if (!content || content.trim().length === 0) {
|
|
85
|
+
const validation = (0, validation_1.buildValidationResultFromMessage)({
|
|
86
|
+
filename,
|
|
87
|
+
filesize,
|
|
88
|
+
format: 'dot',
|
|
89
|
+
message: 'DOT file is empty',
|
|
90
|
+
type: 'content',
|
|
91
|
+
description: 'DOT file content',
|
|
92
|
+
});
|
|
93
|
+
throw new validation_1.ValidationFailureError('Empty DOT content', validation);
|
|
94
|
+
}
|
|
95
|
+
// Check for binary data (contains null bytes or non-printable characters)
|
|
96
|
+
const head = content.substring(0, 100);
|
|
97
|
+
for (let i = 0; i < head.length; i++) {
|
|
98
|
+
const code = head.charCodeAt(i);
|
|
99
|
+
if (code === 0 || (code >= 0 && code <= 8) || (code >= 14 && code <= 31)) {
|
|
100
|
+
const validation = (0, validation_1.buildValidationResultFromMessage)({
|
|
101
|
+
filename,
|
|
102
|
+
filesize,
|
|
103
|
+
format: 'dot',
|
|
104
|
+
message: 'DOT appears to be binary data',
|
|
105
|
+
type: 'content',
|
|
106
|
+
description: 'DOT file content',
|
|
107
|
+
});
|
|
108
|
+
throw new validation_1.ValidationFailureError('Invalid DOT content', validation);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const { nodes, edges } = this.parseDotFile(content);
|
|
112
|
+
const tree = new treeStructure_1.AACTree();
|
|
113
|
+
tree.metadata.format = 'dot';
|
|
114
|
+
// Create pages for each node and add a self button representing the node label
|
|
115
|
+
for (const node of nodes) {
|
|
116
|
+
const page = new treeStructure_1.AACPage({
|
|
117
|
+
id: node.id,
|
|
118
|
+
name: node.label,
|
|
119
|
+
grid: [],
|
|
120
|
+
buttons: [],
|
|
121
|
+
parentId: null,
|
|
122
|
+
});
|
|
123
|
+
tree.addPage(page);
|
|
124
|
+
// Add a self button so single-node graphs yield one button
|
|
125
|
+
page.addButton(new treeStructure_1.AACButton({
|
|
126
|
+
id: `${node.id}_self`,
|
|
127
|
+
label: node.label,
|
|
128
|
+
message: node.label,
|
|
129
|
+
semanticAction: {
|
|
130
|
+
intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
|
|
131
|
+
text: node.label,
|
|
132
|
+
fallback: { type: 'SPEAK', message: node.label },
|
|
133
|
+
},
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
// Create navigation buttons based on edges
|
|
137
|
+
for (const edge of edges) {
|
|
138
|
+
const fromPage = tree.getPage(edge.from);
|
|
139
|
+
if (fromPage) {
|
|
140
|
+
const button = new treeStructure_1.AACButton({
|
|
141
|
+
id: `nav_${edge.from}_${edge.to}`,
|
|
142
|
+
label: edge.label || edge.to,
|
|
143
|
+
message: '',
|
|
144
|
+
targetPageId: edge.to,
|
|
145
|
+
});
|
|
146
|
+
fromPage.addButton(button);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return tree;
|
|
81
150
|
}
|
|
82
151
|
catch (error) {
|
|
83
|
-
|
|
84
|
-
if (typeof filePathOrBuffer === 'string') {
|
|
152
|
+
if (error instanceof validation_1.ValidationFailureError) {
|
|
85
153
|
throw error;
|
|
86
154
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// Check for binary data (contains null bytes or non-printable characters) without control-regex
|
|
95
|
-
const head = content.substring(0, 100);
|
|
96
|
-
let hasControl = false;
|
|
97
|
-
for (let i = 0; i < head.length; i++) {
|
|
98
|
-
const code = head.charCodeAt(i);
|
|
99
|
-
// Allow UTF-8 characters (code >= 127)
|
|
100
|
-
if (code === 0 || (code >= 0 && code <= 8) || (code >= 14 && code <= 31)) {
|
|
101
|
-
hasControl = true;
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
if (hasControl) {
|
|
106
|
-
return new treeStructure_1.AACTree();
|
|
107
|
-
}
|
|
108
|
-
const { nodes, edges } = this.parseDotFile(content);
|
|
109
|
-
const tree = new treeStructure_1.AACTree();
|
|
110
|
-
tree.metadata.format = 'dot';
|
|
111
|
-
// Create pages for each node and add a self button representing the node label
|
|
112
|
-
for (const node of nodes) {
|
|
113
|
-
const page = new treeStructure_1.AACPage({
|
|
114
|
-
id: node.id,
|
|
115
|
-
name: node.label,
|
|
116
|
-
grid: [],
|
|
117
|
-
buttons: [],
|
|
118
|
-
parentId: null,
|
|
155
|
+
const validation = (0, validation_1.buildValidationResultFromMessage)({
|
|
156
|
+
filename,
|
|
157
|
+
filesize,
|
|
158
|
+
format: 'dot',
|
|
159
|
+
message: error?.message || 'Failed to parse DOT file',
|
|
160
|
+
type: 'parse',
|
|
161
|
+
description: 'Parse DOT graph',
|
|
119
162
|
});
|
|
120
|
-
|
|
121
|
-
// Add a self button so single-node graphs yield one button
|
|
122
|
-
page.addButton(new treeStructure_1.AACButton({
|
|
123
|
-
id: `${node.id}_self`,
|
|
124
|
-
label: node.label,
|
|
125
|
-
message: node.label,
|
|
126
|
-
semanticAction: {
|
|
127
|
-
intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
|
|
128
|
-
text: node.label,
|
|
129
|
-
fallback: { type: 'SPEAK', message: node.label },
|
|
130
|
-
},
|
|
131
|
-
}));
|
|
132
|
-
}
|
|
133
|
-
// Create navigation buttons based on edges
|
|
134
|
-
for (const edge of edges) {
|
|
135
|
-
const fromPage = tree.getPage(edge.from);
|
|
136
|
-
if (fromPage) {
|
|
137
|
-
const button = new treeStructure_1.AACButton({
|
|
138
|
-
id: `nav_${edge.from}_${edge.to}`,
|
|
139
|
-
label: edge.label || edge.to,
|
|
140
|
-
message: '',
|
|
141
|
-
targetPageId: edge.to,
|
|
142
|
-
});
|
|
143
|
-
fromPage.addButton(button);
|
|
144
|
-
}
|
|
163
|
+
throw new validation_1.ValidationFailureError('Failed to load DOT file', validation, error);
|
|
145
164
|
}
|
|
146
|
-
return tree;
|
|
147
165
|
}
|
|
148
166
|
processTexts(filePathOrBuffer, translations, outputPath) {
|
|
149
167
|
const safeBuffer = Buffer.isBuffer(filePathOrBuffer)
|
|
@@ -9,6 +9,8 @@ const treeStructure_1 = require("../core/treeStructure");
|
|
|
9
9
|
// Removed unused import: FileProcessor
|
|
10
10
|
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
11
11
|
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const validation_1 = require("../validation");
|
|
12
14
|
class OpmlProcessor extends baseProcessor_1.BaseProcessor {
|
|
13
15
|
constructor(options) {
|
|
14
16
|
super(options);
|
|
@@ -88,55 +90,91 @@ class OpmlProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
88
90
|
return texts;
|
|
89
91
|
}
|
|
90
92
|
loadIntoTree(filePathOrBuffer) {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
// Validate XML before parsing, fast-xml-parser is permissive by default
|
|
98
|
-
const validationResult = fast_xml_parser_1.XMLValidator.validate(content);
|
|
99
|
-
if (validationResult !== true) {
|
|
100
|
-
const reason = validationResult?.err?.msg || JSON.stringify(validationResult);
|
|
101
|
-
throw new Error(`Invalid OPML XML: ${reason}`);
|
|
102
|
-
}
|
|
103
|
-
const parser = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false });
|
|
104
|
-
let data;
|
|
93
|
+
const filename = typeof filePathOrBuffer === 'string' ? path_1.default.basename(filePathOrBuffer) : 'upload.opml';
|
|
94
|
+
const buffer = Buffer.isBuffer(filePathOrBuffer)
|
|
95
|
+
? filePathOrBuffer
|
|
96
|
+
: fs_1.default.readFileSync(filePathOrBuffer);
|
|
97
|
+
const content = buffer.toString('utf8');
|
|
105
98
|
try {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return tree; // Return empty tree if no outline data
|
|
117
|
-
}
|
|
118
|
-
const outlines = Array.isArray(bodyOutline) ? bodyOutline : [bodyOutline];
|
|
119
|
-
let firstRootId = null;
|
|
120
|
-
outlines.forEach((outline) => {
|
|
121
|
-
const { page, childPages } = this.processOutline(outline);
|
|
122
|
-
if (page && page.id) {
|
|
123
|
-
tree.addPage(page);
|
|
124
|
-
if (!firstRootId)
|
|
125
|
-
firstRootId = page.id;
|
|
99
|
+
if (!content || !content.trim()) {
|
|
100
|
+
const validationResult = (0, validation_1.buildValidationResultFromMessage)({
|
|
101
|
+
filename,
|
|
102
|
+
filesize: buffer.byteLength,
|
|
103
|
+
format: 'opml',
|
|
104
|
+
message: 'Empty OPML content',
|
|
105
|
+
type: 'content',
|
|
106
|
+
description: 'OPML content is empty',
|
|
107
|
+
});
|
|
108
|
+
throw new validation_1.ValidationFailureError('Empty OPML content', validationResult);
|
|
126
109
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
110
|
+
// Validate XML before parsing, fast-xml-parser is permissive by default
|
|
111
|
+
const validationResult = fast_xml_parser_1.XMLValidator.validate(content);
|
|
112
|
+
if (validationResult !== true) {
|
|
113
|
+
const reason = validationResult?.err?.msg || JSON.stringify(validationResult);
|
|
114
|
+
const structured = (0, validation_1.buildValidationResultFromMessage)({
|
|
115
|
+
filename,
|
|
116
|
+
filesize: buffer.byteLength,
|
|
117
|
+
format: 'opml',
|
|
118
|
+
message: `Invalid OPML XML: ${reason}`,
|
|
119
|
+
type: 'xml',
|
|
120
|
+
description: 'OPML XML validation',
|
|
121
|
+
});
|
|
122
|
+
throw new validation_1.ValidationFailureError('Invalid OPML XML', structured);
|
|
123
|
+
}
|
|
124
|
+
const parser = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false });
|
|
125
|
+
const data = parser.parse(content);
|
|
126
|
+
const tree = new treeStructure_1.AACTree();
|
|
127
|
+
tree.metadata.format = 'opml';
|
|
128
|
+
// Handle case where body.outline might not exist or be in different formats
|
|
129
|
+
const bodyOutline = data.opml?.body?.outline;
|
|
130
|
+
if (!bodyOutline) {
|
|
131
|
+
const structured = (0, validation_1.buildValidationResultFromMessage)({
|
|
132
|
+
filename,
|
|
133
|
+
filesize: buffer.byteLength,
|
|
134
|
+
format: 'opml',
|
|
135
|
+
message: 'Missing body.outline in OPML document',
|
|
136
|
+
type: 'structure',
|
|
137
|
+
description: 'OPML outline root',
|
|
138
|
+
});
|
|
139
|
+
throw new validation_1.ValidationFailureError('Invalid OPML structure', structured);
|
|
140
|
+
}
|
|
141
|
+
const outlines = Array.isArray(bodyOutline) ? bodyOutline : [bodyOutline];
|
|
142
|
+
let firstRootId = null;
|
|
143
|
+
outlines.forEach((outline) => {
|
|
144
|
+
const { page, childPages } = this.processOutline(outline);
|
|
145
|
+
if (page && page.id) {
|
|
146
|
+
tree.addPage(page);
|
|
147
|
+
if (!firstRootId)
|
|
148
|
+
firstRootId = page.id;
|
|
149
|
+
}
|
|
150
|
+
childPages.forEach((childPage) => {
|
|
151
|
+
if (childPage && childPage.id)
|
|
152
|
+
tree.addPage(childPage);
|
|
153
|
+
});
|
|
130
154
|
});
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
155
|
+
// Set rootId to first root page, or fallback to first page if any exist
|
|
156
|
+
if (firstRootId) {
|
|
157
|
+
tree.rootId = firstRootId;
|
|
158
|
+
}
|
|
159
|
+
else if (Object.keys(tree.pages).length > 0) {
|
|
160
|
+
tree.rootId = Object.keys(tree.pages)[0];
|
|
161
|
+
}
|
|
162
|
+
return tree;
|
|
135
163
|
}
|
|
136
|
-
|
|
137
|
-
|
|
164
|
+
catch (err) {
|
|
165
|
+
if (err instanceof validation_1.ValidationFailureError) {
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
const validationResult = (0, validation_1.buildValidationResultFromMessage)({
|
|
169
|
+
filename,
|
|
170
|
+
filesize: buffer.byteLength,
|
|
171
|
+
format: 'opml',
|
|
172
|
+
message: err?.message || 'Failed to parse OPML',
|
|
173
|
+
type: 'parse',
|
|
174
|
+
description: 'Parse OPML XML',
|
|
175
|
+
});
|
|
176
|
+
throw new validation_1.ValidationFailureError('Failed to load OPML file', validationResult, err);
|
|
138
177
|
}
|
|
139
|
-
return tree;
|
|
140
178
|
}
|
|
141
179
|
processTexts(filePathOrBuffer, translations, outputPath) {
|
|
142
180
|
const content = typeof filePathOrBuffer === 'string'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseValidator } from './baseValidator';
|
|
2
|
+
import { ValidationResult } from './validationTypes';
|
|
3
|
+
/**
|
|
4
|
+
* Validator for Apple Panels (.plist or .ascconfig directory)
|
|
5
|
+
*/
|
|
6
|
+
export declare class ApplePanelsValidator extends BaseValidator {
|
|
7
|
+
static validateFile(filePath: string): Promise<ValidationResult>;
|
|
8
|
+
static identifyFormat(content: any, filename: string): Promise<boolean>;
|
|
9
|
+
validate(content: Buffer | Uint8Array, filename: string, filesize: number): Promise<ValidationResult>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.ApplePanelsValidator = void 0;
|
|
30
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
31
|
+
const fs = __importStar(require("fs"));
|
|
32
|
+
const path = __importStar(require("path"));
|
|
33
|
+
const plist_1 = __importDefault(require("plist"));
|
|
34
|
+
const baseValidator_1 = require("./baseValidator");
|
|
35
|
+
/**
|
|
36
|
+
* Validator for Apple Panels (.plist or .ascconfig directory)
|
|
37
|
+
*/
|
|
38
|
+
class ApplePanelsValidator extends baseValidator_1.BaseValidator {
|
|
39
|
+
static async validateFile(filePath) {
|
|
40
|
+
const validator = new ApplePanelsValidator();
|
|
41
|
+
let content;
|
|
42
|
+
const filename = path.basename(filePath);
|
|
43
|
+
let size = 0;
|
|
44
|
+
const stats = fs.existsSync(filePath) ? fs.statSync(filePath) : null;
|
|
45
|
+
if (stats?.isDirectory() && filename.toLowerCase().endsWith('.ascconfig')) {
|
|
46
|
+
const panelPath = path.join(filePath, 'Contents', 'Resources', 'PanelDefinitions.plist');
|
|
47
|
+
if (!fs.existsSync(panelPath)) {
|
|
48
|
+
return validator.validate(Buffer.alloc(0), filename, 0);
|
|
49
|
+
}
|
|
50
|
+
content = fs.readFileSync(panelPath);
|
|
51
|
+
size = fs.statSync(panelPath).size;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
content = fs.readFileSync(filePath);
|
|
55
|
+
size = stats?.size || content.byteLength;
|
|
56
|
+
}
|
|
57
|
+
return validator.validate(content, filename, size);
|
|
58
|
+
}
|
|
59
|
+
static async identifyFormat(content, filename) {
|
|
60
|
+
const name = filename.toLowerCase();
|
|
61
|
+
if (name.endsWith('.plist') || name.endsWith('.ascconfig')) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const str = Buffer.isBuffer(content) ? content.toString('utf-8') : String(content);
|
|
66
|
+
const parsed = plist_1.default.parse(str);
|
|
67
|
+
return Boolean(parsed.panels || parsed.Panels);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async validate(content, filename, filesize) {
|
|
74
|
+
this.reset();
|
|
75
|
+
await this.add_check('filename', 'file extension', async () => {
|
|
76
|
+
if (!filename.toLowerCase().match(/\.(plist|ascconfig)$/)) {
|
|
77
|
+
this.warn('filename should end with .plist or .ascconfig');
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
let parsed = null;
|
|
81
|
+
await this.add_check('plist_parse', 'valid plist/XML', async () => {
|
|
82
|
+
try {
|
|
83
|
+
const str = Buffer.isBuffer(content) ? content.toString('utf-8') : String(content);
|
|
84
|
+
parsed = plist_1.default.parse(str);
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
this.err(`Failed to parse plist: ${e.message}`, true);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
if (!parsed) {
|
|
91
|
+
return this.buildResult(filename, filesize, 'applepanels');
|
|
92
|
+
}
|
|
93
|
+
let panels = [];
|
|
94
|
+
await this.add_check('panels', 'panels present', async () => {
|
|
95
|
+
if (Array.isArray(parsed?.panels)) {
|
|
96
|
+
panels = parsed?.panels;
|
|
97
|
+
}
|
|
98
|
+
else if (parsed?.Panels && typeof parsed.Panels === 'object') {
|
|
99
|
+
panels = Object.values(parsed.Panels);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.err('missing panels/PanelDefinitions content', true);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
panels.slice(0, 5).forEach((panel, idx) => {
|
|
106
|
+
const prefix = `panel[${idx}]`;
|
|
107
|
+
this.add_check_sync(`${prefix}_id`, `${prefix} id`, () => {
|
|
108
|
+
if (!panel?.ID && !panel?.id) {
|
|
109
|
+
this.err('panel missing ID');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
this.add_check_sync(`${prefix}_buttons`, `${prefix} buttons`, () => {
|
|
113
|
+
const buttons = Array.isArray(panel?.PanelObjects)
|
|
114
|
+
? panel.PanelObjects.filter((obj) => obj?.PanelObjectType === 'Button')
|
|
115
|
+
: [];
|
|
116
|
+
if (buttons.length === 0) {
|
|
117
|
+
this.warn('panel has no buttons');
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
return this.buildResult(filename, filesize, 'applepanels');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
exports.ApplePanelsValidator = ApplePanelsValidator;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseValidator } from './baseValidator';
|
|
2
|
+
import { ValidationResult } from './validationTypes';
|
|
3
|
+
/**
|
|
4
|
+
* Validator for Asterics Grid (.grd) JSON files
|
|
5
|
+
*/
|
|
6
|
+
export declare class AstericsGridValidator extends BaseValidator {
|
|
7
|
+
/**
|
|
8
|
+
* Validate from disk
|
|
9
|
+
*/
|
|
10
|
+
static validateFile(filePath: string): Promise<ValidationResult>;
|
|
11
|
+
/**
|
|
12
|
+
* Identify whether the content appears to be an Asterics .grd file
|
|
13
|
+
*/
|
|
14
|
+
static identifyFormat(content: any, filename: string): Promise<boolean>;
|
|
15
|
+
validate(content: Buffer | Uint8Array, filename: string, filesize: number): Promise<ValidationResult>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.AstericsGridValidator = void 0;
|
|
27
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
28
|
+
const fs = __importStar(require("fs"));
|
|
29
|
+
const path = __importStar(require("path"));
|
|
30
|
+
const baseValidator_1 = require("./baseValidator");
|
|
31
|
+
/**
|
|
32
|
+
* Validator for Asterics Grid (.grd) JSON files
|
|
33
|
+
*/
|
|
34
|
+
class AstericsGridValidator extends baseValidator_1.BaseValidator {
|
|
35
|
+
/**
|
|
36
|
+
* Validate from disk
|
|
37
|
+
*/
|
|
38
|
+
static async validateFile(filePath) {
|
|
39
|
+
const validator = new AstericsGridValidator();
|
|
40
|
+
const content = fs.readFileSync(filePath);
|
|
41
|
+
const stats = fs.statSync(filePath);
|
|
42
|
+
return validator.validate(content, path.basename(filePath), stats.size);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Identify whether the content appears to be an Asterics .grd file
|
|
46
|
+
*/
|
|
47
|
+
static async identifyFormat(content, filename) {
|
|
48
|
+
const name = filename.toLowerCase();
|
|
49
|
+
if (name.endsWith('.grd')) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const str = Buffer.isBuffer(content) ? content.toString('utf-8') : String(content);
|
|
54
|
+
const json = JSON.parse(str);
|
|
55
|
+
return Array.isArray(json?.grids);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async validate(content, filename, filesize) {
|
|
62
|
+
this.reset();
|
|
63
|
+
await this.add_check('filename', 'file extension', async () => {
|
|
64
|
+
if (!filename.toLowerCase().endsWith('.grd')) {
|
|
65
|
+
this.warn('filename should end with .grd');
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
let json = null;
|
|
69
|
+
await this.add_check('json_parse', 'valid JSON', async () => {
|
|
70
|
+
try {
|
|
71
|
+
let str = Buffer.isBuffer(content) ? content.toString('utf-8') : String(content);
|
|
72
|
+
if (str.charCodeAt(0) === 0xfeff) {
|
|
73
|
+
str = str.slice(1);
|
|
74
|
+
}
|
|
75
|
+
json = JSON.parse(str);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
this.err(`Failed to parse JSON: ${e.message}`, true);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
if (!json) {
|
|
82
|
+
return this.buildResult(filename, filesize, 'asterics');
|
|
83
|
+
}
|
|
84
|
+
await this.add_check('grids', 'grids array', async () => {
|
|
85
|
+
if (!Array.isArray(json.grids) || json.grids.length === 0) {
|
|
86
|
+
this.err('missing grids array in file', true);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const grids = Array.isArray(json.grids) ? json.grids.slice(0, 5) : [];
|
|
90
|
+
grids.forEach((grid, idx) => {
|
|
91
|
+
const prefix = `grid[${idx}]`;
|
|
92
|
+
this.add_check_sync(`${prefix}_id`, `${prefix} id`, () => {
|
|
93
|
+
if (!grid?.id || typeof grid.id !== 'string') {
|
|
94
|
+
this.err('grid is missing an id');
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
this.add_check_sync(`${prefix}_rows`, `${prefix} rowCount`, () => {
|
|
98
|
+
if (typeof grid?.rowCount !== 'number' || grid.rowCount <= 0) {
|
|
99
|
+
this.err('rowCount must be a positive number');
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
this.add_check_sync(`${prefix}_elements`, `${prefix} elements`, () => {
|
|
103
|
+
if (!Array.isArray(grid?.gridElements)) {
|
|
104
|
+
this.err('gridElements must be an array');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (grid.gridElements.length === 0) {
|
|
108
|
+
this.warn('grid has no elements');
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
return this.buildResult(filename, filesize, 'asterics');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.AstericsGridValidator = AstericsGridValidator;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseValidator } from './baseValidator';
|
|
2
|
+
import { ValidationResult } from './validationTypes';
|
|
3
|
+
/**
|
|
4
|
+
* Validator for Graphviz DOT files
|
|
5
|
+
*/
|
|
6
|
+
export declare class DotValidator extends BaseValidator {
|
|
7
|
+
static validateFile(filePath: string): Promise<ValidationResult>;
|
|
8
|
+
static identifyFormat(content: any, filename: string): Promise<boolean>;
|
|
9
|
+
validate(content: Buffer | Uint8Array, filename: string, filesize: number): Promise<ValidationResult>;
|
|
10
|
+
}
|