diff --git a/__tests__/hl7.build.test.ts b/__tests__/hl7.build.test.ts index 0445a83..e37ff4b 100644 --- a/__tests__/hl7.build.test.ts +++ b/__tests__/hl7.build.test.ts @@ -526,8 +526,12 @@ describe('node hl7 client - builder tests', () => { describe('parse message and batch', () => { const hl7_string: string = "MSH|^~\\&|||||20081231||ADT^A01^ADT_A01|12345||2.7\rEVN||20081231" + const hl7_non_standard: string = "MSH:-+?*:field2:field3component1-field3component2:field4repeat1+field4repeat2:field5subcomponent1*field5subcomponent2:field6?R?" + const hl7_batch: string = "BHS|^~\\&|||||20231208\rMSH|^~\\&|||||20231208||ADT^A01^ADT_A01|CONTROL_ID||2.7\rEVN||20081231\rEVN||20081231\rBTS|1" + const hl7_batch_non_standard: string = "BHS:-+?*:::::20231208\rMSH|^~\\&|||||20231208||ADT^A01^ADT_A01|CONTROL_ID||2.7\rEVN||20081231\rEVN||20081231\rBTS|1" + // const hl7_batch_non_standard_mixed: string = "BHS:-+?*:::::20231208\rMSH|^~\\&|||||20231208||ADT^A01^ADT_A01|CONTROL_ID||2.7\rEVN||20081231\rEVN||20081231\rBTS|1" - test('...verify input', async () => { + test('...verify MSH input', async () => { const message = new Message({text: hl7_string}) @@ -541,11 +545,20 @@ describe('node hl7 client - builder tests', () => { expect(message.get('MSH.10').toString()).toBe("12345") expect(message.get('MSH.12').toString()).toBe("2.7") expect(message.get('EVN.2').toString()).toBe("20081231") + + let count: number = 0 + message.get("EVN").forEach((segment: Node): void => { + expect(segment.name).toBe( "EVN"); + count++; + }); + + expect(count).toBe(1) + }) test('...parse non standard delimiters defined in MSH header', async () => { - const message = new Message({text: "MSH:-+?*:field2:field3component1-field3component2:field4repeat1+field4repeat2:field5subcomponent1*field5subcomponent2:field6?R?"}); + const message = new Message({text: hl7_non_standard}); expect(message.get("MSH.3").toString()).toBe( "field2"); expect(message.get("MSH.4.2").toString()).toBe( "field3component2"); @@ -554,6 +567,41 @@ describe('node hl7 client - builder tests', () => { expect(message.get("MSH.6.1").toString()).toBe( "field5subcomponent1*field5subcomponent2"); expect(message.get("MSH.6.1.2").toString()).toBe( "field5subcomponent2"); expect(message.get("MSH.7").toString()).toBe( "field6+"); + }) + + test('...verify BHS input', async () => { + + const batch = new Batch({text: hl7_batch}) + + expect(batch.toString().substring(0, 8)).toBe("BHS|^~\\&") + expect(batch.get('BHS.1').toString()).toBe("|") + expect(batch.get('BHS.2').toString()).toBe("^~\\&") + }) + + test('...parse non standard delimiters defined in BHS header', async () => { + + const batch = new Batch({text: hl7_batch_non_standard}) + + expect(batch.toString().substring(0, 8)).toBe("BHS:-+?*") + expect(batch.get('BHS.1').toString()).toBe(":") + expect(batch.get('BHS.2').toString()).toBe("-+?*") + }) + + test('...verify BHS input ... 1 message should exist ... 2 EVN segments inside', async () => { + + const batch = new Batch({text: hl7_batch}) + const messages = batch.messages() + expect(messages.length).toBe(1) + + messages.forEach((message: Message): void => { + + let count: number = 0 + message.get("EVN").forEach((segment: Node): void => { + expect(segment.name).toBe( "EVN"); + count++; + }); + expect(count).toBe(2) + }) }) diff --git a/__tests__/hl7.parse.test.ts b/__tests__/hl7.parse.test.ts deleted file mode 100644 index a5c3193..0000000 --- a/__tests__/hl7.parse.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -import {Parser} from "../src"; -import { -/* rep_sample_hl7, */ - sample_bad_batch, - sample_bad_batch1, - /* sample_bad_file, - sample_bad_file1, - sample_bad_file2, - sample_bad_file3,*/ - sample_batch, - sample_batch1, - sample_batch2, - /* sample_file, - sample_file1, - sample_file2, - sample_file3, - sample_file4, - sample_file5, - sample_file6,*/ - sample_hl7 -} from "./__data__/sample_messages"; - - -describe('node hl7 client - parser tests', () => { - - let parser: Parser - - beforeEach(() => { - parser = new Parser() - }) - - describe('sanity test', () => { - - test.skip('error - no data passed', async () => { - try { - await parser.processRawData({data: ""}) - } catch (e: any) { - expect(e.message).toBe('data object not passed or not defined.'); - } - - }) - - test.skip('error - no data object', async () => { - try { - // @ts-expect-error no data object - await parser.processRawData() - } catch (e: any) { - expect(e.message).toBe('data object needs to be defined.'); - } - - }) - - }) - - describe('actual test', () => { - - describe('batch tests', () => { - - test.skip('...non batch HL7 message', async () => { - - for (const hl7_message of [ - sample_hl7 - ]) { - await parser.processRawData({data: hl7_message}) - const batch = await parser.getBatchProcess() - expect(batch).toBe(false); - } - }) - - test.skip('... batch HL7 messages', async () => { - - for (const hl7_message of [ - sample_batch, - sample_batch1, - sample_batch2 - ]) { - await parser.processRawData({data: hl7_message}) - const batch = await parser.getBatchProcess() - expect(batch).toBe(true); - } - }); - - test.skip('... bad batch HL7 messages', async () => { - - for (const hl7_message of [ - sample_bad_batch, - sample_bad_batch1 - ]) { - await parser.processRawData({data: hl7_message}) - // @ts-ignore - const batch = await parser.getBatchProcess() - } - - }) - - - }) - - }) - -}) \ No newline at end of file diff --git a/src/builder/batch.ts b/src/builder/batch.ts index 274d6ab..010f44d 100644 --- a/src/builder/batch.ts +++ b/src/builder/batch.ts @@ -1,5 +1,5 @@ import * as Util from '../utils' -import { HL7FatalError } from '../utils/exception' +import { HL7FatalError, HL7ParserError } from '../utils/exception' import { ClientBuilderBatchOptions, normalizedClientBatchBuilderOptions @@ -18,6 +18,9 @@ import { SegmentList } from './modules/segmentList' export class Batch extends RootBase { /** @internal **/ _opt: ReturnType + /** @internal */ + _lines?: string[] + /** @internal */ _messagesCount: number /** @@ -29,6 +32,10 @@ export class Batch extends RootBase { super(opt) this._opt = opt this._messagesCount = 0 + + if (typeof opt.text !== 'undefined' && opt.parsing === true && opt.text !== '') { + this._lines = this._splitBatch(opt.text).filter(line => line.includes('MSH')) + } } /** @@ -51,6 +58,22 @@ export class Batch extends RootBase { segment.set('1', this._messagesCount) } + /** + * Get Messages + * @since 1.0.0 + */ + messages (): Message[] { + if (typeof this._lines !== 'undefined' && typeof this._opt.newLine !== 'undefined') { + const message: Message[] = [] + const re = new RegExp(`${this._opt.newLine}$`, 'g') + for (let i = 0; i < this._lines.length; i++) { + message.push(new Message({ text: this._lines[i].replace(re, '') })) + } + return message + } + throw new HL7ParserError(500, 'No messages inside batch') + } + /** @internal */ read (path: string[]): Node { const segmentName = path.shift() as string @@ -118,4 +141,40 @@ export class Batch extends RootBase { } throw new HL7FatalError(500, 'Unable to process _getFirstSegment.') } + + /** @internal */ + private _getSegIndexes (names: string[], data: string, list: string[] = []): string[] { + for (let i = 0; i < names.length; i++) { + const regexp = new RegExp(`(\n|\r\n|^|\r)${names[i]}\\|`, 'g'); let m + while ((m = regexp.exec(data)) != null) { + const s = m[0] + if (s.includes('\r\n')) { + m.index = m.index + 2 + } else if (s.includes('\n')) { + m.index++ + } else if (s.includes('\r')) { + m.index++ + } + if (m.index !== null) { + list.push(m.index.toString()) + } + } + } + return list + } + + /** @internal */ + private _splitBatch (data: string, batch: string[] = []): string[] { + const getSegIndex = this._getSegIndexes(['FHS', 'BHS', 'MSH', 'BTS', 'FTS'], data) + getSegIndex.sort((a, b) => parseInt(a) - parseInt(b)) + for (let i = 0; i < getSegIndex.length; i++) { + const start = parseInt(getSegIndex[i]) + let end = parseInt(getSegIndex[i + 1]) + if (i + 1 === getSegIndex.length) { + end = data.length + } + batch.push(data.slice(start, end)) + } + return batch + } } diff --git a/src/builder/message.ts b/src/builder/message.ts index 8966d05..9d40338 100644 --- a/src/builder/message.ts +++ b/src/builder/message.ts @@ -158,7 +158,6 @@ export class Message extends RootBase { } } return undefined - // throw new HL7FatalError(500, 'Unable to process _getFirstSegment.') } /** @internal */ diff --git a/src/client/parser.ts b/src/client/parser.ts deleted file mode 100644 index 8b1b699..0000000 --- a/src/client/parser.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { HL7ParserError } from '../utils/exception' -import { ParserProcessRawData } from '../utils/normalize' -import { ParserPlan } from '../utils/parserPlan' - -/** Parser Class - * @since 1.0.0 */ -export class Parser { - /** @internal */ - _isBatchProcessing: boolean - /** @internal */ - _parsePlan?: ParserPlan - /** @internal */ - _regex = /MSH/gm - - constructor () { - this._isBatchProcessing = false - } - - /** Will return true/false to indicate to the user if we did a batch processing method. - * @since 1.0.0 */ - async getBatchProcess (): Promise { - return this._isBatchProcessing - } - - /** Process a raw HL7 message. - * Must be properly formatted, or we will return an error. - * @see ParserProcessRawData for HL7 message sample. - * @since 1.0.0 - * @param props - * @example - */ - async processRawData (props: ParserProcessRawData): Promise { - // make the data all one line - const data = props.data.trim() - - if (!data.startsWith('MSH') && - !data.startsWith('FHS') && - !data.startsWith('BHS')) { - this._throwError('message does not start as a proper hl7 message') - } - - // generate parse plan - this._parsePlan = new ParserPlan(data) - - // check to see if we need to do batch processing - this._isBatchProcessing = await this._isBatch(data) - - let lines: string[] | string = '' - if (this._isBatchProcessing) { - lines = await this._splitBatch(data) - } else { - /** noop **/ - } - console.log(lines) - - /* - } else { - // regular processing - this.emit('data.processing', data) - - // split up the lines. this is hardcoded to \n - const lines = data.split('\n').filter(line => line.includes('|')) - - for (let i = 0; i < lines.length; i++) { - const name = lines[i].substring(0, 3) - const content = lines[i].split(this._lineSplitter) - // @todo if MSH, get parser options for this HL7 message. use those when parsing data - this._results?.push({name: `${name}${this._dataSep}${i+1}`, data: new Segment(this, name, content,i), content: lines[i]}) - } - } */ - } - - /** @internal */ - private async _getSegIndexes (names: string[], data: string, list: string[] = []): Promise { - for (let i = 0; i < names.length; i++) { - const regexp = new RegExp(`(\n|\r\n|^|\r)${names[i]}\\|`, 'g'); let m - while ((m = regexp.exec(data)) != null) { - const s = m[0] - if (s.includes('\r\n')) { - m.index = m.index + 2 - } else if (s.includes('\n')) { - m.index++ - } else if (s.includes('\r')) { - m.index++ - } - if (m.index !== null) { - list.push(m.index.toString()) - } - } - } - return list - } - - /** @internal */ - private async _isBatch (data: string): Promise { - return (data.startsWith('FHS') || data.startsWith('BHS')) - } - - /** @internal */ - private _throwError (message: string): Error { - throw new HL7ParserError(500, message) - } - - /** @internal */ - private async _splitBatch (data: string, batch: string[] = []): Promise { - const getSegIndex = await this._getSegIndexes(['FHS', 'BHS', 'MSH', 'BTS', 'FTS'], data) - getSegIndex.sort((a, b) => parseInt(a) - parseInt(b)) - for (let i = 0; i < getSegIndex.length; i++) { - const start = parseInt(getSegIndex[i]) - let end = parseInt(getSegIndex[i + 1]) - if (i + 1 === getSegIndex.length) { - end = data.length - } - batch.push(data.slice(start, end)) - } - return batch - } -} diff --git a/src/index.ts b/src/index.ts index 8b7f39b..c8ec4d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,6 @@ import { Field } from './builder/modules/field' import { FieldRepetition } from './builder/modules/fieldRepetition' import { Listener } from './client/listener' import { Message } from './builder/message' -import { Parser } from './client/parser' import { Segment } from './builder/modules/segment' import { SegmentList } from './builder/modules/segmentList' import { HL7_2_7 } from './specification/2.7.js' @@ -15,7 +14,7 @@ import { Batch } from './builder/batch' import { ParserPlan } from './utils/parserPlan' export default Client -export { Client, Listener, Parser, ParserPlan, Batch, Message, Delimiters, Segment, SegmentList, Component, SubComponent, Field, FieldRepetition } +export { Client, Listener, ParserPlan, Batch, Message, Delimiters, Segment, SegmentList, Component, SubComponent, Field, FieldRepetition } export * from './utils/index' /** HL7 Specs **/ diff --git a/src/utils/normalize.ts b/src/utils/normalize.ts index ffa11fa..2133eda 100644 --- a/src/utils/normalize.ts +++ b/src/utils/normalize.ts @@ -228,13 +228,9 @@ export function normalizedClientMessageBuilderOptions (raw?: ClientBuilderMessag } export function normalizedClientBatchBuilderOptions (raw?: ClientBuilderBatchOptions): ClientBuilderBatchOptions { - const props = { ...DEFAULT_CLIENT_BUILDER_OPTS, ...raw } + const props: ClientBuilderBatchOptions = { ...DEFAULT_CLIENT_BUILDER_OPTS, ...raw } - if (typeof props.batchHeader === 'undefined' && props.text !== '') { - throw new Error('batchHeader must be set if no HL7 message is being passed.') - } else if (typeof props.batchHeader !== 'undefined' && props.text !== '') { - throw new Error('batchHeader and mshHeader must be set if no HL7 message is being passed.') - } else if (props.text !== '' && props.text.slice(0, 3) !== 'BHS') { + if (typeof props.batchHeader === 'undefined' && typeof props.text !== 'undefined' && props.text !== '' && props.text.slice(0, 3) !== 'BHS') { throw new Error('text must begin with the BHS segment.') } @@ -244,8 +240,16 @@ export function normalizedClientBatchBuilderOptions (raw?: ClientBuilderBatchOpt if (props.text === '') { props.text = `BHS${props.separatorField}${props.separatorComponent}${props.separatorRepetition}${props.separatorEscape}${props.separatorSubComponent}` - } else { + } else if (typeof props.text !== 'undefined') { + const plan: ParserPlan = new ParserPlan(props.text.slice(3, 8)) props.parsing = true + // check to make sure that we set the correct properties + props.newLine = props.text.includes('\r') ? '\r' : '\n' + props.separatorField = plan.separatorField + props.separatorComponent = plan.separatorComponent + props.separatorRepetition = plan.separatorRepetition + props.separatorEscape = plan.separatorEscape + props.separatorSubComponent = plan.separatorSubComponent } return props