UNPKG

@makakwastaken/ts-edifact

Version:
480 lines 19.3 kB
"use strict"; /* 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