edifact_orders_iso_16033
Version:
parser for EDIFACT ORDERS ISO 16033 Ophthalmic optics and instruments
225 lines (205 loc) • 8.16 kB
JavaScript
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;