Files
vda-to-edifact-converter/js/edifact-validator.js
2026-03-13 09:53:40 +01:00

219 lines
7.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* EDIFACT Validator & Alternative Parser (Main Process only)
* Uses ts-edifact library as a parallel layer alongside the custom DelforParser.
*
* IMPORTANT: This file runs in the Node.js Main Process only.
* Do NOT include this file in index.html or use window.EDIBridge here.
*
* Exposed via IPC (see main.js):
* 'validate-edifact' → validateEdifact(content)
* 'parse-edifact-lib' → parseWithLib(content)
*
* ts-edifact API:
* const { Reader } = require('ts-edifact');
* const reader = new Reader(); // instanciate per call (stateful)
* const segments = reader.parse(str); // returns { name, elements: string[][] }[]
*/
'use strict';
/**
* Parse EDIFACT content using ts-edifact Reader.
* Returns a clean segment array for comparison with the custom DelforParser.
*
* Segment format: { name: 'UNH', elements: [['1'], ['DELFOR','D','04A','UN']] }
*
* @param {string} content - Raw EDIFACT content (string)
* @returns {{ segments: Array, segmentCount: number, error?: string }}
*/
function parseWithLib(content) {
try {
const { Reader, NullValidator, Parser } = require('ts-edifact');
// Normalize line endings
const normalized = content.replace(/\r\n/g, '').replace(/\r/g, '').trim();
// Each Reader instance is stateful — create a new one per call
const reader = new Reader();
// --- FIX: Disable strict segment validation & Enable UNOC ---
const validator = new NullValidator();
if (typeof validator.define !== 'function') validator.define = () => { };
reader.validator = validator;
reader.parser = new Parser(validator);
reader.defined = true;
reader.encoding('UNOC'); // Allow lowercase and special chars
// Re-setup Reader callbacks for the new parser
const result = reader.result;
let elements = reader.elements;
let components = reader.components;
reader.parser.onOpenSegment = function (segment) {
elements = [];
result.push({ name: segment, elements: elements });
};
reader.parser.onElement = function () {
components = [];
elements.push(components);
};
reader.parser.onComponent = function (value) {
components.push(value);
};
const segments = reader.parse(normalized);
return {
segments,
segmentCount: segments.length
};
} catch (e) {
return {
segments: [],
segmentCount: 0,
error: e.message
};
}
}
/**
* Validate EDIFACT content using ts-edifact library + structural checks.
*
* Performs:
* 1. Syntax parsing via ts-edifact Reader (catches malformed EDI)
* 2. Structural checks: UNB/UNZ envelope, UNH/UNT balance, BGM presence
* 3. UNT segment count verification
* 4. Message type & version detection
*
* @param {string} content - Raw EDIFACT content
* @returns {{ valid: boolean, errors: string[], warnings: string[], stats: object }}
*/
function validateEdifact(content) {
const errors = [];
const warnings = [];
const stats = {};
try {
const { Reader, NullValidator, Parser } = require('ts-edifact');
// --- Step 1: Parse with ts-edifact ---
let segments = null;
try {
const normalized = content.replace(/\r\n/g, '').replace(/\r/g, '').trim();
const reader = new Reader();
// --- FIX: Disable strict segment validation & Enable UNOC ---
const validator = new NullValidator();
if (typeof validator.define !== 'function') validator.define = () => { };
reader.validator = validator;
reader.parser = new Parser(validator);
reader.defined = true;
reader.encoding('UNOC');
// Re-setup Reader callbacks
const result = reader.result;
let elements = reader.elements;
let components = reader.components;
reader.parser.onOpenSegment = function (segment) {
elements = [];
result.push({ name: segment, elements: elements });
};
reader.parser.onElement = function () {
components = [];
elements.push(components);
};
reader.parser.onComponent = function (value) {
components.push(value);
};
segments = reader.parse(normalized);
} catch (parseErr) {
errors.push('Syntaxfehler: ' + parseErr.message);
}
if (!segments || !Array.isArray(segments)) {
return { valid: errors.length === 0, errors, warnings, stats };
}
// Build a flat tag list for quick lookups
const tags = segments.map(s => s.name);
stats.segmentCount = segments.length;
stats.allSegments = tags;
// --- Step 2: Interchange envelope (UNB / UNZ) ---
if (!tags.includes('UNB')) {
warnings.push('Kein UNB-Segment (Interchange-Header) gefunden.');
} else {
const unb = segments.find(s => s.name === 'UNB');
if (unb && unb.elements[1]) stats.sender = unb.elements[1][0] || '';
if (unb && unb.elements[2]) stats.receiver = unb.elements[2][0] || '';
}
if (!tags.includes('UNZ')) warnings.push('Kein UNZ-Segment (Interchange-Trailer) gefunden.');
// --- Step 3: Message envelope (UNH / UNT) ---
const unhCount = tags.filter(t => t === 'UNH').length;
const untCount = tags.filter(t => t === 'UNT').length;
stats.messageCount = unhCount;
if (unhCount === 0) {
errors.push('Kein UNH-Segment (Message-Header) gefunden.');
} else {
// Detect message type from first UNH+<ref>+<type>:<ver>:<release>:<ctrl>
const unh = segments.find(s => s.name === 'UNH');
if (unh && unh.elements[1]) {
stats.messageType = unh.elements[1][0] || '';
stats.messageVersion = (unh.elements[1][1] || '') + (unh.elements[1][2] || '');
stats.controlRef = unh.elements[0]?.[0] || '';
}
}
if (untCount === 0) {
errors.push('Kein UNT-Segment (Message-Trailer) gefunden.');
}
if (unhCount !== untCount) {
errors.push(`UNH/UNT-Anzahl stimmt nicht überein: ${unhCount} × UNH vs ${untCount} × UNT.`);
}
// --- Step 4: UNT segment count check ---
// UNT+<segCount>+<msgRef>' — segCount includes UNH and UNT itself
const unt = segments.find(s => s.name === 'UNT');
if (unt && unt.elements[0]) {
const declared = parseInt(unt.elements[0][0]);
// Find the slice from UNH to UNT (inclusive)
const unhIdx = segments.findIndex(s => s.name === 'UNH');
const untIdx = segments.findIndex(s => s.name === 'UNT');
if (!isNaN(declared) && unhIdx >= 0 && untIdx >= 0) {
const actual = untIdx - unhIdx + 1;
if (declared !== actual) {
warnings.push(
`UNT deklariert ${declared} Segmente, gezählt (UNH→UNT): ${actual}.`
);
}
}
}
// --- Step 5: BGM check ---
if (!tags.includes('BGM')) {
warnings.push('Kein BGM-Segment (Nachrichtenbeginn) gefunden.');
} else {
const bgm = segments.find(s => s.name === 'BGM');
if (bgm && bgm.elements[0]) stats.documentCode = bgm.elements[0][0] || '';
}
} catch (e) {
errors.push('Unerwarteter Validierungsfehler: ' + e.message);
}
return {
valid: errors.length === 0,
errors,
warnings,
stats
};
}
module.exports = { validateEdifact, parseWithLib };