@fin.cx/einvoice 5.0.3 → 5.1.3
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_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/einvoice.d.ts +4 -8
- package/dist_ts/einvoice.js +46 -7
- package/dist_ts/formats/converters/xml-to-einvoice.converter.d.ts +21 -0
- package/dist_ts/formats/converters/xml-to-einvoice.converter.js +127 -0
- package/dist_ts/formats/semantic/bt-bg.model.d.ts +445 -0
- package/dist_ts/formats/semantic/bt-bg.model.js +12 -0
- package/dist_ts/formats/semantic/semantic.adapter.d.ts +87 -0
- package/dist_ts/formats/semantic/semantic.adapter.js +538 -0
- package/dist_ts/formats/semantic/semantic.validator.d.ts +48 -0
- package/dist_ts/formats/semantic/semantic.validator.js +596 -0
- package/dist_ts/formats/utils/currency.calculator.decimal.d.ts +98 -0
- package/dist_ts/formats/utils/currency.calculator.decimal.js +212 -0
- package/dist_ts/formats/utils/currency.utils.d.ts +99 -0
- package/dist_ts/formats/utils/currency.utils.js +250 -0
- package/dist_ts/formats/utils/decimal.d.ts +151 -0
- package/dist_ts/formats/utils/decimal.js +435 -0
- package/dist_ts/formats/validation/codelist.validator.d.ts +49 -0
- package/dist_ts/formats/validation/codelist.validator.js +188 -0
- package/dist_ts/formats/validation/conformance.harness.d.ts +99 -0
- package/dist_ts/formats/validation/conformance.harness.js +471 -0
- package/dist_ts/formats/validation/en16931.business-rules.validator.d.ts +58 -0
- package/dist_ts/formats/validation/en16931.business-rules.validator.js +459 -0
- package/dist_ts/formats/validation/facturx.validator.d.ts +75 -0
- package/dist_ts/formats/validation/facturx.validator.js +522 -0
- package/dist_ts/formats/validation/integrated.validator.d.ts +82 -0
- package/dist_ts/formats/validation/integrated.validator.js +311 -0
- package/dist_ts/formats/validation/peppol.validator.d.ts +77 -0
- package/dist_ts/formats/validation/peppol.validator.js +529 -0
- package/dist_ts/formats/validation/schematron.downloader.d.ts +71 -0
- package/dist_ts/formats/validation/schematron.downloader.js +254 -0
- package/dist_ts/formats/validation/schematron.integration.d.ts +60 -0
- package/dist_ts/formats/validation/schematron.integration.js +230 -0
- package/dist_ts/formats/validation/schematron.validator.d.ts +79 -0
- package/dist_ts/formats/validation/schematron.validator.js +288 -0
- package/dist_ts/formats/validation/schematron.worker.d.ts +50 -0
- package/dist_ts/formats/validation/schematron.worker.js +185 -0
- package/dist_ts/formats/validation/validation.types.d.ts +163 -0
- package/dist_ts/formats/validation/validation.types.js +181 -0
- package/dist_ts/formats/validation/vat-categories.validator.d.ts +87 -0
- package/dist_ts/formats/validation/vat-categories.validator.js +497 -0
- package/dist_ts/formats/validation/xrechnung.validator.d.ts +92 -0
- package/dist_ts/formats/validation/xrechnung.validator.js +417 -0
- package/dist_ts/interfaces/common.d.ts +1 -0
- package/dist_ts/interfaces/en16931-metadata.d.ts +83 -0
- package/dist_ts/interfaces/en16931-metadata.js +6 -0
- package/dist_ts/plugins.d.ts +3 -1
- package/dist_ts/plugins.js +7 -2
- package/dist_ts_install/download-schematron.d.ts +6 -0
- package/dist_ts_install/download-schematron.js +62 -0
- package/dist_ts_install/download-test-samples.d.ts +19 -0
- package/dist_ts_install/download-test-samples.js +169 -0
- package/dist_ts_install/download-xrechnung-rules.d.ts +7 -0
- package/dist_ts_install/download-xrechnung-rules.js +146 -0
- package/dist_ts_install/index.d.ts +8 -0
- package/dist_ts_install/index.js +246 -0
- package/npmextra.json +1 -1
- package/package.json +12 -6
- package/readme.md +255 -810
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/einvoice.ts +63 -14
- package/ts/formats/converters/xml-to-einvoice.converter.ts +142 -0
- package/ts/formats/semantic/bt-bg.model.ts +524 -0
- package/ts/formats/semantic/semantic.adapter.ts +600 -0
- package/ts/formats/semantic/semantic.validator.ts +654 -0
- package/ts/formats/utils/currency.calculator.decimal.ts +323 -0
- package/ts/formats/utils/currency.utils.ts +299 -0
- package/ts/formats/utils/decimal.ts +509 -0
- package/ts/formats/validation/codelist.validator.ts +317 -0
- package/ts/formats/validation/conformance.harness.ts +591 -0
- package/ts/formats/validation/en16931.business-rules.validator.ts +694 -0
- package/ts/formats/validation/facturx.validator.ts +579 -0
- package/ts/formats/validation/integrated.validator.ts +405 -0
- package/ts/formats/validation/peppol.validator.ts +589 -0
- package/ts/formats/validation/schematron.downloader.ts +304 -0
- package/ts/formats/validation/schematron.integration.ts +285 -0
- package/ts/formats/validation/schematron.validator.ts +348 -0
- package/ts/formats/validation/schematron.worker.ts +221 -0
- package/ts/formats/validation/validation.types.ts +274 -0
- package/ts/formats/validation/vat-categories.validator.ts +845 -0
- package/ts/formats/validation/xrechnung.validator.ts +494 -0
- package/ts/interfaces/common.ts +1 -0
- package/ts/interfaces/en16931-metadata.ts +104 -0
- package/ts/plugins.ts +8 -0
- package/ts/tspublish.json +3 -0
- package/dist_ts/classes.decoder.d.ts +0 -40
- package/dist_ts/classes.decoder.js +0 -320
- package/dist_ts/classes.encoder.d.ts +0 -51
- package/dist_ts/classes.encoder.js +0 -293
- package/dist_ts/classes.xinvoice.d.ts +0 -136
- package/dist_ts/classes.xinvoice.js +0 -352
- package/dist_ts/formats/base.decoder.d.ts +0 -19
- package/dist_ts/formats/base.decoder.js +0 -130
- package/dist_ts/formats/base.validator.d.ts +0 -44
- package/dist_ts/formats/base.validator.js +0 -39
- package/dist_ts/formats/decoder.factory.d.ts +0 -15
- package/dist_ts/formats/decoder.factory.js +0 -46
- package/dist_ts/formats/factorx.decoder.d.ts +0 -18
- package/dist_ts/formats/factorx.decoder.js +0 -178
- package/dist_ts/formats/factorx.encoder.d.ts +0 -51
- package/dist_ts/formats/factorx.encoder.js +0 -293
- package/dist_ts/formats/facturx.decoder.d.ts +0 -18
- package/dist_ts/formats/facturx.decoder.js +0 -210
- package/dist_ts/formats/facturx.encoder.d.ts +0 -51
- package/dist_ts/formats/facturx.encoder.js +0 -293
- package/dist_ts/formats/facturx.validator.d.ts +0 -67
- package/dist_ts/formats/facturx.validator.js +0 -266
- package/dist_ts/formats/pdf/robust-pdf.extractor.d.ts +0 -40
- package/dist_ts/formats/pdf/robust-pdf.extractor.js +0 -324
- package/dist_ts/formats/ubl.validator.d.ts +0 -82
- package/dist_ts/formats/ubl.validator.js +0 -306
- package/dist_ts/formats/validator.factory.d.ts +0 -18
- package/dist_ts/formats/validator.factory.js +0 -78
- package/dist_ts/formats/xinvoice.decoder.d.ts +0 -28
- package/dist_ts/formats/xinvoice.decoder.js +0 -332
- package/dist_ts/formats/xinvoice.encoder.d.ts +0 -19
- package/dist_ts/formats/xinvoice.encoder.js +0 -282
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import * as SaxonJS from 'saxon-js';
|
|
3
|
+
import type { ValidationResult } from './validation.types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Schematron validation options
|
|
7
|
+
*/
|
|
8
|
+
export interface SchematronOptions {
|
|
9
|
+
phase?: string; // Schematron phase to activate
|
|
10
|
+
parameters?: Record<string, any>; // Parameters to pass to Schematron
|
|
11
|
+
includeWarnings?: boolean; // Include warning-level messages
|
|
12
|
+
maxErrors?: number; // Maximum errors before stopping
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Schematron validation engine using Saxon-JS
|
|
17
|
+
* Provides official standards validation through Schematron rules
|
|
18
|
+
*/
|
|
19
|
+
export class SchematronValidator {
|
|
20
|
+
private compiledStylesheet: any;
|
|
21
|
+
private schematronRules: string;
|
|
22
|
+
private isCompiled: boolean = false;
|
|
23
|
+
|
|
24
|
+
constructor(schematronRules?: string) {
|
|
25
|
+
this.schematronRules = schematronRules || '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Load Schematron rules from file or string
|
|
30
|
+
*/
|
|
31
|
+
public async loadSchematron(source: string, isFilePath: boolean = true): Promise<void> {
|
|
32
|
+
if (isFilePath) {
|
|
33
|
+
// Load from file
|
|
34
|
+
const smartfile = await import('@push.rocks/smartfile');
|
|
35
|
+
this.schematronRules = await smartfile.SmartFile.fromFilePath(source).then(f => f.contentBuffer.toString());
|
|
36
|
+
} else {
|
|
37
|
+
// Use provided string
|
|
38
|
+
this.schematronRules = source;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Reset compilation state
|
|
42
|
+
this.isCompiled = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Compile Schematron to XSLT using ISO Schematron skeleton
|
|
47
|
+
*/
|
|
48
|
+
private async compileSchematron(): Promise<void> {
|
|
49
|
+
if (this.isCompiled) return;
|
|
50
|
+
|
|
51
|
+
// The Schematron to XSLT transformation requires the ISO Schematron skeleton
|
|
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}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate an XML document against loaded Schematron rules
|
|
74
|
+
*/
|
|
75
|
+
public async validate(
|
|
76
|
+
xmlContent: string,
|
|
77
|
+
options: SchematronOptions = {}
|
|
78
|
+
): Promise<ValidationResult[]> {
|
|
79
|
+
if (!this.schematronRules) {
|
|
80
|
+
throw new Error('No Schematron rules loaded');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Ensure Schematron is compiled
|
|
84
|
+
await this.compileSchematron();
|
|
85
|
+
|
|
86
|
+
const results: ValidationResult[] = [];
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Transform the XML with the compiled Schematron XSLT
|
|
90
|
+
const transformResult = await SaxonJS.transform({
|
|
91
|
+
stylesheetInternal: this.compiledStylesheet,
|
|
92
|
+
sourceText: xmlContent,
|
|
93
|
+
destination: 'serialized',
|
|
94
|
+
stylesheetParams: options.parameters || {}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Parse the SVRL (Schematron Validation Report Language) output
|
|
98
|
+
results.push(...this.parseSVRL(transformResult.principalResult));
|
|
99
|
+
|
|
100
|
+
// Apply options filters
|
|
101
|
+
if (!options.includeWarnings) {
|
|
102
|
+
return results.filter(r => r.severity !== 'warning');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (options.maxErrors && results.filter(r => r.severity === 'error').length > options.maxErrors) {
|
|
106
|
+
return results.slice(0, options.maxErrors);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return results;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
results.push({
|
|
112
|
+
ruleId: 'SCHEMATRON-ERROR',
|
|
113
|
+
source: 'SCHEMATRON',
|
|
114
|
+
severity: 'error',
|
|
115
|
+
message: `Schematron validation failed: ${error.message}`,
|
|
116
|
+
btReference: undefined,
|
|
117
|
+
bgReference: undefined
|
|
118
|
+
});
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Parse SVRL output to ValidationResult array
|
|
125
|
+
*/
|
|
126
|
+
private parseSVRL(svrlXml: string): ValidationResult[] {
|
|
127
|
+
const results: ValidationResult[] = [];
|
|
128
|
+
|
|
129
|
+
// Parse SVRL XML
|
|
130
|
+
const parser = new plugins.xmldom.DOMParser();
|
|
131
|
+
const doc = parser.parseFromString(svrlXml, 'text/xml');
|
|
132
|
+
|
|
133
|
+
// Get all failed assertions and successful reports
|
|
134
|
+
const failedAsserts = doc.getElementsByTagName('svrl:failed-assert');
|
|
135
|
+
const successfulReports = doc.getElementsByTagName('svrl:successful-report');
|
|
136
|
+
|
|
137
|
+
// Process failed assertions (these are errors)
|
|
138
|
+
for (let i = 0; i < failedAsserts.length; i++) {
|
|
139
|
+
const assert = failedAsserts[i];
|
|
140
|
+
const result = this.extractValidationResult(assert, 'error');
|
|
141
|
+
if (result) results.push(result);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Process successful reports (these can be warnings or info)
|
|
145
|
+
for (let i = 0; i < successfulReports.length; i++) {
|
|
146
|
+
const report = successfulReports[i];
|
|
147
|
+
const result = this.extractValidationResult(report, 'warning');
|
|
148
|
+
if (result) results.push(result);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return results;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Extract ValidationResult from SVRL element
|
|
156
|
+
*/
|
|
157
|
+
private extractValidationResult(
|
|
158
|
+
element: Element,
|
|
159
|
+
defaultSeverity: 'error' | 'warning'
|
|
160
|
+
): ValidationResult | null {
|
|
161
|
+
const text = element.getElementsByTagName('svrl:text')[0]?.textContent || '';
|
|
162
|
+
const location = element.getAttribute('location') || undefined;
|
|
163
|
+
const test = element.getAttribute('test') || '';
|
|
164
|
+
const id = element.getAttribute('id') || element.getAttribute('role') || 'UNKNOWN';
|
|
165
|
+
const flag = element.getAttribute('flag') || defaultSeverity;
|
|
166
|
+
|
|
167
|
+
// Determine severity from flag attribute
|
|
168
|
+
let severity: 'error' | 'warning' | 'info' = defaultSeverity;
|
|
169
|
+
if (flag.toLowerCase().includes('fatal') || flag.toLowerCase().includes('error')) {
|
|
170
|
+
severity = 'error';
|
|
171
|
+
} else if (flag.toLowerCase().includes('warning')) {
|
|
172
|
+
severity = 'warning';
|
|
173
|
+
} else if (flag.toLowerCase().includes('info')) {
|
|
174
|
+
severity = 'info';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Extract BT/BG references if present
|
|
178
|
+
const btMatch = text.match(/\[BT-(\d+)\]/);
|
|
179
|
+
const bgMatch = text.match(/\[BG-(\d+)\]/);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
ruleId: id,
|
|
183
|
+
source: 'EN16931',
|
|
184
|
+
severity,
|
|
185
|
+
message: text,
|
|
186
|
+
syntaxPath: location,
|
|
187
|
+
btReference: btMatch ? `BT-${btMatch[1]}` : undefined,
|
|
188
|
+
bgReference: bgMatch ? `BG-${bgMatch[1]}` : undefined,
|
|
189
|
+
profile: 'EN16931'
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
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
|
+
/**
|
|
225
|
+
* Check if validator has rules loaded
|
|
226
|
+
*/
|
|
227
|
+
public hasRules(): boolean {
|
|
228
|
+
return !!this.schematronRules;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get list of available phases from Schematron
|
|
233
|
+
*/
|
|
234
|
+
public async getPhases(): Promise<string[]> {
|
|
235
|
+
if (!this.schematronRules) return [];
|
|
236
|
+
|
|
237
|
+
const parser = new plugins.xmldom.DOMParser();
|
|
238
|
+
const doc = parser.parseFromString(this.schematronRules, 'text/xml');
|
|
239
|
+
const phases = doc.getElementsByTagName('sch:phase');
|
|
240
|
+
|
|
241
|
+
const phaseNames: string[] = [];
|
|
242
|
+
for (let i = 0; i < phases.length; i++) {
|
|
243
|
+
const id = phases[i].getAttribute('id');
|
|
244
|
+
if (id) phaseNames.push(id);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return phaseNames;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Validate with specific phase activated
|
|
252
|
+
*/
|
|
253
|
+
public async validateWithPhase(
|
|
254
|
+
xmlContent: string,
|
|
255
|
+
phase: string,
|
|
256
|
+
options: SchematronOptions = {}
|
|
257
|
+
): Promise<ValidationResult[]> {
|
|
258
|
+
return this.validate(xmlContent, { ...options, phase });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Factory function to create validator with standard Schematron packs
|
|
264
|
+
*/
|
|
265
|
+
export async function createStandardValidator(
|
|
266
|
+
standard: 'EN16931' | 'XRECHNUNG' | 'PEPPOL' | 'FACTURX'
|
|
267
|
+
): Promise<SchematronValidator> {
|
|
268
|
+
const validator = new SchematronValidator();
|
|
269
|
+
|
|
270
|
+
// Load appropriate Schematron based on standard
|
|
271
|
+
// These paths would point to actual Schematron files in production
|
|
272
|
+
switch (standard) {
|
|
273
|
+
case 'EN16931':
|
|
274
|
+
// Would load from ConnectingEurope/eInvoicing-EN16931
|
|
275
|
+
await validator.loadSchematron('assets_downloaded/schematron/en16931/EN16931-UBL-validation.sch');
|
|
276
|
+
break;
|
|
277
|
+
case 'XRECHNUNG':
|
|
278
|
+
// Would load from itplr-kosit/xrechnung-schematron
|
|
279
|
+
await validator.loadSchematron('assets_downloaded/schematron/xrechnung/XRechnung-UBL-validation.sch');
|
|
280
|
+
break;
|
|
281
|
+
case 'PEPPOL':
|
|
282
|
+
// Would load from OpenPEPPOL/peppol-bis-invoice-3
|
|
283
|
+
await validator.loadSchematron('assets_downloaded/schematron/peppol/PEPPOL-EN16931-UBL.sch');
|
|
284
|
+
break;
|
|
285
|
+
case 'FACTURX':
|
|
286
|
+
// Would load from Factur-X specific Schematron
|
|
287
|
+
await validator.loadSchematron('assets_downloaded/schematron/facturx/Factur-X-EN16931-validation.sch');
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return validator;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Hybrid validator that combines TypeScript and Schematron validation
|
|
296
|
+
*/
|
|
297
|
+
export class HybridValidator {
|
|
298
|
+
private schematronValidator: SchematronValidator;
|
|
299
|
+
private tsValidators: Array<{ validate: (xml: string) => ValidationResult[] }> = [];
|
|
300
|
+
|
|
301
|
+
constructor(schematronValidator?: SchematronValidator) {
|
|
302
|
+
this.schematronValidator = schematronValidator || new SchematronValidator();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Add a TypeScript validator to the pipeline
|
|
307
|
+
*/
|
|
308
|
+
public addTSValidator(validator: { validate: (xml: string) => ValidationResult[] }): void {
|
|
309
|
+
this.tsValidators.push(validator);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Run all validators and merge results
|
|
314
|
+
*/
|
|
315
|
+
public async validate(
|
|
316
|
+
xmlContent: string,
|
|
317
|
+
options: SchematronOptions = {}
|
|
318
|
+
): Promise<ValidationResult[]> {
|
|
319
|
+
const results: ValidationResult[] = [];
|
|
320
|
+
|
|
321
|
+
// Run TypeScript validators first (faster, better UX)
|
|
322
|
+
for (const validator of this.tsValidators) {
|
|
323
|
+
try {
|
|
324
|
+
results.push(...validator.validate(xmlContent));
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.warn(`TS validator failed: ${error.message}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Run Schematron validation if available
|
|
331
|
+
if (this.schematronValidator.hasRules()) {
|
|
332
|
+
try {
|
|
333
|
+
const schematronResults = await this.schematronValidator.validate(xmlContent, options);
|
|
334
|
+
results.push(...schematronResults);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.warn(`Schematron validation failed: ${error.message}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Deduplicate results by ruleId
|
|
341
|
+
const seen = new Set<string>();
|
|
342
|
+
return results.filter(r => {
|
|
343
|
+
if (seen.has(r.ruleId)) return false;
|
|
344
|
+
seen.add(r.ruleId);
|
|
345
|
+
return true;
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { Worker } from 'worker_threads';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import type { ValidationResult } from './validation.types.js';
|
|
4
|
+
import type { SchematronOptions } from './schematron.validator.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Worker pool for Schematron validation
|
|
8
|
+
* Provides non-blocking validation in worker threads
|
|
9
|
+
*/
|
|
10
|
+
export class SchematronWorkerPool {
|
|
11
|
+
private workers: Worker[] = [];
|
|
12
|
+
private availableWorkers: Worker[] = [];
|
|
13
|
+
private taskQueue: Array<{
|
|
14
|
+
xmlContent: string;
|
|
15
|
+
options: SchematronOptions;
|
|
16
|
+
resolve: (results: ValidationResult[]) => void;
|
|
17
|
+
reject: (error: Error) => void;
|
|
18
|
+
}> = [];
|
|
19
|
+
private maxWorkers: number;
|
|
20
|
+
private schematronRules: string = '';
|
|
21
|
+
|
|
22
|
+
constructor(maxWorkers: number = 4) {
|
|
23
|
+
this.maxWorkers = maxWorkers;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initialize worker pool
|
|
28
|
+
*/
|
|
29
|
+
public async initialize(schematronRules: string): Promise<void> {
|
|
30
|
+
this.schematronRules = schematronRules;
|
|
31
|
+
|
|
32
|
+
// Create workers
|
|
33
|
+
for (let i = 0; i < this.maxWorkers; i++) {
|
|
34
|
+
await this.createWorker();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create a new worker
|
|
40
|
+
*/
|
|
41
|
+
private async createWorker(): Promise<void> {
|
|
42
|
+
const workerPath = path.join(import.meta.url, 'schematron.worker.impl.js');
|
|
43
|
+
|
|
44
|
+
const worker = new Worker(`
|
|
45
|
+
const { parentPort } = require('worker_threads');
|
|
46
|
+
const SaxonJS = require('saxon-js');
|
|
47
|
+
|
|
48
|
+
let compiledStylesheet = null;
|
|
49
|
+
|
|
50
|
+
parentPort.on('message', async (msg) => {
|
|
51
|
+
try {
|
|
52
|
+
if (msg.type === 'init') {
|
|
53
|
+
// Compile Schematron to XSLT
|
|
54
|
+
compiledStylesheet = await SaxonJS.compile({
|
|
55
|
+
stylesheetText: msg.xslt,
|
|
56
|
+
warnings: 'silent'
|
|
57
|
+
});
|
|
58
|
+
parentPort.postMessage({ type: 'ready' });
|
|
59
|
+
} else if (msg.type === 'validate') {
|
|
60
|
+
if (!compiledStylesheet) {
|
|
61
|
+
throw new Error('Worker not initialized');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Transform XML with compiled Schematron
|
|
65
|
+
const result = await SaxonJS.transform({
|
|
66
|
+
stylesheetInternal: compiledStylesheet,
|
|
67
|
+
sourceText: msg.xmlContent,
|
|
68
|
+
destination: 'serialized',
|
|
69
|
+
stylesheetParams: msg.options.parameters || {}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
parentPort.postMessage({
|
|
73
|
+
type: 'result',
|
|
74
|
+
svrl: result.principalResult
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
parentPort.postMessage({
|
|
79
|
+
type: 'error',
|
|
80
|
+
error: error.message
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
`, { eval: true });
|
|
85
|
+
|
|
86
|
+
// Initialize worker with Schematron rules
|
|
87
|
+
await new Promise<void>((resolve, reject) => {
|
|
88
|
+
worker.once('message', (msg) => {
|
|
89
|
+
if (msg.type === 'ready') {
|
|
90
|
+
resolve();
|
|
91
|
+
} else if (msg.type === 'error') {
|
|
92
|
+
reject(new Error(msg.error));
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Send initialization message
|
|
97
|
+
worker.postMessage({
|
|
98
|
+
type: 'init',
|
|
99
|
+
xslt: this.generateXSLTFromSchematron(this.schematronRules)
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
this.workers.push(worker);
|
|
104
|
+
this.availableWorkers.push(worker);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Validate XML using worker pool
|
|
109
|
+
*/
|
|
110
|
+
public async validate(
|
|
111
|
+
xmlContent: string,
|
|
112
|
+
options: SchematronOptions = {}
|
|
113
|
+
): Promise<ValidationResult[]> {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
// Add task to queue
|
|
116
|
+
this.taskQueue.push({ xmlContent, options, resolve, reject });
|
|
117
|
+
this.processTasks();
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Process queued validation tasks
|
|
123
|
+
*/
|
|
124
|
+
private processTasks(): void {
|
|
125
|
+
while (this.taskQueue.length > 0 && this.availableWorkers.length > 0) {
|
|
126
|
+
const task = this.taskQueue.shift()!;
|
|
127
|
+
const worker = this.availableWorkers.shift()!;
|
|
128
|
+
|
|
129
|
+
// Set up one-time listeners
|
|
130
|
+
const messageHandler = (msg: any) => {
|
|
131
|
+
if (msg.type === 'result') {
|
|
132
|
+
// Parse SVRL and return results
|
|
133
|
+
const results = this.parseSVRL(msg.svrl);
|
|
134
|
+
task.resolve(results);
|
|
135
|
+
|
|
136
|
+
// Return worker to pool
|
|
137
|
+
this.availableWorkers.push(worker);
|
|
138
|
+
worker.removeListener('message', messageHandler);
|
|
139
|
+
|
|
140
|
+
// Process next task
|
|
141
|
+
this.processTasks();
|
|
142
|
+
} else if (msg.type === 'error') {
|
|
143
|
+
task.reject(new Error(msg.error));
|
|
144
|
+
|
|
145
|
+
// Return worker to pool
|
|
146
|
+
this.availableWorkers.push(worker);
|
|
147
|
+
worker.removeListener('message', messageHandler);
|
|
148
|
+
|
|
149
|
+
// Process next task
|
|
150
|
+
this.processTasks();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
worker.on('message', messageHandler);
|
|
155
|
+
|
|
156
|
+
// Send validation task
|
|
157
|
+
worker.postMessage({
|
|
158
|
+
type: 'validate',
|
|
159
|
+
xmlContent: task.xmlContent,
|
|
160
|
+
options: task.options
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Parse SVRL output
|
|
167
|
+
*/
|
|
168
|
+
private parseSVRL(svrlXml: string): ValidationResult[] {
|
|
169
|
+
const results: ValidationResult[] = [];
|
|
170
|
+
|
|
171
|
+
// This would use the same parsing logic as SchematronValidator
|
|
172
|
+
// Simplified for brevity
|
|
173
|
+
|
|
174
|
+
return results;
|
|
175
|
+
}
|
|
176
|
+
|
|
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
|
+
/**
|
|
198
|
+
* Terminate all workers
|
|
199
|
+
*/
|
|
200
|
+
public async terminate(): Promise<void> {
|
|
201
|
+
await Promise.all(this.workers.map(w => w.terminate()));
|
|
202
|
+
this.workers = [];
|
|
203
|
+
this.availableWorkers = [];
|
|
204
|
+
this.taskQueue = [];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Get pool statistics
|
|
209
|
+
*/
|
|
210
|
+
public getStats(): {
|
|
211
|
+
totalWorkers: number;
|
|
212
|
+
availableWorkers: number;
|
|
213
|
+
queuedTasks: number;
|
|
214
|
+
} {
|
|
215
|
+
return {
|
|
216
|
+
totalWorkers: this.workers.length,
|
|
217
|
+
availableWorkers: this.availableWorkers.length,
|
|
218
|
+
queuedTasks: this.taskQueue.length
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|