Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix edge case for element missing in document #26

Merged
merged 19 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"project": "./tsconfig.json"
},
"rules": {
"@typescript-eslint/strict-boolean-expressions": ["off"],
"@typescript-eslint/no-this-alias": ["off"],
"@typescript-eslint/prefer-includes": ["off"],
"@typescript-eslint/restrict-template-expressions": ["off"],
Expand Down
31,734 changes: 23,223 additions & 8,511 deletions package-lock.json

Large diffs are not rendered by default.

58 changes: 29 additions & 29 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "node-x12",
"version": "1.6.2",
"version": "1.7.0",
"description": "ASC X12 parser, generator, query engine, and mapper; now with support for streams.",
"main": "index.js",
"keywords": [
Expand Down Expand Up @@ -40,41 +40,41 @@
},
"dependencies": {},
"devDependencies": {
"@babel/cli": "^7.10.5",
"@babel/core": "^7.11.0",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/preset-env": "^7.11.0",
"@babel/preset-typescript": "^7.10.4",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@types/mocha": "^8.0.1",
"@types/node": "^14.0.27",
"@typescript-eslint/eslint-plugin": "^3.8.0",
"codecov": "^3.7.2",
"del-cli": "^3.0.1",
"@babel/cli": "^7.17.6",
"@babel/core": "^7.17.9",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/preset-env": "^7.16.11",
"@babel/preset-typescript": "^7.16.7",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@types/mocha": "^9.1.1",
"@types/node": "^17.0.25",
"@typescript-eslint/eslint-plugin": "^5.20.0",
"codecov": "^3.8.2",
"del-cli": "^4.0.1",
"dts-bundle-webpack": "^1.0.2",
"eslint": "^7.6.0",
"eslint": "^8.13.0",
"eslint-config-standard-jsdoc": "^9.3.0",
"eslint-config-standard-with-typescript": "^18.0.2",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jsdoc": "^30.1.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsdoc": "^39.2.7",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-standard": "^4.1.0",
"gulp": "^4.0.2",
"gulp-mocha": "^7.0.2",
"gulp-mocha": "^8.0.0",
"gulp-shell": "^0.8.0",
"gulp-zip": "^5.0.2",
"gulp-zip": "^5.1.0",
"jsdoc-babel": "^0.5.0",
"jsdoc-to-markdown": "^6.0.1",
"liquidjs": "^9.14.1",
"mocha": "^8.1.0",
"jsdoc-to-markdown": "^7.1.1",
"liquidjs": "^9.37.0",
"mocha": "^9.2.2",
"mocha-lcov-reporter": "^1.3.0",
"nyc": "^15.1.0",
"source-map-support": "^0.5.19",
"ts-loader": "^8.0.2",
"ts-node": "^8.10.2",
"typescript": "^3.9.7",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12"
"source-map-support": "^0.5.21",
"ts-loader": "^9.2.8",
"ts-node": "^10.7.0",
"typescript": "^4.6.3",
"webpack": "^5.72.0",
"webpack-cli": "^4.9.2"
}
}
37 changes: 21 additions & 16 deletions src/X12QueryEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@ import { X12Transaction } from './X12Transaction'
import { X12Segment } from './X12Segment'
import { X12Element } from './X12Element'

export type X12QueryMode = 'strict' | 'loose'

export class X12QueryEngine {
/**
* @description Factory for querying EDI using the node-x12 object model.
* @param {X12Parser|boolean} [parser] - Pass an external parser or set the strictness of the internal parser.
* @param {'strict'|'loose'} [mode='strict'] - Sets the mode of the query engine, defaults to classic 'strict'; adds new behavior of 'loose', which will return an empty value for a missing element so long as the segment exists.
*/
constructor (parser: X12Parser | boolean = true) {
constructor (parser: X12Parser | boolean = true, mode: X12QueryMode = 'strict') {
this._parser = typeof parser === 'boolean' ? new X12Parser(parser) : parser
this._mode = mode
}

private readonly _parser: X12Parser

private readonly _mode: X12QueryMode
private readonly _forEachPattern: RegExp = /FOREACH\([A-Z0-9]{2,3}\)=>.+/g
private readonly _concatPattern: RegExp = /CONCAT\(.+,.+\)=>.+/g

Expand Down Expand Up @@ -53,11 +57,8 @@ export class X12QueryEngine {

const results = new Array<X12QueryResult>()

for (let i = 0; i < interchange.functionalGroups.length; i++) {
const group = interchange.functionalGroups[i]

for (let j = 0; j < group.transactions.length; j++) {
const txn = group.transactions[j]
for (const group of interchange.functionalGroups) {
for (const txn of group.transactions) {
let segments = txn.segments

if (hlPathMatch !== null) {
Expand Down Expand Up @@ -257,9 +258,7 @@ export class X12QueryEngine {

const results = new Array<X12QueryResult>()

for (let i = 0; i < segments.length; i++) {
const segment = segments[i]

for (const segment of segments) {
if (segment === null || segment === undefined) {
continue
}
Expand All @@ -270,10 +269,16 @@ export class X12QueryEngine {

const value = segment.valueOf(posint, defaultValue)

if (value !== null && this._testQualifiers(transaction, segment, qualifiers)) {
results.push(
new X12QueryResult(interchange, functionalGroup, transaction, segment, segment.elements[posint - 1], value)
)
if (this._testQualifiers(transaction, segment, qualifiers)) {
if ((typeof value !== 'undefined' && value !== null)) {
results.push(
new X12QueryResult(interchange, functionalGroup, transaction, segment, segment.elements[posint - 1], value)
)
} else if (this._mode === 'loose') {
results.push(
new X12QueryResult(interchange, functionalGroup, transaction, segment, new X12Element(), undefined)
)
}
}
}

Expand All @@ -285,8 +290,8 @@ export class X12QueryEngine {
return true
}

for (let i = 0; i < qualifiers.length; i++) {
const qualifier = qualifiers[i].substr(1)
for (const qualifierValue of qualifiers) {
const qualifier = qualifierValue.substr(1)
const elementReference = qualifier.substring(0, qualifier.indexOf('['))
const elementValue = qualifier.substring(qualifier.indexOf('[') + 2, qualifier.lastIndexOf(']') - 1)
const tag = elementReference.substr(0, elementReference.length - 2)
Expand Down
6 changes: 4 additions & 2 deletions src/X12Segment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ export class X12Segment {
values[15] = this.options.subElementDelimiter
}

if (values.length === enumerable.COUNT) {
if (values.length === enumerable.COUNT || values.length === enumerable.COUNT_MIN) {
for (let i = 0; i < values.length; i++) {
const name = `${this.tag}${String.prototype.padStart.call(i + 1, 2, '0')}`
const max = enumerable[name]
Expand Down Expand Up @@ -241,7 +241,9 @@ export class X12Segment {
}
} else {
throw new GeneratorError(
`Segment "${this.tag}" with ${values.length} elements does meet the required count of ${enumerable.COUNT}.`
typeof enumerable.COUNT_MIN === 'number'
? `Segment "${this.tag}" with ${values.length} elements does not meet the required count of min ${enumerable.COUNT_MIN} or max ${enumerable.COUNT}.`
: `Segment "${this.tag}" with ${values.length} elements does not meet the required count of ${enumerable.COUNT}.`
)
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/X12SegmentHeader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ export const STSegmentHeader: X12SegmentHeader = {
ST01: 3,
ST02: 9,
ST02_MIN: 4,
COUNT: 2,
ST03: 35,
ST03_MIN: 1,
COUNT: 3,
COUNT_MIN: 2,
PADDING: false
}
}
12 changes: 7 additions & 5 deletions src/X12Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { X12Segment } from './X12Segment'
import { STSegmentHeader } from './X12SegmentHeader'
import { X12TransactionMap } from './X12TransactionMap'
import { defaultSerializationOptions, X12SerializationOptions } from './X12SerializationOptions'
import type { X12QueryMode } from './X12QueryEngine'

export class X12Transaction {
/**
Expand Down Expand Up @@ -68,11 +69,12 @@ export class X12Transaction {
/**
* @description Map data from a transaction set to a javascript object.
* @param {object} map - The javascript object containing keys and querys to resolve.
* @param {Function} helper - A helper function which will be executed on every resolved query value.
* @param {Function|'strict'|'loose'} [helper] - A helper function which will be executed on every resolved query value, or the mode for the query engine.
* @param {'strict'|'loose'} [mode] - The mode for the query engine when performing the transform.
* @returns {object} An object containing resolved values mapped to object keys.
*/
toObject (map: object, helper?: Function): object {
const mapper = new X12TransactionMap(map, this, helper)
toObject (map: object, helper?: Function | X12QueryMode, mode?: X12QueryMode): object {
const mapper = new X12TransactionMap(map, this, helper as Function, mode)

return mapper.toObject()
}
Expand All @@ -91,8 +93,8 @@ export class X12Transaction {
edi += options.endOfLine
}

for (let i = 0; i < this.segments.length; i++) {
edi += this.segments[i].toString(options)
for (const segment of this.segments) {
edi += segment.toString(options)

if (options.format) {
edi += options.endOfLine
Expand Down
76 changes: 59 additions & 17 deletions src/X12TransactionMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { QuerySyntaxError } from './Errors'
import { X12Interchange } from './X12Interchange'
import { X12QueryEngine } from './X12QueryEngine'
import { X12QueryEngine, X12QueryMode } from './X12QueryEngine'
import { X12Transaction } from './X12Transaction'
import { TxEngine } from './X12SerializationOptions'
// @ts-expect-error
Expand All @@ -22,27 +22,70 @@ function isEmpty (val: any): boolean {
return false
}

/**
* @private
* @description Check if a string is query mode.
* @param {any} str - The string to check.
* @returns {boolean} Whether the string is a query mode.
*/
function isX12QueryMode (str: any): str is X12QueryMode {
return str === 'loose' || str === 'strict'
}

/**
* @private
* @description Check if a string is query mode.
* @param {any} str - The string to check.
* @returns {boolean} Whether the string is a query mode.
*/
function isTxEngine (str: any): str is TxEngine {
return str === 'internal' || str === 'liquidjs'
}

export class X12TransactionMap {
/**
* @description Factory for mapping transaction set data to javascript object map.
* @param {object} map - The javascript object containing keys and querys to resolve.
* @param {X12Transaction} [transaction] - A transaction set to map.
* @param {Function|'liquidjs'|'internal'} [helper] - A helper function which will be executed on every resolved query value, or a macro engine.
* @param {'liquidjs'|'internal'} [txEngine] - A macro engine to use; either 'internal' or 'liquidjs'; defaults to internal for backwords compatibility.
* @param {Function|'liquidjs'|'internal'|'strict'|'loose'} [helper] - A helper function which will be executed on every resolved query value, a macro engine, or mode.
* @param {'liquidjs'|'internal'|'strict'|'loose'} [txEngine] - A macro engine to use; either 'internal' or 'liquidjs'; defaults to internal for backwords compatibility, or the mode.
* @param {'strict'|'loose'} [mode='strict'] - The mode for transforming, passed to the query engine, and defaults to 'strict'; may be set to 'loose' for new behavior with missing elements in the dom.
*/
constructor (map: any, transaction?: X12Transaction, helper?: Function | TxEngine, txEngine?: TxEngine) {
constructor (map: any, transaction?: X12Transaction, mode?: X12QueryMode)
constructor (map: any, transaction?: X12Transaction, txEngine?: TxEngine)
constructor (map: any, transaction?: X12Transaction, txEngine?: TxEngine, mode?: X12QueryMode)
constructor (map: any, transaction?: X12Transaction, helper?: Function, mode?: X12QueryMode)
constructor (map: any, transaction?: X12Transaction, helper?: Function, txEngine?: TxEngine)
constructor (map: any, transaction?: X12Transaction, helper?: Function, txEngine?: TxEngine, mode?: X12QueryMode)
constructor (map: any, transaction?: X12Transaction, helper?: Function | TxEngine | X12QueryMode, txEngine?: TxEngine | X12QueryMode, mode?: X12QueryMode) {
this._map = map
this._transaction = transaction
this.helper = typeof helper === 'function' ? helper : this._helper
if (typeof helper === 'string' && typeof txEngine === 'undefined') {
txEngine = helper

if (typeof helper === 'string' && (typeof txEngine === 'undefined' || typeof mode === 'undefined')) {
if (isTxEngine(helper)) {
if (isX12QueryMode(txEngine)) {
mode = txEngine
}

txEngine = helper
} else if (isX12QueryMode(helper)) {
mode = helper
}
}

if (typeof txEngine === 'string' && typeof mode === 'undefined' && isX12QueryMode(txEngine)) {
mode = txEngine
}
this.txEngine = txEngine === undefined ? 'internal' : txEngine

this.txEngine = isTxEngine(txEngine) ? txEngine : 'internal'
this._mode = isX12QueryMode(mode) ? mode : 'strict'
}

protected _map: any
protected _transaction: X12Transaction
protected _object: any
protected _mode: X12QueryMode
helper: Function
txEngine: TxEngine

Expand Down Expand Up @@ -75,7 +118,7 @@ export class X12TransactionMap {

const clone = JSON.parse(JSON.stringify(map))
let clones: object[] = null
const engine = new X12QueryEngine(false)
const engine = new X12QueryEngine(false, this._mode)
const interchange = new X12Interchange()
interchange.setHeader([
'00',
Expand Down Expand Up @@ -149,7 +192,7 @@ export class X12TransactionMap {
} else if (result.value === null || Array.isArray(clones)) {
if (result.value !== null) {
clones.forEach((cloned: object[]) => {
cloned[key] = this.helper(key, result.value, map[key], callback)
cloned[key as unknown as number] = this.helper(key, result.value, map[key], callback)
})
} else {
if (!Array.isArray(clones)) {
Expand Down Expand Up @@ -379,8 +422,8 @@ export class X12TransactionMap {
lp.forEach(segment => {
const elements = []

for (let j = 0; j < segment.elements.length; j += 1) {
const resolved = resolveKey(segment.elements[j])
for (const segElement of segment.elements) {
const resolved = resolveKey(segElement)

if (Array.isArray(resolved)) {
elements.push(resolved[i])
Expand Down Expand Up @@ -429,14 +472,13 @@ export class X12TransactionMap {
let looper: any[] = []
let loop = false

for (let i = 0; i < map.header.length; i += 1) {
header.push(resolveKey(map.header[i]))
for (const headerElement of map.header) {
header.push(resolveKey(headerElement))
}

transaction.setHeader(header)

for (let i = 0; i < map.segments.length; i += 1) {
const segment = map.segments[i]
for (const segment of map.segments) {
const elements = []

if (segment.loopStart as boolean) {
Expand All @@ -447,8 +489,8 @@ export class X12TransactionMap {
}

if (!loop) {
for (let j = 0; j < segment.elements.length; j += 1) {
elements.push(resolveKey(segment.elements[j]))
for (const segElement of segment.elements) {
elements.push(resolveKey(segElement))
}

transaction.addSegment(segment.tag, elements)
Expand Down
Loading