@fin.cx/einvoice 5.1.4 → 5.2.1
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/.smartconfig.json +59 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/einvoice.d.ts +14 -5
- package/dist_ts/einvoice.js +173 -66
- package/dist_ts/errors.js +14 -4
- package/dist_ts/formats/base/base.decoder.d.ts +4 -0
- package/dist_ts/formats/base/base.decoder.js +9 -1
- package/dist_ts/formats/base/base.validator.js +3 -2
- package/dist_ts/formats/cii/cii.decoder.d.ts +1 -0
- package/dist_ts/formats/cii/cii.decoder.js +20 -10
- package/dist_ts/formats/cii/cii.encoder.js +2 -5
- package/dist_ts/formats/cii/cii.validator.d.ts +1 -1
- package/dist_ts/formats/cii/cii.validator.js +19 -12
- package/dist_ts/formats/cii/facturx/facturx.encoder.js +25 -16
- package/dist_ts/formats/converters/xml-to-einvoice.converter.js +3 -3
- package/dist_ts/formats/factories/decoder.factory.d.ts +3 -1
- package/dist_ts/formats/factories/decoder.factory.js +4 -3
- package/dist_ts/formats/factories/validator.factory.d.ts +4 -1
- package/dist_ts/formats/factories/validator.factory.js +11 -8
- package/dist_ts/formats/pdf/extractors/associated.extractor.js +3 -2
- package/dist_ts/formats/pdf/extractors/base.extractor.d.ts +1 -1
- package/dist_ts/formats/pdf/extractors/base.extractor.js +50 -52
- package/dist_ts/formats/pdf/extractors/standard.extractor.js +3 -2
- package/dist_ts/formats/pdf/extractors/text.extractor.js +19 -22
- package/dist_ts/formats/pdf/pdf.embedder.js +3 -2
- package/dist_ts/formats/pdf/pdf.extractor.d.ts +2 -1
- package/dist_ts/formats/pdf/pdf.extractor.js +20 -10
- package/dist_ts/formats/semantic/semantic.adapter.js +42 -12
- package/dist_ts/formats/semantic/semantic.validator.js +3 -1
- package/dist_ts/formats/ubl/en16931.ubl.validator.js +5 -3
- package/dist_ts/formats/ubl/generic/ubl.encoder.js +11 -7
- package/dist_ts/formats/ubl/ubl.decoder.d.ts +1 -0
- package/dist_ts/formats/ubl/ubl.decoder.js +18 -8
- package/dist_ts/formats/ubl/ubl.validator.d.ts +1 -1
- package/dist_ts/formats/ubl/ubl.validator.js +17 -10
- package/dist_ts/formats/ubl/xrechnung/xrechnung.decoder.js +37 -29
- package/dist_ts/formats/utils/currency.calculator.decimal.js +4 -1
- package/dist_ts/formats/utils/currency.utils.js +4 -1
- package/dist_ts/formats/utils/decimal.js +9 -2
- package/dist_ts/formats/utils/format.detector.js +47 -17
- package/dist_ts/formats/validation/codelist.validator.js +2 -4
- package/dist_ts/formats/validation/conformance.harness.js +13 -6
- package/dist_ts/formats/validation/en16931.business-rules.validator.js +4 -4
- package/dist_ts/formats/validation/en16931.validator.d.ts +6 -0
- package/dist_ts/formats/validation/en16931.validator.js +18 -12
- package/dist_ts/formats/validation/facturx.validator.js +147 -148
- package/dist_ts/formats/validation/integrated.validator.js +12 -4
- package/dist_ts/formats/validation/peppol.validator.js +2 -1
- package/dist_ts/formats/validation/schematron.downloader.d.ts +0 -1
- package/dist_ts/formats/validation/schematron.downloader.js +12 -7
- package/dist_ts/formats/validation/schematron.integration.js +16 -6
- package/dist_ts/formats/validation/schematron.validator.d.ts +9 -11
- package/dist_ts/formats/validation/schematron.validator.js +70 -72
- package/dist_ts/formats/validation/schematron.worker.d.ts +7 -6
- package/dist_ts/formats/validation/schematron.worker.js +48 -42
- package/dist_ts/formats/validation/vat-categories.validator.js +3 -4
- package/dist_ts/formats/validation/xrechnung.validator.js +7 -7
- package/dist_ts/index.js +3 -2
- package/dist_ts/interfaces/common.js +1 -2
- package/dist_ts/plugins.d.ts +3 -6
- package/dist_ts/plugins.js +3 -12
- package/dist_ts/vendor/pako.d.ts +5 -0
- package/dist_ts/vendor/pako.js +3 -0
- package/dist_ts/vendor/pdflib.d.ts +2 -0
- package/dist_ts/vendor/pdflib.js +9 -0
- package/dist_ts/vendor/saxonjs.d.ts +23 -0
- package/dist_ts/vendor/saxonjs.js +9 -0
- package/dist_ts/vendor/xmldom.d.ts +20 -0
- package/dist_ts/vendor/xmldom.js +5 -0
- package/dist_ts_install/download-schematron.d.ts +0 -3
- package/dist_ts_install/download-schematron.js +20 -11
- package/dist_ts_install/download-test-samples.d.ts +0 -3
- package/dist_ts_install/download-test-samples.js +4 -2
- package/dist_ts_install/download-xrechnung-rules.js +2 -1
- package/dist_ts_install/index.d.ts +0 -5
- package/dist_ts_install/index.js +26 -12
- package/license +21 -0
- package/package.json +21 -15
- package/readme.hints.md +24 -8
- package/readme.md +195 -328
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/einvoice.ts +156 -33
- package/ts/formats/base/base.decoder.ts +7 -0
- package/ts/formats/cii/cii.decoder.ts +17 -8
- package/ts/formats/cii/cii.validator.ts +18 -13
- package/ts/formats/cii/facturx/facturx.encoder.ts +25 -15
- package/ts/formats/converters/xml-to-einvoice.converter.ts +4 -5
- package/ts/formats/factories/decoder.factory.ts +7 -2
- package/ts/formats/factories/validator.factory.ts +14 -8
- package/ts/formats/pdf/extractors/associated.extractor.ts +2 -1
- package/ts/formats/pdf/extractors/base.extractor.ts +3 -2
- package/ts/formats/pdf/extractors/standard.extractor.ts +2 -1
- package/ts/formats/pdf/pdf.embedder.ts +6 -3
- package/ts/formats/pdf/pdf.extractor.ts +24 -16
- package/ts/formats/semantic/semantic.adapter.ts +49 -15
- package/ts/formats/ubl/en16931.ubl.validator.ts +5 -3
- package/ts/formats/ubl/generic/ubl.encoder.ts +12 -7
- package/ts/formats/ubl/ubl.decoder.ts +18 -9
- package/ts/formats/ubl/ubl.validator.ts +17 -12
- package/ts/formats/ubl/xrechnung/xrechnung.decoder.ts +41 -28
- package/ts/formats/utils/format.detector.ts +57 -19
- package/ts/formats/validation/conformance.harness.ts +6 -4
- package/ts/formats/validation/en16931.validator.ts +20 -10
- package/ts/formats/validation/integrated.validator.ts +5 -3
- package/ts/formats/validation/schematron.downloader.ts +11 -8
- package/ts/formats/validation/schematron.integration.ts +11 -5
- package/ts/formats/validation/schematron.validator.ts +79 -79
- package/ts/formats/validation/schematron.worker.ts +59 -41
- package/ts/index.ts +2 -1
- package/ts/interfaces/common.ts +1 -3
- package/ts/plugins.ts +2 -29
- package/ts/readme.md +76 -0
- package/ts/vendor/modules.d.ts +32 -0
- package/ts/vendor/pako.ts +8 -0
- package/ts/vendor/pdflib.ts +12 -0
- package/ts/vendor/saxonjs.ts +36 -0
- package/ts/vendor/xmldom.ts +27 -0
- package/npmextra.json +0 -35
|
@@ -27,11 +27,11 @@ export class EN16931Validator {
|
|
|
27
27
|
];
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* Collects mandatory EN16931 field errors without throwing.
|
|
31
31
|
* @param invoice The invoice object to validate
|
|
32
|
-
* @
|
|
32
|
+
* @returns List of validation error messages
|
|
33
33
|
*/
|
|
34
|
-
public static
|
|
34
|
+
public static collectMandatoryFieldErrors(invoice: any): string[] {
|
|
35
35
|
const errors: string[] = [];
|
|
36
36
|
|
|
37
37
|
// BR-01: Invoice number is mandatory
|
|
@@ -49,7 +49,7 @@ export class EN16931Validator {
|
|
|
49
49
|
errors.push('BR-06: Seller name is mandatory');
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// BR-07: Buyer name is mandatory
|
|
52
|
+
// BR-07: Buyer name is mandatory
|
|
53
53
|
if (!invoice.to?.name) {
|
|
54
54
|
errors.push('BR-07: Buyer name is mandatory');
|
|
55
55
|
}
|
|
@@ -67,11 +67,10 @@ export class EN16931Validator {
|
|
|
67
67
|
// BR-05: Invoice currency code is mandatory
|
|
68
68
|
if (!invoice.currency) {
|
|
69
69
|
errors.push('BR-05: Invoice currency code is mandatory');
|
|
70
|
-
} else {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
70
|
+
} else if (!this.VALID_CURRENCIES.includes(invoice.currency.toUpperCase())) {
|
|
71
|
+
errors.push(
|
|
72
|
+
`BR-05: Invalid currency code: ${invoice.currency}. Must be a valid ISO 4217 currency code`
|
|
73
|
+
);
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
// BR-08: Seller postal address is mandatory
|
|
@@ -84,6 +83,17 @@ export class EN16931Validator {
|
|
|
84
83
|
errors.push('BR-10: Buyer postal address (city, postal code, country) is mandatory');
|
|
85
84
|
}
|
|
86
85
|
|
|
86
|
+
return errors;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Validates that an invoice object contains all mandatory EN16931 fields
|
|
91
|
+
* @param invoice The invoice object to validate
|
|
92
|
+
* @throws Error if mandatory fields are missing
|
|
93
|
+
*/
|
|
94
|
+
public static validateMandatoryFields(invoice: any): void {
|
|
95
|
+
const errors = this.collectMandatoryFieldErrors(invoice);
|
|
96
|
+
|
|
87
97
|
if (errors.length > 0) {
|
|
88
98
|
throw new Error(`EN16931 validation failed:\n${errors.join('\n')}`);
|
|
89
99
|
}
|
|
@@ -132,4 +142,4 @@ export class EN16931Validator {
|
|
|
132
142
|
throw new Error(`EN16931 XML validation failed:\n${errors.join('\n')}`);
|
|
133
143
|
}
|
|
134
144
|
}
|
|
135
|
-
}
|
|
145
|
+
}
|
|
@@ -60,7 +60,8 @@ export class MainValidator {
|
|
|
60
60
|
this.schematronEnabled = true;
|
|
61
61
|
console.log(`Schematron validation enabled for ${standard} ${format}`);
|
|
62
62
|
} catch (error) {
|
|
63
|
-
|
|
63
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
64
|
+
console.warn(`Failed to initialize Schematron: ${errorMessage}`);
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -121,7 +122,8 @@ export class MainValidator {
|
|
|
121
122
|
);
|
|
122
123
|
results.push(...schematronResults);
|
|
123
124
|
} catch (error) {
|
|
124
|
-
|
|
125
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
126
|
+
console.warn(`Schematron validation error: ${errorMessage}`);
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
|
|
@@ -402,4 +404,4 @@ export async function createValidator(
|
|
|
402
404
|
}
|
|
403
405
|
|
|
404
406
|
// Export for convenience
|
|
405
|
-
export type { ValidationReport, ValidationResult, ValidationOptions } from './validation.types.js';
|
|
407
|
+
export type { ValidationReport, ValidationResult, ValidationOptions } from './validation.types.js';
|
|
@@ -79,7 +79,6 @@ export const SCHEMATRON_SOURCES: Record<string, SchematronSource[]> = {
|
|
|
79
79
|
*/
|
|
80
80
|
export class SchematronDownloader {
|
|
81
81
|
private cacheDir: string;
|
|
82
|
-
private smartfile: any;
|
|
83
82
|
|
|
84
83
|
constructor(cacheDir: string = 'assets_downloaded/schematron') {
|
|
85
84
|
this.cacheDir = cacheDir;
|
|
@@ -90,7 +89,6 @@ export class SchematronDownloader {
|
|
|
90
89
|
*/
|
|
91
90
|
public async initialize(): Promise<void> {
|
|
92
91
|
// Ensure cache directory exists
|
|
93
|
-
this.smartfile = await import('@push.rocks/smartfile');
|
|
94
92
|
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
95
93
|
}
|
|
96
94
|
|
|
@@ -139,7 +137,8 @@ export class SchematronDownloader {
|
|
|
139
137
|
console.log(`Successfully downloaded: ${fileName}`);
|
|
140
138
|
return filePath;
|
|
141
139
|
} catch (error) {
|
|
142
|
-
|
|
140
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
141
|
+
throw new Error(`Failed to download ${source.name}: ${errorMessage}`);
|
|
143
142
|
}
|
|
144
143
|
}
|
|
145
144
|
|
|
@@ -160,7 +159,8 @@ export class SchematronDownloader {
|
|
|
160
159
|
const path = await this.download(source);
|
|
161
160
|
paths.push(path);
|
|
162
161
|
} catch (error) {
|
|
163
|
-
|
|
162
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
163
|
+
console.warn(`Failed to download ${source.name}: ${errorMessage}`);
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -209,7 +209,8 @@ export class SchematronDownloader {
|
|
|
209
209
|
}
|
|
210
210
|
}
|
|
211
211
|
} catch (error) {
|
|
212
|
-
|
|
212
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
213
|
+
console.warn(`Failed to list cached files: ${errorMessage}`);
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
return files;
|
|
@@ -230,7 +231,8 @@ export class SchematronDownloader {
|
|
|
230
231
|
|
|
231
232
|
console.log('Schematron cache cleared');
|
|
232
233
|
} catch (error) {
|
|
233
|
-
|
|
234
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
235
|
+
console.warn(`Failed to clear cache: ${errorMessage}`);
|
|
234
236
|
}
|
|
235
237
|
}
|
|
236
238
|
|
|
@@ -296,9 +298,10 @@ export async function downloadISOSkeletons(targetDir: string = 'assets_downloade
|
|
|
296
298
|
|
|
297
299
|
console.log(`Downloaded: ${name}`);
|
|
298
300
|
} catch (error) {
|
|
299
|
-
|
|
301
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
302
|
+
console.warn(`Failed to download ${name}: ${errorMessage}`);
|
|
300
303
|
}
|
|
301
304
|
}
|
|
302
305
|
|
|
303
306
|
console.log('ISO Schematron skeleton download complete');
|
|
304
|
-
}
|
|
307
|
+
}
|
|
@@ -61,10 +61,13 @@ export class IntegratedValidator {
|
|
|
61
61
|
try {
|
|
62
62
|
await fs.access(schematronPath);
|
|
63
63
|
} catch {
|
|
64
|
-
throw new Error(`Schematron file not found: ${schematronPath}. Run '
|
|
64
|
+
throw new Error(`Schematron file not found: ${schematronPath}. Run 'pnpm run download-schematron' first.`);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
await this.schematronValidator.loadSchematron(schematronPath, true);
|
|
68
|
+
if (!this.schematronValidator.canValidate()) {
|
|
69
|
+
throw new Error(`Schematron file is not a precompiled Saxon-JS SEF resource: ${schematronPath}`);
|
|
70
|
+
}
|
|
68
71
|
this.schematronLoaded = true;
|
|
69
72
|
}
|
|
70
73
|
|
|
@@ -146,7 +149,8 @@ export class IntegratedValidator {
|
|
|
146
149
|
});
|
|
147
150
|
results.push(...schematronResults);
|
|
148
151
|
} catch (error) {
|
|
149
|
-
|
|
152
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
153
|
+
console.warn(`Schematron validation failed: ${errorMessage}`);
|
|
150
154
|
}
|
|
151
155
|
}
|
|
152
156
|
|
|
@@ -224,7 +228,8 @@ export class IntegratedValidator {
|
|
|
224
228
|
try {
|
|
225
229
|
await this.loadSchematron('EN16931', format);
|
|
226
230
|
} catch (error) {
|
|
227
|
-
|
|
231
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
232
|
+
console.warn(`Could not load Schematron: ${errorMessage}`);
|
|
228
233
|
}
|
|
229
234
|
}
|
|
230
235
|
|
|
@@ -278,8 +283,9 @@ export async function createStandardValidator(
|
|
|
278
283
|
try {
|
|
279
284
|
await validator.loadSchematron(standard, format);
|
|
280
285
|
} catch (error) {
|
|
281
|
-
|
|
286
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
287
|
+
console.warn(`Schematron not available for ${standard} ${format}: ${errorMessage}`);
|
|
282
288
|
}
|
|
283
289
|
|
|
284
290
|
return validator;
|
|
285
|
-
}
|
|
291
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
|
-
import * as SaxonJS from 'saxon-js';
|
|
3
2
|
import type { ValidationResult } from './validation.types.js';
|
|
3
|
+
import { loadSaxonJS, type ISaxonJSTransformOptions, type ISaxonJSTransformResult } from '../../vendor/saxonjs.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Schematron validation options
|
|
@@ -12,14 +12,17 @@ export interface SchematronOptions {
|
|
|
12
12
|
maxErrors?: number; // Maximum errors before stopping
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
type TSchematronSourceKind = 'none' | 'schematron' | 'sef';
|
|
16
|
+
|
|
15
17
|
/**
|
|
16
18
|
* Schematron validation engine using Saxon-JS
|
|
17
19
|
* Provides official standards validation through Schematron rules
|
|
18
20
|
*/
|
|
19
21
|
export class SchematronValidator {
|
|
20
|
-
private compiledStylesheet: any;
|
|
21
22
|
private schematronRules: string;
|
|
22
|
-
private
|
|
23
|
+
private sourceKind: TSchematronSourceKind = 'none';
|
|
24
|
+
private stylesheetFileName?: string;
|
|
25
|
+
private stylesheetInternal?: unknown;
|
|
23
26
|
|
|
24
27
|
constructor(schematronRules?: string) {
|
|
25
28
|
this.schematronRules = schematronRules || '';
|
|
@@ -29,43 +32,25 @@ export class SchematronValidator {
|
|
|
29
32
|
* Load Schematron rules from file or string
|
|
30
33
|
*/
|
|
31
34
|
public async loadSchematron(source: string, isFilePath: boolean = true): Promise<void> {
|
|
35
|
+
let sourceContent: string;
|
|
36
|
+
|
|
32
37
|
if (isFilePath) {
|
|
33
|
-
|
|
34
|
-
const smartfile = await import('@push.rocks/smartfile');
|
|
35
|
-
this.schematronRules = await smartfile.SmartFile.fromFilePath(source).then(f => f.contentBuffer.toString());
|
|
38
|
+
sourceContent = await plugins.fs.readFile(source, 'utf-8');
|
|
36
39
|
} else {
|
|
37
|
-
|
|
38
|
-
this.schematronRules = source;
|
|
40
|
+
sourceContent = source;
|
|
39
41
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// For now, we'll use a simplified approach with direct XSLT generation
|
|
53
|
-
// In production, we would use the official ISO Schematron skeleton XSLTs
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
// Convert Schematron to XSLT
|
|
57
|
-
// This is a simplified version - in production we'd use the full ISO skeleton
|
|
58
|
-
const xslt = this.generateXSLTFromSchematron(this.schematronRules);
|
|
59
|
-
|
|
60
|
-
// Compile the XSLT with Saxon-JS
|
|
61
|
-
this.compiledStylesheet = await SaxonJS.compile({
|
|
62
|
-
stylesheetText: xslt,
|
|
63
|
-
warnings: 'silent'
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
this.isCompiled = true;
|
|
67
|
-
} catch (error) {
|
|
68
|
-
throw new Error(`Failed to compile Schematron: ${error.message}`);
|
|
42
|
+
|
|
43
|
+
this.schematronRules = sourceContent;
|
|
44
|
+
this.sourceKind = this.detectSourceKind(source, sourceContent, isFilePath);
|
|
45
|
+
this.stylesheetFileName = undefined;
|
|
46
|
+
this.stylesheetInternal = undefined;
|
|
47
|
+
|
|
48
|
+
if (this.sourceKind === 'sef') {
|
|
49
|
+
if (isFilePath) {
|
|
50
|
+
this.stylesheetFileName = source;
|
|
51
|
+
} else {
|
|
52
|
+
this.stylesheetInternal = JSON.parse(sourceContent);
|
|
53
|
+
}
|
|
69
54
|
}
|
|
70
55
|
}
|
|
71
56
|
|
|
@@ -80,19 +65,28 @@ export class SchematronValidator {
|
|
|
80
65
|
throw new Error('No Schematron rules loaded');
|
|
81
66
|
}
|
|
82
67
|
|
|
83
|
-
// Ensure Schematron is compiled
|
|
84
|
-
await this.compileSchematron();
|
|
85
|
-
|
|
86
68
|
const results: ValidationResult[] = [];
|
|
69
|
+
|
|
70
|
+
if (!this.canValidate()) {
|
|
71
|
+
return [this.createUnavailableResult()];
|
|
72
|
+
}
|
|
87
73
|
|
|
88
74
|
try {
|
|
89
|
-
|
|
90
|
-
const transformResult = await SaxonJS.transform({
|
|
91
|
-
stylesheetInternal: this.compiledStylesheet,
|
|
75
|
+
const transformOptions: ISaxonJSTransformOptions = {
|
|
92
76
|
sourceText: xmlContent,
|
|
93
77
|
destination: 'serialized',
|
|
94
78
|
stylesheetParams: options.parameters || {}
|
|
95
|
-
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (this.stylesheetFileName) {
|
|
82
|
+
transformOptions.stylesheetFileName = this.stylesheetFileName;
|
|
83
|
+
} else {
|
|
84
|
+
transformOptions.stylesheetInternal = this.stylesheetInternal;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Transform the XML with a precompiled Saxon-JS SEF stylesheet.
|
|
88
|
+
const SaxonJS = await loadSaxonJS();
|
|
89
|
+
const transformResult = await SaxonJS.transform(transformOptions, 'async') as ISaxonJSTransformResult;
|
|
96
90
|
|
|
97
91
|
// Parse the SVRL (Schematron Validation Report Language) output
|
|
98
92
|
results.push(...this.parseSVRL(transformResult.principalResult));
|
|
@@ -108,11 +102,12 @@ export class SchematronValidator {
|
|
|
108
102
|
|
|
109
103
|
return results;
|
|
110
104
|
} catch (error) {
|
|
105
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
111
106
|
results.push({
|
|
112
107
|
ruleId: 'SCHEMATRON-ERROR',
|
|
113
108
|
source: 'SCHEMATRON',
|
|
114
109
|
severity: 'error',
|
|
115
|
-
message: `Schematron validation failed: ${
|
|
110
|
+
message: `Schematron validation failed: ${errorMessage}`,
|
|
116
111
|
btReference: undefined,
|
|
117
112
|
bgReference: undefined
|
|
118
113
|
});
|
|
@@ -190,43 +185,46 @@ export class SchematronValidator {
|
|
|
190
185
|
};
|
|
191
186
|
}
|
|
192
187
|
|
|
193
|
-
/**
|
|
194
|
-
* Generate simplified XSLT from Schematron
|
|
195
|
-
* This is a placeholder - in production, use ISO Schematron skeleton
|
|
196
|
-
*/
|
|
197
|
-
private generateXSLTFromSchematron(schematron: string): string {
|
|
198
|
-
// This is a simplified transformation
|
|
199
|
-
// In production, we would use the official ISO Schematron skeleton XSLTs
|
|
200
|
-
// (iso_schematron_skeleton.xsl, iso_svrl_for_xslt2.xsl, etc.)
|
|
201
|
-
|
|
202
|
-
// For now, return a basic XSLT that creates SVRL output
|
|
203
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
204
|
-
<xsl:stylesheet version="3.0"
|
|
205
|
-
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
|
206
|
-
xmlns:svrl="http://purl.oclc.org/dsdl/svrl">
|
|
207
|
-
|
|
208
|
-
<xsl:output method="xml" indent="yes"/>
|
|
209
|
-
|
|
210
|
-
<xsl:template match="/">
|
|
211
|
-
<svrl:schematron-output>
|
|
212
|
-
<!-- This is a placeholder transformation -->
|
|
213
|
-
<!-- Real implementation would process Schematron patterns and rules -->
|
|
214
|
-
<svrl:active-pattern>
|
|
215
|
-
<xsl:attribute name="document">
|
|
216
|
-
<xsl:value-of select="base-uri(/)"/>
|
|
217
|
-
</xsl:attribute>
|
|
218
|
-
</svrl:active-pattern>
|
|
219
|
-
</svrl:schematron-output>
|
|
220
|
-
</xsl:template>
|
|
221
|
-
</xsl:stylesheet>`;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
188
|
/**
|
|
225
189
|
* Check if validator has rules loaded
|
|
226
190
|
*/
|
|
227
191
|
public hasRules(): boolean {
|
|
228
192
|
return !!this.schematronRules;
|
|
229
193
|
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Check if loaded rules can be executed by Saxon-JS.
|
|
197
|
+
*/
|
|
198
|
+
public canValidate(): boolean {
|
|
199
|
+
return this.sourceKind === 'sef' && Boolean(this.stylesheetFileName || this.stylesheetInternal);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private detectSourceKind(source: string, sourceContent: string, isFilePath: boolean): TSchematronSourceKind {
|
|
203
|
+
const trimmed = sourceContent.trim();
|
|
204
|
+
if (!trimmed) {
|
|
205
|
+
return 'none';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (isFilePath && /\.sef\.json$/i.test(source)) {
|
|
209
|
+
return 'sef';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (trimmed.startsWith('{')) {
|
|
213
|
+
return 'sef';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return 'schematron';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private createUnavailableResult(): ValidationResult {
|
|
220
|
+
return {
|
|
221
|
+
ruleId: 'SCHEMATRON-UNCOMPILED',
|
|
222
|
+
source: 'SCHEMATRON',
|
|
223
|
+
severity: 'error',
|
|
224
|
+
message: 'Schematron validation requires a precompiled Saxon-JS SEF JSON stylesheet. Raw .sch files are loaded for inspection only and are not executed.',
|
|
225
|
+
remediation: 'Compile Schematron rules to .sef.json with Saxon tooling and load the compiled SEF resource.'
|
|
226
|
+
};
|
|
227
|
+
}
|
|
230
228
|
|
|
231
229
|
/**
|
|
232
230
|
* Get list of available phases from Schematron
|
|
@@ -323,7 +321,8 @@ export class HybridValidator {
|
|
|
323
321
|
try {
|
|
324
322
|
results.push(...validator.validate(xmlContent));
|
|
325
323
|
} catch (error) {
|
|
326
|
-
|
|
324
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
325
|
+
console.warn(`TS validator failed: ${errorMessage}`);
|
|
327
326
|
}
|
|
328
327
|
}
|
|
329
328
|
|
|
@@ -333,7 +332,8 @@ export class HybridValidator {
|
|
|
333
332
|
const schematronResults = await this.schematronValidator.validate(xmlContent, options);
|
|
334
333
|
results.push(...schematronResults);
|
|
335
334
|
} catch (error) {
|
|
336
|
-
|
|
335
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
336
|
+
console.warn(`Schematron validation failed: ${errorMessage}`);
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
339
|
|
|
@@ -345,4 +345,4 @@ export class HybridValidator {
|
|
|
345
345
|
return true;
|
|
346
346
|
});
|
|
347
347
|
}
|
|
348
|
-
}
|
|
348
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Worker } from 'worker_threads';
|
|
2
|
-
import * as path from 'path';
|
|
3
2
|
import type { ValidationResult } from './validation.types.js';
|
|
4
3
|
import type { SchematronOptions } from './schematron.validator.js';
|
|
5
4
|
|
|
5
|
+
export interface ISchematronWorkerPoolInitializeOptions {
|
|
6
|
+
isFilePath?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
6
9
|
/**
|
|
7
10
|
* Worker pool for Schematron validation
|
|
8
11
|
* Provides non-blocking validation in worker threads
|
|
@@ -17,7 +20,8 @@ export class SchematronWorkerPool {
|
|
|
17
20
|
reject: (error: Error) => void;
|
|
18
21
|
}> = [];
|
|
19
22
|
private maxWorkers: number;
|
|
20
|
-
private
|
|
23
|
+
private stylesheetFileName?: string;
|
|
24
|
+
private stylesheetInternal?: unknown;
|
|
21
25
|
|
|
22
26
|
constructor(maxWorkers: number = 4) {
|
|
23
27
|
this.maxWorkers = maxWorkers;
|
|
@@ -26,48 +30,81 @@ export class SchematronWorkerPool {
|
|
|
26
30
|
/**
|
|
27
31
|
* Initialize worker pool
|
|
28
32
|
*/
|
|
29
|
-
public async initialize(
|
|
30
|
-
|
|
33
|
+
public async initialize(
|
|
34
|
+
stylesheetSource: string,
|
|
35
|
+
options: ISchematronWorkerPoolInitializeOptions = {}
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
this.loadStylesheet(stylesheetSource, options);
|
|
31
38
|
|
|
32
39
|
// Create workers
|
|
33
40
|
for (let i = 0; i < this.maxWorkers; i++) {
|
|
34
41
|
await this.createWorker();
|
|
35
42
|
}
|
|
36
43
|
}
|
|
44
|
+
|
|
45
|
+
private loadStylesheet(
|
|
46
|
+
stylesheetSource: string,
|
|
47
|
+
options: ISchematronWorkerPoolInitializeOptions
|
|
48
|
+
): void {
|
|
49
|
+
const source = stylesheetSource.trim();
|
|
50
|
+
const isFilePath = options.isFilePath ?? /\.sef\.json$/i.test(stylesheetSource);
|
|
51
|
+
|
|
52
|
+
this.stylesheetFileName = undefined;
|
|
53
|
+
this.stylesheetInternal = undefined;
|
|
54
|
+
|
|
55
|
+
if (isFilePath) {
|
|
56
|
+
if (!/\.sef\.json$/i.test(stylesheetSource)) {
|
|
57
|
+
throw new Error('Schematron worker validation requires a precompiled Saxon-JS SEF JSON stylesheet file.');
|
|
58
|
+
}
|
|
59
|
+
this.stylesheetFileName = stylesheetSource;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!source.startsWith('{')) {
|
|
64
|
+
throw new Error('Schematron worker validation requires a precompiled Saxon-JS SEF JSON stylesheet. Raw .sch files are not executed.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.stylesheetInternal = JSON.parse(source);
|
|
68
|
+
}
|
|
37
69
|
|
|
38
70
|
/**
|
|
39
71
|
* Create a new worker
|
|
40
72
|
*/
|
|
41
73
|
private async createWorker(): Promise<void> {
|
|
42
|
-
const workerPath = path.join(import.meta.url, 'schematron.worker.impl.js');
|
|
43
|
-
|
|
44
74
|
const worker = new Worker(`
|
|
45
75
|
const { parentPort } = require('worker_threads');
|
|
46
76
|
const SaxonJS = require('saxon-js');
|
|
47
77
|
|
|
48
|
-
let
|
|
78
|
+
let stylesheetFileName = null;
|
|
79
|
+
let stylesheetInternal = null;
|
|
49
80
|
|
|
50
81
|
parentPort.on('message', async (msg) => {
|
|
51
82
|
try {
|
|
52
83
|
if (msg.type === 'init') {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
84
|
+
stylesheetFileName = msg.stylesheetFileName || null;
|
|
85
|
+
stylesheetInternal = msg.stylesheetInternal || null;
|
|
86
|
+
if (!stylesheetFileName && !stylesheetInternal) {
|
|
87
|
+
throw new Error('Worker not initialized with a SEF stylesheet');
|
|
88
|
+
}
|
|
58
89
|
parentPort.postMessage({ type: 'ready' });
|
|
59
90
|
} else if (msg.type === 'validate') {
|
|
60
|
-
if (!
|
|
91
|
+
if (!stylesheetFileName && !stylesheetInternal) {
|
|
61
92
|
throw new Error('Worker not initialized');
|
|
62
93
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const result = await SaxonJS.transform({
|
|
66
|
-
stylesheetInternal: compiledStylesheet,
|
|
94
|
+
|
|
95
|
+
const transformOptions = {
|
|
67
96
|
sourceText: msg.xmlContent,
|
|
68
97
|
destination: 'serialized',
|
|
69
98
|
stylesheetParams: msg.options.parameters || {}
|
|
70
|
-
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (stylesheetFileName) {
|
|
102
|
+
transformOptions.stylesheetFileName = stylesheetFileName;
|
|
103
|
+
} else {
|
|
104
|
+
transformOptions.stylesheetInternal = stylesheetInternal;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const result = await SaxonJS.transform(transformOptions, 'async');
|
|
71
108
|
|
|
72
109
|
parentPort.postMessage({
|
|
73
110
|
type: 'result',
|
|
@@ -83,7 +120,7 @@ export class SchematronWorkerPool {
|
|
|
83
120
|
});
|
|
84
121
|
`, { eval: true });
|
|
85
122
|
|
|
86
|
-
// Initialize worker with
|
|
123
|
+
// Initialize worker with a precompiled SEF stylesheet.
|
|
87
124
|
await new Promise<void>((resolve, reject) => {
|
|
88
125
|
worker.once('message', (msg) => {
|
|
89
126
|
if (msg.type === 'ready') {
|
|
@@ -96,7 +133,8 @@ export class SchematronWorkerPool {
|
|
|
96
133
|
// Send initialization message
|
|
97
134
|
worker.postMessage({
|
|
98
135
|
type: 'init',
|
|
99
|
-
|
|
136
|
+
stylesheetFileName: this.stylesheetFileName,
|
|
137
|
+
stylesheetInternal: this.stylesheetInternal
|
|
100
138
|
});
|
|
101
139
|
});
|
|
102
140
|
|
|
@@ -174,26 +212,6 @@ export class SchematronWorkerPool {
|
|
|
174
212
|
return results;
|
|
175
213
|
}
|
|
176
214
|
|
|
177
|
-
/**
|
|
178
|
-
* Generate XSLT from Schematron (simplified)
|
|
179
|
-
*/
|
|
180
|
-
private generateXSLTFromSchematron(schematron: string): string {
|
|
181
|
-
// Simplified - would use ISO Schematron skeleton in production
|
|
182
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
183
|
-
<xsl:stylesheet version="3.0"
|
|
184
|
-
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
|
185
|
-
xmlns:svrl="http://purl.oclc.org/dsdl/svrl">
|
|
186
|
-
|
|
187
|
-
<xsl:output method="xml" indent="yes"/>
|
|
188
|
-
|
|
189
|
-
<xsl:template match="/">
|
|
190
|
-
<svrl:schematron-output>
|
|
191
|
-
<svrl:active-pattern document="{base-uri(/)}"/>
|
|
192
|
-
</svrl:schematron-output>
|
|
193
|
-
</xsl:template>
|
|
194
|
-
</xsl:stylesheet>`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
215
|
/**
|
|
198
216
|
* Terminate all workers
|
|
199
217
|
*/
|
|
@@ -218,4 +236,4 @@ export class SchematronWorkerPool {
|
|
|
218
236
|
queuedTasks: this.taskQueue.length
|
|
219
237
|
};
|
|
220
238
|
}
|
|
221
|
-
}
|
|
239
|
+
}
|
package/ts/index.ts
CHANGED
|
@@ -144,11 +144,12 @@ export function validateXml(
|
|
|
144
144
|
const validator = ValidatorFactory.createValidator(xml);
|
|
145
145
|
return validator.validate(level);
|
|
146
146
|
} catch (error) {
|
|
147
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
147
148
|
return {
|
|
148
149
|
valid: false,
|
|
149
150
|
errors: [{
|
|
150
151
|
code: 'VAL-ERROR',
|
|
151
|
-
message: `Validation error: ${
|
|
152
|
+
message: `Validation error: ${errorMessage}`
|
|
152
153
|
}],
|
|
153
154
|
level
|
|
154
155
|
};
|
package/ts/interfaces/common.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { business, finance } from '../plugins.js';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Supported electronic invoice formats
|
|
5
3
|
*/
|
|
@@ -88,4 +86,4 @@ export type { TCreditNote } from '@tsclass/tsclass/dist_ts/finance/index.js';
|
|
|
88
86
|
export type { TDebitNote } from '@tsclass/tsclass/dist_ts/finance/index.js';
|
|
89
87
|
export type { TContact } from '@tsclass/tsclass/dist_ts/business/index.js';
|
|
90
88
|
export type { TLetterEnvelope } from '@tsclass/tsclass/dist_ts/business/index.js';
|
|
91
|
-
export type { TDocumentEnvelope } from '@tsclass/tsclass/dist_ts/business/index.js';
|
|
89
|
+
export type { TDocumentEnvelope } from '@tsclass/tsclass/dist_ts/business/index.js';
|