UNPKG

edifact_orders_iso_16033

Version:

parser for EDIFACT ORDERS ISO 16033 Ophthalmic optics and instruments

225 lines (205 loc) 8.16 kB
const Parser = require("edifact/parser"); const Tokenizer = require("edifact/tokenizer"); const Cache = require("edifact/cache"); const Validator = require("edifact/validator"); const _ = require('lodash'); // enhance error message that are difficult to interpret in case of non visible characters Parser.errors.invalidControlAfterSegment = function (segment, character) { return new Error(`Invalid character ${character} (${character.charCodeAt(0)}) after reading segment name ${segment}`); } Parser.errors.invalidCharacter= function (character, index) { return new Error(`Invalid character ${character} (${character.charCodeAt(0)}) at position ${index}`); } /** Segment * this class represent a segment with all elements / component */ class Segment { constructor(segment) { this.segment = segment; this.elements = []; } /** return the value of a given element/component of the segment * if the element or component doen't exist, return undefined. * * @param {int} element the number of the element of the segment * @param {int} component the number of the compenent of the element or undefined if all components must be returned */ val(element,component) { let v = this.elements[element] if (component !== undefined) { v = v && v[component]; } return v; } } class MessageError extends Error{ }; /** Message * The super class for all message interpreters. * * Implement the mechanism of message interpretation but does noting in itself * since it has no implementation of any segment * */ class Message { constructor(encoding,segments,elements) { // for internal use this._segments = segments; this._elements = elements; this._encoding = encoding; } /** reset is called at the beginning of interpret and must clear all previous data. * * should be overwritten by the class that extends Message */ reset(){ this.data = {}; } /** Interpret an EDIFACT Messaage by calling methods for each segment * * Errors will be reported by exceptions * * @param {string} edifact the edifact string */ interpret(edifact) { Tokenizer.cache = new Cache(40); // workaround bug https://github.com/tdecaluwe/node-edifact/issues/33 const validator = new Validator(); validator.define(this._segments); validator.define(this._elements); this.parser = new Parser(validator); this.parser.on('opensegment', (segment) => { this.openSegment(segment); }); this.parser.on('closesegment', () => { this.closeSegment(); }); this.parser.on('element', () => { this.element(); }); this.parser.on('component', (data) => { this.component(data); }); this.parser.encoding(this._encoding); // those properties keep the current status of the parsing this._currentMessage = undefined; this._section = 'Interchange'; // = before messages in message: Header or Detail or Summary cf(https://service.unece.org/trade/untdid/d00a/trmd/orders_c.htm) this._currentLine = undefined; this._currentSegment = undefined; this._segmentNumber = 1; // the first segment UNA is already consumed by the Parser this._segmentNumberInMessage = 0; this._currentElement = undefined; this.reset(); // do the parsing and catch errors try { this.parser.write(edifact); } catch (error) { if (error instanceof MessageError) { //comes from the Message throw(error); } else { throw new Error(`Edifact Parser Error: ${this.errorLocalization()} ${error}`); } } this.parser.end(); } ////////////////////// for internal use //////////////////////////////////// //------ error reporting ----------------- errorLocalization() { // todo traiter le message return `section=${this._section}, segment=${_.get(this,'_currentSegment.segment')} (segment #${this._segmentNumber}) ${_.get(this,'this._currentMessage.lines.length') ? 'line ' + this._currentMessage.lines.length : ''} >`; } notImplemented(message) { throw new MessageError(`Not Implemented: ${this.errorLocalization()} ${message}`); } unexpected(message) { throw new MessageError(`Unexpected : ${this.errorLocalization()} ${message}`); } /** throw an error if variable has not an expected value * * variable = the variable to check * values an expected value or an array of expected value. if undefined, doesn't check, just print the message * * returns variable if everything ok */ expect(variable, values, message) { if (Array.isArray(values)) { if (values.indexOf(variable) === -1) { throw new MessageError(`Unexpected value: ${this.errorLocalization()} ${message} found ${variable} but should be one of ${values}`); }; } else { if (variable !== values) { throw new MessageError(`Unexpected value: ${this.errorLocalization()} ${message} found ${variable} but should be ${values}`); } } return variable; } /** check that a given component has a given value or is in a given set of values * * @param {*} elementNumber of the current segment * @param {*} componentNumber in the element * @param {*} values either a string or an array of string */ expectVal(elementNumber,componentNumber,values){ const variable = this._currentSegment.val(elementNumber,componentNumber); if (Array.isArray(values)) { if (values.indexOf(variable) === -1) { const elementCode = this._segments[this._currentSegment.segment].elements[elementNumber]; const componentType = this._elements[elementCode].components[componentNumber]; throw new MessageError(`Unexpected value: ${this.errorLocalization()} ${elementCode} [${componentNumber}]${componentType} found ${variable} but should be one of ${values}`); }; } else { if (variable !== values) { const elementCode = this._segments[this._currentSegment.segment].elements[elementNumber]; const componentType = this._elements[elementCode].components[componentNumber]; throw new MessageError(`Unexpected value: ${this.errorLocalization()} ${elementCode} [${componentNumber}]${componentType} found ${variable} but should be ${values}`); } } return variable; } // ---- helpers ----------------------------- /** set a property if the given element / component is defined * @param {object} obj the object to set a property * @param {string} property the property name * @param {number} elementNumber the element number of the current segment * @param {number} componentNumber the number of the component to look for. * if undefined, the array of components is put into the property * * if either the element or element.component of the _currentSegment is undefined, nothing is done */ setProp(obj, property, elementNumber, componentNumber) { let v = this._currentSegment.elements[elementNumber]; if (componentNumber !== undefined) { v = v && v[componentNumber]; } if (v) { obj[property] = v; } } /** understand the previous segment and open a new one in order to collect elements * * * @param {*} segment name of the segment */ openSegment(segment) { this._segmentNumber++; this._segmentNumberInMessage++; this._currentSegment = new Segment(segment); } closeSegment() { if (this._currentSegment) { let interpreter = this['segment'+this._currentSegment.segment]; if (interpreter) { interpreter.call(this, this._currentSegment); } else { // TODO à terme faire une erreur, une fois que tous les interpreteurs ont été écrit this.notImplemented(`segment ${this._currentSegment.segment} has no interpreter`); } } else { throw new Error('Internal Error: unexpected close Segment and currentSegment is undefined'); } } element() { this._currentElement = []; this._currentSegment.elements.push(this._currentElement); } component(data) { this._currentElement.push(data); } } module.exports = Message;