@makakwastaken/ts-edifact
Version:
Edifact parser library
480 lines • 19.3 kB
JavaScript
;
/* eslint-disable no-case-declarations */
/* eslint-disable no-redeclare */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/**
* @author Roman Vottner
* @copyright 2020 Roman Vottner
* @license Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.InterchangeBuilder = exports.Edifact = exports.Message = exports.Group = void 0;
const fs = __importStar(require("node:fs"));
const edifact_1 = require("./edifact");
const index_1 = require("./index");
const tracker_1 = require("./tracker");
const util_1 = require("./util");
class Group {
name;
parent;
data = [];
constructor(name, parent) {
this.name = name;
this.parent = parent;
}
addSegment(segment) {
if (segment) {
this.data.push(segment);
}
}
addGroup(group) {
if (group) {
this.data.push(group);
}
}
groupCount() {
let count = 0;
for (const group of this.data) {
if (group instanceof Group) {
count++;
}
}
return count;
}
containsGroup(groupName) {
for (const group of this.data) {
if (group instanceof Group && group.name === groupName) {
return true;
}
}
return false;
}
groupByName(groupName) {
for (const group of this.data) {
if (group instanceof Group && group.name === groupName) {
return group;
}
}
return undefined;
}
}
exports.Group = Group;
class Message {
messageHeader;
header = [];
detail = [];
summary = [];
constructor(data) {
const formattedComponents = (0, util_1.formatComponents)(data.elements, data.name);
this.messageHeader = formattedComponents;
}
addSegment(segment, sectionName) {
this.section(sectionName).push(segment);
}
addGroup(group, sectionName) {
this.section(sectionName).push(group);
}
section(name) {
if (name === 'header') {
return this.header;
}
if (name === 'detail') {
return this.detail;
}
if (name === 'summary') {
return this.summary;
}
return this.header.concat(this.detail).concat(this.summary);
}
groupCount(sectionName) {
let count = 0;
for (const group of this.section(sectionName)) {
if (group instanceof Group) {
count++;
}
}
return count;
}
containsGroup(groupName, sectionName) {
for (const group of this.section(sectionName)) {
if (group instanceof Group && group.name === groupName) {
return true;
}
}
return false;
}
groupByName(groupName, sectionName) {
for (const group of this.section(sectionName)) {
if (group instanceof Group && group.name === groupName) {
return group;
}
}
return undefined;
}
}
exports.Message = Message;
class Edifact {
syntaxIdentifier;
sender;
receiver;
date;
time;
interchangeNumber;
recipientsRef;
applicationRef;
processingPriorityCode;
ackRequest;
agreementId;
testIndicator;
messages = [];
constructor(elements) {
const formattedElements = (0, util_1.formatComponents)(elements, 'UNH');
this.syntaxIdentifier =
formattedElements.syntaxIdentifier;
this.sender = formattedElements.interchangeSender;
this.receiver = formattedElements.interchangeRecipient;
this.date = (formattedElements.dateAndTimeOfPreparation?.date ||
'');
this.time = (formattedElements.dateAndTimeOfPreparation?.time ||
'');
this.interchangeNumber = elements[4].components[0].value;
if (elements.length >= 6) {
this.recipientsRef =
formattedElements.interchangeControlReference;
}
if (elements.length >= 7) {
this.applicationRef = formattedElements.applicationReference;
}
if (elements.length >= 8) {
this.processingPriorityCode =
formattedElements.processingPriorityCode;
}
if (elements.length >= 9) {
this.ackRequest = Number.parseInt(formattedElements.acknowledgementRequest || '');
}
if (elements.length >= 10) {
this.agreementId =
formattedElements.interchangeAgreementIdentifier;
}
if (elements.length === 11) {
this.testIndicator = Number.parseInt(formattedElements.testIndicator || '');
}
else {
this.testIndicator = 0;
}
}
addMessage(message) {
this.messages.push(message);
}
}
exports.Edifact = Edifact;
class InterchangeBuilder {
interchange;
stack = [];
curSection = 'header';
/**
* Uses the provided parsing result to create an Edifact interchange structure. This
* process will validate the order of the parsed segment definitions against available
* Edifact message structure definition files, which are determined by the respective
* version defined in the UNH segments of the parsing result.
*
* This process will fail if mandatory segments are missing of if any unexpected
* segments, not defined in the message structure definition file, are found.
*
* @param parsingResult The actual result of the Edifact document parsing process.
* @param basePath The base location the Edifact message structure definition files
* in JSON format can be found
*/
constructor(parsingResult, separators, basePath) {
if (!parsingResult || parsingResult.length === 0) {
throw Error('Invalid list of parsed segments provided');
}
let interchange;
for (const segment of parsingResult) {
if (segment.name === 'UNB') {
interchange = new Edifact(segment.elements);
continue;
}
if (segment.name === 'UNZ') {
continue;
}
if (segment.name === 'UNH') {
const message = new Message(segment);
// lookup the message definition for the respective edifact version, i.e. D96A => INVOIC
const messageVersion = message.messageHeader.messageIdentifier.messageVersionNumber +
message.messageHeader.messageIdentifier.messageReleaseNumber;
const messageType = message.messageHeader.messageIdentifier.messageType;
const table = this.getMessageStructureDefForMessage(basePath, messageVersion, messageType);
this.stack = [new tracker_1.Pointer(table, 0)];
this.curSection = 'header';
if (interchange) {
interchange.addMessage(message);
}
else {
throw Error('');
}
}
/* eslint-disable no-case-declarations */
const message = interchange?.messages[interchange.messages.length - 1];
if (message) {
const messageVersion = message.messageHeader.messageIdentifier.messageVersionNumber +
message.messageHeader.messageIdentifier.messageReleaseNumber;
this.accept(segment, message, messageVersion, separators.decimalSeparator);
}
else {
throw Error(`Couldn't process ${segment.name} segment as no message was found.`);
}
if (segment.name === 'UNT') {
this.reset();
}
}
if (interchange) {
this.interchange = interchange;
}
else {
throw Error('Could not generate EDIFACT interchange structure');
}
}
reset() {
this.stack.length = 1;
this.stack[0].position = 0;
this.stack[0].count = 0;
}
accept(segment, obj, version, decimalSeparator) {
let current = this.stack[this.stack.length - 1];
let optionals = [];
let probe = 0;
while (segment.name !== current.content() ||
current.count === current.repetition()) {
if (Array.isArray(current.content()) &&
current.count < current.repetition()) {
// Enter the subgroup
probe++;
if (!current.mandatory()) {
optionals.push(this.stack.length);
}
current.count++;
current = new tracker_1.Pointer(current.content(), 0);
this.stack.push(current);
}
else {
// Check if we are omitting mandatory content
if (current.mandatory() && current.count === 0) {
if (optionals.length === 0) {
const segName = current.name();
if (segName) {
throw new Error(`A mandatory segment ${current.content()} defined in segment group '${segName}' is missing`);
}
// We will never encounter groups here, so we can safely use the
// name of the segment stored in it's content property
throw new Error(`A mandatory segment ${current.content()} is missing`);
}
// If we are omitting mandatory content inside a conditional group,
// we just skip the entire group
probe = probe - this.stack.length;
this.stack.length = optionals.pop();
current = this.stack[this.stack.length - 1];
probe = probe + this.stack.length;
}
current.position++;
current.count = 0;
const sect = current.section();
if (sect) {
this.curSection = sect;
}
if (current.position === current.array.length) {
this.stack.pop();
current = this.stack[this.stack.length - 1];
if (this.stack.length === 0) {
throw new Error(`Reached the end of the segment table while processing segment ${segment.name}`);
}
if (probe === 0 && current.count < current.repetition()) {
// If we are not currently probing (meaning the tracker actually
// accepted the group), we should retry the current group, except if
// the maximum number of repetition was reached
probe++;
optionals = [this.stack.length];
current.count++;
current = new tracker_1.Pointer(current.content(), 0);
this.stack.push(current);
}
else {
if (!current.mandatory() || current.count > 1) {
optionals.pop();
}
// Decrease the probing level only if the tracker is currently in a
// probing state
probe = probe > 0 ? probe - 1 : 0;
// Make sure the tracker won't enter the current group again by
// setting an appropriate count value on the groups pointer
current.count = current.repetition();
}
}
}
}
current.count += 1;
// Generate the tree-structure of the Edifact document
if (this.stack.length > 1) {
let curObj = obj;
for (let idx = 0; idx < this.stack.length; idx++) {
const pointer = this.stack[idx];
const groupName = pointer.name();
if (groupName) {
if (!curObj.containsGroup(groupName)) {
const group = new Group(groupName, curObj);
curObj.addGroup(group, this.curSection);
curObj = group;
}
else {
const group = curObj.groupByName(groupName);
if (group) {
curObj = group;
// check wheter the stack count is larger than 1, if so, we know that
// there is a repetition going on, which we would like to put into
// their own subgroups.
if (pointer.count > 1) {
// If the first entry in the object is not a group, we need to
// pop everything from that group, create a new subgroup, assign
// the popped fields to the subgroup and add the subgroup to the
// group. We can assume that the first entry to a group will never
// be a group itself but a segment
if (!(group.data[0] instanceof Group)) {
const subGroup = new Group('0', group);
for (const data of group.data) {
if (data instanceof Group) {
subGroup.addGroup(data);
}
else {
subGroup.addSegment(data);
}
}
group.data = [];
group.addGroup(subGroup);
}
const subGroup = group.groupByName(`${pointer.count - 1}`);
if (subGroup) {
curObj = subGroup;
}
else {
const sg = new Group(`${group.groupCount()}`, group);
group.addGroup(sg);
curObj = sg;
}
}
}
else {
throw Error(`Could not find group ${groupName} as part of ${curObj.toString()}`);
}
}
}
else {
const seg = (0, edifact_1.toSegmentObject)(segment, version, decimalSeparator);
curObj.addSegment(seg, this.curSection);
}
}
}
else {
// UNH is already converted to a Message object, so we don't need to store
// that data again
if (segment.name !== 'UNH') {
const seg = (0, edifact_1.toSegmentObject)(segment, version, decimalSeparator);
obj.addSegment(seg, this.curSection);
}
}
}
getMessageStructureDefForMessage(basePath, messageVersion, messageType) {
let path = `${basePath + messageVersion}_${messageType}.struct.json`;
if (fs.existsSync(path)) {
return this.readFileAsMessageStructure(path);
}
path = `${basePath + messageType}struct.json`;
if (fs.existsSync(path)) {
return this.readFileAsMessageStructure(path);
}
switch (messageType) {
// default back to D01B messages
case 'APERAK':
return index_1.APERAK;
case 'AUTHOR':
return index_1.AUTHOR;
case 'BALANC':
return index_1.BALANC;
case 'DESADV':
return index_1.DESADV;
case 'GENRAL':
return index_1.GENRAL;
case 'IFTMIN':
return index_1.IFTMIN;
case 'INVOIC':
return index_1.INVOIC;
case 'INVRPT':
return index_1.INVRPT;
case 'ORDERS':
return index_1.ORDERS;
case 'OSTENQ':
return index_1.OSTENQ;
case 'OSTRPT':
return index_1.OSTRPT;
case 'PARTIN':
return index_1.PARTIN;
case 'TAXCON':
return index_1.TAXCON;
case 'VATDEC':
return index_1.VATDEC;
default:
throw new Error(`Could not find message definiton for message type '${messageType}' of version '${messageVersion}'`);
}
}
readFileAsMessageStructure(path) {
const data = fs.readFileSync(path, { encoding: 'utf-8' });
return JSON.parse(data);
}
}
exports.InterchangeBuilder = InterchangeBuilder;
//# sourceMappingURL=interchangeBuilder.js.map