@makakwastaken/ts-edifact
Version:
Edifact parser library
223 lines • 10.2 kB
JavaScript
/* 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.
*/
import { Cache } from './cache';
import { ComponentValueTableBuilder } from './components';
import { Configuration } from './configuration';
import { Parser } from './parser';
import { SegmentTableBuilder } from './segments';
import { findElement, isDefined } from './util';
import { Dictionary, ValidatorImpl, } from './validator';
/**
* The `Reader` class is included for backwards compatibility. It translates an
* UN/EDIFACT document to an array of segments. Each segment has a `name` and
* `elements` property where `elements` is an array consisting of component
* arrays. The class exposes a `parse()` method which accepts the document as a
* string.
*/
export class Reader {
result;
elements;
element;
validator;
parser;
defined = false;
validationTables = [];
componentValues = new Dictionary();
definitionCache = new Cache(15);
unbCharsetDefined = false;
separators;
constructor(messageSpecDir, config = {}) {
const configuration = new Configuration({
validator: new ValidatorImpl(config.throwOnMissingSegments || false),
});
this.parser = new Parser(configuration);
this.validator = configuration.validator;
// Holds the results
this.result = [];
// Holds the elements
this.elements = [];
let components = [];
// Holds the temporary element components.
let componentIndex = 0;
let activeSegment;
// When a new segment is opened, reset all things element and set the current segment
this.parser.onOpenSegment = (segment, segmentEntry) => {
this.elements = [];
this.element = undefined;
// Set the currently active segments
activeSegment = segmentEntry
? { id: segment, segmentEntry: segmentEntry }
: null;
};
this.parser.onElement = (newElement) => {
// Add the previous element
if (this.element) {
this.elements.push({ ...this.element, components });
}
// Set the current element
this.element = newElement;
// Reset component values
components = [];
componentIndex = 0;
};
this.parser.onComponent = (value) => {
if (activeSegment?.id === 'UNB' && !this.unbCharsetDefined) {
this.parser.updateCharset(value);
this.unbCharsetDefined = true;
}
// Replace value at index with correct one
if (this.element) {
const component = this.element.components[componentIndex];
// Check if it is a coded value
if (this.componentValues.contains(component.id)) {
const componentFormatRegex = /(a)?(n)?(\.\.)?([0-9]*)?/g;
const componentFormat = componentFormatRegex.exec(component.format);
if (isDefined(componentFormat)) {
const upto = componentFormat[3] === '..';
const componentValues = this.componentValues.get(component.id);
const componentValue = componentValues[value];
if (!componentValue) {
if (upto) {
// If the value is marked as 'upto' it is not required to be in the component value table as it can be ''
components.push({
...component,
value: {
id: value,
value: 'No code provided', // Can't be '' as it is marked as failure if id and value are the same
description: 'No code provided',
},
});
}
else if (config.throwOnInvalidComponentValue) {
throw new Error(`Invalid component value '${value}' for component '${component.id}'`);
}
else {
components.push({
...component,
value: {
id: value,
value,
description: 'Code not found',
},
});
}
}
else {
components.push({ ...component, value: componentValue });
}
}
}
else {
components.push({ ...component, value });
}
componentIndex++;
}
};
this.parser.onCloseSegment = () => {
if (isDefined(activeSegment)) {
if (this.element) {
// Add the final element, when a segment ends (Prevents the final element from missing)
this.elements.push({ ...this.element, components });
}
// Update the respective segment and element definitions once we know the exact version
// of the document
if (activeSegment.id === 'UNH') {
const messageIdentifier = findElement(this.elements, 'S009')?.components;
const messageType = messageIdentifier?.[0]?.value;
const messageVersion = messageIdentifier?.[1]?.value;
const messageRelease = messageIdentifier?.[2]?.value;
const key = `${messageVersion + messageRelease}_${messageType}`;
if (this.definitionCache.contains(key)) {
const { segmentTable, componentValueTable } = this.definitionCache.get(key);
this.componentValues = componentValueTable;
this.validator.define(segmentTable);
}
else {
// Get the segments and component definitions
let segmentTableBuilder = new SegmentTableBuilder(messageType);
let componentValueTableBuilder = new ComponentValueTableBuilder(messageType);
const version = (messageVersion + messageRelease).toUpperCase();
segmentTableBuilder = segmentTableBuilder.forVersion(version);
componentValueTableBuilder = componentValueTableBuilder.forVersion(version);
if (messageSpecDir) {
segmentTableBuilder =
segmentTableBuilder.specLocation(messageSpecDir);
componentValueTableBuilder.specLocation(messageSpecDir);
}
else {
segmentTableBuilder = segmentTableBuilder.specLocation('./');
componentValueTableBuilder =
componentValueTableBuilder.specLocation('./');
}
const segmentTable = segmentTableBuilder.build();
const componentValueTable = componentValueTableBuilder.build();
this.componentValues = componentValueTable;
this.validator.define(segmentTable);
this.definitionCache.insert(key, {
segmentTable,
componentValueTable,
});
}
}
// Add the current elements to the results array
this.result.push({
name: activeSegment.id,
elements: this.elements,
});
activeSegment = null;
}
};
// will initialize default separators
this.separators = this.parser.separators();
}
/**
* Provide the underlying `Validator` with segment or element definitions.
*
* @summary Define segment and element structures.
* @param definitions An object containing the definitions.
*/
define(definitions) {
this.validator.define(definitions);
}
initializeIfNeeded() {
if (!this.defined) {
if (this.validationTables.length > 0) {
for (const table of this.validationTables) {
this.validator.define(table);
}
}
else {
// basic Edifact envelop validation, i.e. UNB, UNH, UNS and UNZ
this.validator.define(SegmentTableBuilder.enrichWithDefaultSegments(new Dictionary()));
}
this.defined = true;
}
}
parse(document) {
this.initializeIfNeeded();
this.result = [];
this.parser.write(document);
this.parser.end();
// update separators in case the document contained a UNA header
// with custom separators
this.separators = this.parser.separators();
return this.result;
}
}
//# sourceMappingURL=reader.js.map