@rethinkhealth/hl7v2-parser 0.11.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,573 +1,561 @@
1
- // src/parser.ts
2
- import { DEFAULT_DELIMITERS as DEFAULT_DELIMITERS2 } from "@rethinkhealth/hl7v2-utils";
3
-
4
- // src/preprocessor.ts
5
- import { DEFAULT_DELIMITERS } from "@rethinkhealth/hl7v2-utils";
6
- var stripBOM = (ctx) => {
7
- if (ctx.input.codePointAt(0) === 65279) {
8
- ctx.input = ctx.input.slice(1);
9
- }
10
- return ctx;
1
+ import { DEFAULT_DELIMITERS, isEmptyNode } from "@rethinkhealth/hl7v2-utils";
2
+ //#region src/preprocessor.ts
3
+ /**
4
+ * Step: strip UTF-8 BOM.
5
+ */
6
+ const stripBOM = (ctx) => {
7
+ if (ctx.input.codePointAt(0) === 65279) ctx.input = ctx.input.slice(1);
8
+ return ctx;
11
9
  };
12
- var normalizeNewlines = (ctx) => {
13
- ctx.input = ctx.input.replaceAll(/\r?\n/g, ctx.delimiters.segment);
14
- return ctx;
10
+ /**
11
+ * Step: normalize newlines to the default delimiters.
12
+ */
13
+ const normalizeNewlines = (ctx) => {
14
+ ctx.input = ctx.input.replaceAll(/\r?\n/g, ctx.delimiters.segment);
15
+ return ctx;
15
16
  };
16
- var detectDelimiters = (ctx) => {
17
- if (ctx.input.startsWith("MSH")) {
18
- const field = ctx.input.charAt(3) || DEFAULT_DELIMITERS.field;
19
- const enc = ctx.input.slice(4, 8);
20
- const component = enc.charAt(0) || DEFAULT_DELIMITERS.component;
21
- const repetition = enc.charAt(1) || DEFAULT_DELIMITERS.repetition;
22
- const _escape = enc.charAt(2) || DEFAULT_DELIMITERS.escape;
23
- const subcomponent = enc.charAt(3) || DEFAULT_DELIMITERS.subcomponent;
24
- const { segment } = DEFAULT_DELIMITERS;
25
- ctx.delimiters = {
26
- component,
27
- escape: _escape,
28
- field,
29
- repetition,
30
- segment,
31
- subcomponent
32
- };
33
- }
34
- return ctx;
17
+ /**
18
+ * Step: auto-detect delimiters from MSH.
19
+ */
20
+ const detectDelimiters = (ctx) => {
21
+ if (ctx.input.startsWith("MSH")) {
22
+ const field = ctx.input.charAt(3) || DEFAULT_DELIMITERS.field;
23
+ const enc = ctx.input.slice(4, 8);
24
+ const component = enc.charAt(0) || DEFAULT_DELIMITERS.component;
25
+ const repetition = enc.charAt(1) || DEFAULT_DELIMITERS.repetition;
26
+ const _escape = enc.charAt(2) || DEFAULT_DELIMITERS.escape;
27
+ const subcomponent = enc.charAt(3) || DEFAULT_DELIMITERS.subcomponent;
28
+ const { segment } = DEFAULT_DELIMITERS;
29
+ ctx.delimiters = {
30
+ component,
31
+ escape: _escape,
32
+ field,
33
+ repetition,
34
+ segment,
35
+ subcomponent
36
+ };
37
+ }
38
+ return ctx;
35
39
  };
36
- var defaultPreprocessors = [
37
- stripBOM,
38
- normalizeNewlines,
39
- detectDelimiters
40
+ /**
41
+ * Default preprocessing pipeline.
42
+ */
43
+ const defaultPreprocessors = [
44
+ stripBOM,
45
+ normalizeNewlines,
46
+ detectDelimiters
40
47
  ];
48
+ /**
49
+ * Run the pipeline to initialize ParserContext.
50
+ */
41
51
  function runPreprocessors(ctx, steps) {
42
- for (const step of steps) {
43
- ctx = step(ctx);
44
- }
45
- return ctx;
52
+ for (const step of steps) ctx = step(ctx);
53
+ return ctx;
46
54
  }
47
-
48
- // src/processor.ts
49
- import { isEmptyNode } from "@rethinkhealth/hl7v2-utils";
55
+ //#endregion
56
+ //#region src/processor.ts
57
+ /**
58
+ * Check if a field has structural content beyond a single empty value.
59
+ * A field with multiple repetitions, multiple components, or multiple
60
+ * subcomponents has structural content even if all values are empty.
61
+ * This is used to prevent dropping trailing fields like |^| or |&|
62
+ * that have meaningful delimiter structure.
63
+ */
50
64
  function hasStructuralContent(field) {
51
- if (field.children.length > 1) {
52
- return true;
53
- }
54
- const rep = field.children[0];
55
- if (rep && rep.children.length > 1) {
56
- return true;
57
- }
58
- const comp = rep?.children[0];
59
- return comp !== void 0 && comp.children.length > 1;
65
+ if (field.children.length > 1) return true;
66
+ const rep = field.children[0];
67
+ if (rep && rep.children.length > 1) return true;
68
+ const comp = rep?.children[0];
69
+ return comp !== void 0 && comp.children.length > 1;
60
70
  }
61
71
  function createSubcomponent(start) {
62
- return {
63
- position: { end: start, start },
64
- type: "subcomponent",
65
- value: ""
66
- };
72
+ return {
73
+ position: {
74
+ end: start,
75
+ start
76
+ },
77
+ type: "subcomponent",
78
+ value: ""
79
+ };
67
80
  }
68
81
  function createComponent(start, mode) {
69
- return {
70
- children: mode === "empty" ? [] : [createSubcomponent(start)],
71
- position: { end: start, start },
72
- type: "component"
73
- };
82
+ return {
83
+ children: mode === "empty" ? [] : [createSubcomponent(start)],
84
+ position: {
85
+ end: start,
86
+ start
87
+ },
88
+ type: "component"
89
+ };
74
90
  }
75
91
  function createFieldRepetition(start, mode) {
76
- return {
77
- children: mode === "empty" ? [] : [createComponent(start, mode)],
78
- position: { end: start, start },
79
- type: "field-repetition"
80
- };
92
+ return {
93
+ children: mode === "empty" ? [] : [createComponent(start, mode)],
94
+ position: {
95
+ end: start,
96
+ start
97
+ },
98
+ type: "field-repetition"
99
+ };
81
100
  }
82
101
  function createField(start, mode) {
83
- return {
84
- children: mode === "empty" ? [] : [createFieldRepetition(start, mode)],
85
- position: { end: start, start },
86
- type: "field"
87
- };
102
+ return {
103
+ children: mode === "empty" ? [] : [createFieldRepetition(start, mode)],
104
+ position: {
105
+ end: start,
106
+ start
107
+ },
108
+ type: "field"
109
+ };
88
110
  }
89
111
  function createParserCore(ctx) {
90
- const mode = ctx.emptyMode;
91
- const root = {
92
- children: [],
93
- data: {
94
- delimiters: ctx.delimiters
95
- },
96
- type: "root"
97
- };
98
- let seg = null;
99
- let field = null;
100
- let rep = null;
101
- let comp = null;
102
- let currentSub = null;
103
- let segmentHasContent = false;
104
- let lastContentEnd = null;
105
- let documentEnd = null;
106
- let expectingSegmentName = true;
107
- let justSetSegmentName = false;
108
- let rootStartSet = false;
109
- const resetState = () => {
110
- seg = null;
111
- field = null;
112
- rep = null;
113
- comp = null;
114
- currentSub = null;
115
- segmentHasContent = false;
116
- lastContentEnd = null;
117
- expectingSegmentName = true;
118
- };
119
- const openSegment = (name, position) => {
120
- seg = {
121
- children: [],
122
- name,
123
- position: { end: position.end, start: position.start },
124
- type: "segment"
125
- };
126
- root.children.push(seg);
127
- field = null;
128
- rep = null;
129
- comp = null;
130
- currentSub = null;
131
- segmentHasContent = false;
132
- justSetSegmentName = true;
133
- expectingSegmentName = false;
134
- };
135
- const openField = (start) => {
136
- if (!seg) {
137
- throw new Error(
138
- "Cannot open field without an active segment. TEXT token with segment name must precede field content."
139
- );
140
- }
141
- field = createField(start, mode);
142
- seg.children.push(field);
143
- if (mode === "empty") {
144
- rep = null;
145
- comp = null;
146
- currentSub = null;
147
- } else {
148
- rep = field.children[0] ?? null;
149
- comp = rep?.children[0] ?? null;
150
- currentSub = comp?.children[0] ?? null;
151
- }
152
- segmentHasContent = true;
153
- };
154
- const openRepetition = (start) => {
155
- if (!field) {
156
- openField(start);
157
- if (mode === "empty") {
158
- const emptyRep = createFieldRepetition(start, mode);
159
- field.children.push(emptyRep);
160
- }
161
- }
162
- if (mode === "empty" && field.children.length === 0) {
163
- const emptyRep = createFieldRepetition(start, mode);
164
- field.children.push(emptyRep);
165
- }
166
- rep = createFieldRepetition(start, mode);
167
- field.children.push(rep);
168
- if (mode === "empty") {
169
- comp = null;
170
- currentSub = null;
171
- } else {
172
- comp = rep.children[0] ?? null;
173
- currentSub = comp?.children[0] ?? null;
174
- }
175
- segmentHasContent = true;
176
- };
177
- const ensureComponent = (start) => {
178
- if (!field) {
179
- openField(start);
180
- }
181
- if (!rep) {
182
- rep = createFieldRepetition(start, mode);
183
- field.children.push(rep);
184
- }
185
- if (!comp) {
186
- comp = createComponent(start, mode);
187
- rep.children.push(comp);
188
- }
189
- segmentHasContent = true;
190
- };
191
- const openComponent = (start) => {
192
- if (!field) {
193
- openField(start);
194
- if (mode === "empty") {
195
- rep = createFieldRepetition(start, mode);
196
- field.children.push(rep);
197
- const emptyComp = createComponent(start, mode);
198
- rep.children.push(emptyComp);
199
- }
200
- }
201
- if (!rep) {
202
- rep = createFieldRepetition(start, mode);
203
- field.children.push(rep);
204
- if (mode === "empty") {
205
- const emptyComp = createComponent(start, mode);
206
- rep.children.push(emptyComp);
207
- }
208
- } else if (mode === "empty" && rep.children.length === 0) {
209
- const emptyComp = createComponent(start, mode);
210
- rep.children.push(emptyComp);
211
- }
212
- comp = createComponent(start, mode);
213
- rep.children.push(comp);
214
- currentSub = mode === "empty" ? null : comp.children[0] ?? null;
215
- segmentHasContent = true;
216
- };
217
- const ensureForText = (start) => {
218
- if (!field) {
219
- openField(start);
220
- if (mode !== "empty") {
221
- return;
222
- }
223
- }
224
- if (!rep) {
225
- if (mode !== "empty") {
226
- openRepetition(start);
227
- return;
228
- }
229
- rep = createFieldRepetition(start, mode);
230
- field.children.push(rep);
231
- }
232
- if (!comp) {
233
- if (mode !== "empty") {
234
- openComponent(start);
235
- return;
236
- }
237
- comp = createComponent(start, mode);
238
- rep.children.push(comp);
239
- }
240
- if (!currentSub) {
241
- currentSub = createSubcomponent(start);
242
- comp.children.push(currentSub);
243
- segmentHasContent = true;
244
- }
245
- };
246
- const updatePositionsToEnd = (endPos) => {
247
- if (currentSub?.position) {
248
- currentSub.position.end = endPos;
249
- }
250
- if (comp?.position) {
251
- comp.position.end = endPos;
252
- }
253
- if (rep?.position) {
254
- rep.position.end = endPos;
255
- }
256
- if (field?.position) {
257
- field.position.end = endPos;
258
- }
259
- if (seg?.position) {
260
- seg.position.end = endPos;
261
- }
262
- };
263
- const processToken = (tok) => {
264
- switch (tok.type) {
265
- case "SEGMENT_END": {
266
- const endPos = lastContentEnd || tok.position.start;
267
- updatePositionsToEnd(endPos);
268
- dropTrailingEmptyFieldIfPresent();
269
- resetState();
270
- return;
271
- }
272
- case "FIELD_DELIM": {
273
- lastContentEnd = tok.position.end;
274
- documentEnd = tok.position.end;
275
- if (justSetSegmentName) {
276
- justSetSegmentName = false;
277
- return;
278
- }
279
- if (!field) {
280
- openField(tok.position.start);
281
- }
282
- openField(tok.position.end);
283
- return;
284
- }
285
- case "REPETITION_DELIM": {
286
- lastContentEnd = tok.position.end;
287
- documentEnd = tok.position.end;
288
- if (!field) {
289
- openField(tok.position.start);
290
- }
291
- openRepetition(tok.position.end);
292
- return;
293
- }
294
- case "COMPONENT_DELIM": {
295
- lastContentEnd = tok.position.end;
296
- documentEnd = tok.position.end;
297
- openComponent(tok.position.end);
298
- return;
299
- }
300
- case "SUBCOMP_DELIM": {
301
- lastContentEnd = tok.position.end;
302
- documentEnd = tok.position.end;
303
- ensureComponent(tok.position.start);
304
- if (mode === "empty" && comp.children.length === 0) {
305
- const emptySub = createSubcomponent(tok.position.start);
306
- comp.children.push(emptySub);
307
- }
308
- currentSub = createSubcomponent(tok.position.end);
309
- comp?.children.push(currentSub);
310
- segmentHasContent = true;
311
- return;
312
- }
313
- case "TEXT": {
314
- const val = tok.value ?? "";
315
- if (!rootStartSet) {
316
- root.position = {
317
- end: tok.position.start,
318
- start: tok.position.start
319
- };
320
- rootStartSet = true;
321
- }
322
- if (expectingSegmentName) {
323
- openSegment(val, tok.position);
324
- lastContentEnd = tok.position.end;
325
- documentEnd = tok.position.end;
326
- return;
327
- }
328
- ensureForText(tok.position.start);
329
- if (currentSub) {
330
- currentSub.value += val;
331
- }
332
- updatePositionsToEnd(tok.position.end);
333
- lastContentEnd = tok.position.end;
334
- documentEnd = tok.position.end;
335
- segmentHasContent = true;
336
- return;
337
- }
338
- default: {
339
- throw new Error(`Unexpected token type: ${tok.type}`);
340
- }
341
- }
342
- };
343
- const finalize = () => {
344
- if (lastContentEnd && seg) {
345
- updatePositionsToEnd(lastContentEnd);
346
- }
347
- dropTrailingEmptyFieldIfPresent();
348
- if (seg && !segmentHasContent) {
349
- root.children.pop();
350
- }
351
- if (root.position && documentEnd) {
352
- root.position.end = documentEnd;
353
- } else if (!root.position) {
354
- root.position = {
355
- end: { column: 1, line: 1, offset: 0 },
356
- start: { column: 1, line: 1, offset: 0 }
357
- };
358
- }
359
- return root;
360
- };
361
- function dropTrailingEmptyFieldIfPresent() {
362
- if (!seg || seg.children.length === 0) {
363
- return;
364
- }
365
- const lastChild = seg.children.at(-1);
366
- if (lastChild?.type !== "field") {
367
- return;
368
- }
369
- const lastField = lastChild;
370
- if (isEmptyNode(lastField) && !hasStructuralContent(lastField)) {
371
- seg.children.pop();
372
- }
373
- }
374
- return { finalize, processToken, root };
112
+ const mode = ctx.emptyMode;
113
+ const root = {
114
+ children: [],
115
+ type: "root"
116
+ };
117
+ let seg = null;
118
+ let field = null;
119
+ let rep = null;
120
+ let comp = null;
121
+ let currentSub = null;
122
+ let segmentHasContent = false;
123
+ let lastContentEnd = null;
124
+ let documentEnd = null;
125
+ let expectingSegmentName = true;
126
+ let justSetSegmentName = false;
127
+ let rootStartSet = false;
128
+ const resetState = () => {
129
+ seg = null;
130
+ field = null;
131
+ rep = null;
132
+ comp = null;
133
+ currentSub = null;
134
+ segmentHasContent = false;
135
+ lastContentEnd = null;
136
+ expectingSegmentName = true;
137
+ };
138
+ const openSegment = (name, position) => {
139
+ seg = {
140
+ children: [],
141
+ name,
142
+ position: {
143
+ end: position.end,
144
+ start: position.start
145
+ },
146
+ type: "segment"
147
+ };
148
+ root.children.push(seg);
149
+ field = null;
150
+ rep = null;
151
+ comp = null;
152
+ currentSub = null;
153
+ segmentHasContent = false;
154
+ justSetSegmentName = true;
155
+ expectingSegmentName = false;
156
+ };
157
+ const openField = (start) => {
158
+ if (!seg) throw new Error("Cannot open field without an active segment. TEXT token with segment name must precede field content.");
159
+ field = createField(start, mode);
160
+ seg.children.push(field);
161
+ if (mode === "empty") {
162
+ rep = null;
163
+ comp = null;
164
+ currentSub = null;
165
+ } else {
166
+ rep = field.children[0] ?? null;
167
+ comp = rep?.children[0] ?? null;
168
+ currentSub = comp?.children[0] ?? null;
169
+ }
170
+ segmentHasContent = true;
171
+ };
172
+ const openRepetition = (start) => {
173
+ if (!field) {
174
+ openField(start);
175
+ if (mode === "empty") {
176
+ const emptyRep = createFieldRepetition(start, mode);
177
+ field.children.push(emptyRep);
178
+ }
179
+ }
180
+ if (mode === "empty" && field.children.length === 0) {
181
+ const emptyRep = createFieldRepetition(start, mode);
182
+ field.children.push(emptyRep);
183
+ }
184
+ rep = createFieldRepetition(start, mode);
185
+ field.children.push(rep);
186
+ if (mode === "empty") {
187
+ comp = null;
188
+ currentSub = null;
189
+ } else {
190
+ comp = rep.children[0] ?? null;
191
+ currentSub = comp?.children[0] ?? null;
192
+ }
193
+ segmentHasContent = true;
194
+ };
195
+ const ensureComponent = (start) => {
196
+ if (!field) openField(start);
197
+ if (!rep) {
198
+ rep = createFieldRepetition(start, mode);
199
+ field.children.push(rep);
200
+ }
201
+ if (!comp) {
202
+ comp = createComponent(start, mode);
203
+ rep.children.push(comp);
204
+ }
205
+ segmentHasContent = true;
206
+ };
207
+ const openComponent = (start) => {
208
+ if (!field) {
209
+ openField(start);
210
+ if (mode === "empty") {
211
+ rep = createFieldRepetition(start, mode);
212
+ field.children.push(rep);
213
+ const emptyComp = createComponent(start, mode);
214
+ rep.children.push(emptyComp);
215
+ }
216
+ }
217
+ if (!rep) {
218
+ rep = createFieldRepetition(start, mode);
219
+ field.children.push(rep);
220
+ if (mode === "empty") {
221
+ const emptyComp = createComponent(start, mode);
222
+ rep.children.push(emptyComp);
223
+ }
224
+ } else if (mode === "empty" && rep.children.length === 0) {
225
+ const emptyComp = createComponent(start, mode);
226
+ rep.children.push(emptyComp);
227
+ }
228
+ comp = createComponent(start, mode);
229
+ rep.children.push(comp);
230
+ currentSub = mode === "empty" ? null : comp.children[0] ?? null;
231
+ segmentHasContent = true;
232
+ };
233
+ const ensureForText = (start) => {
234
+ if (!field) {
235
+ openField(start);
236
+ if (mode !== "empty") return;
237
+ }
238
+ if (!rep) {
239
+ if (mode !== "empty") {
240
+ openRepetition(start);
241
+ return;
242
+ }
243
+ rep = createFieldRepetition(start, mode);
244
+ field.children.push(rep);
245
+ }
246
+ if (!comp) {
247
+ if (mode !== "empty") {
248
+ openComponent(start);
249
+ return;
250
+ }
251
+ comp = createComponent(start, mode);
252
+ rep.children.push(comp);
253
+ }
254
+ if (!currentSub) {
255
+ currentSub = createSubcomponent(start);
256
+ comp.children.push(currentSub);
257
+ segmentHasContent = true;
258
+ }
259
+ };
260
+ const updatePositionsToEnd = (endPos) => {
261
+ if (currentSub?.position) currentSub.position.end = endPos;
262
+ if (comp?.position) comp.position.end = endPos;
263
+ if (rep?.position) rep.position.end = endPos;
264
+ if (field?.position) field.position.end = endPos;
265
+ if (seg?.position) seg.position.end = endPos;
266
+ };
267
+ const processToken = (tok) => {
268
+ switch (tok.type) {
269
+ case "SEGMENT_END":
270
+ updatePositionsToEnd(lastContentEnd || tok.position.start);
271
+ dropTrailingEmptyFieldIfPresent();
272
+ resetState();
273
+ return;
274
+ case "FIELD_DELIM":
275
+ lastContentEnd = tok.position.end;
276
+ documentEnd = tok.position.end;
277
+ if (justSetSegmentName) {
278
+ justSetSegmentName = false;
279
+ return;
280
+ }
281
+ if (!field) openField(tok.position.start);
282
+ openField(tok.position.end);
283
+ return;
284
+ case "REPETITION_DELIM":
285
+ lastContentEnd = tok.position.end;
286
+ documentEnd = tok.position.end;
287
+ if (!field) openField(tok.position.start);
288
+ openRepetition(tok.position.end);
289
+ return;
290
+ case "COMPONENT_DELIM":
291
+ lastContentEnd = tok.position.end;
292
+ documentEnd = tok.position.end;
293
+ openComponent(tok.position.end);
294
+ return;
295
+ case "SUBCOMP_DELIM":
296
+ lastContentEnd = tok.position.end;
297
+ documentEnd = tok.position.end;
298
+ ensureComponent(tok.position.start);
299
+ if (mode === "empty" && comp.children.length === 0) {
300
+ const emptySub = createSubcomponent(tok.position.start);
301
+ comp.children.push(emptySub);
302
+ }
303
+ currentSub = createSubcomponent(tok.position.end);
304
+ comp?.children.push(currentSub);
305
+ segmentHasContent = true;
306
+ return;
307
+ case "TEXT": {
308
+ const val = tok.value ?? "";
309
+ if (!rootStartSet) {
310
+ root.position = {
311
+ end: tok.position.start,
312
+ start: tok.position.start
313
+ };
314
+ rootStartSet = true;
315
+ }
316
+ if (expectingSegmentName) {
317
+ openSegment(val, tok.position);
318
+ lastContentEnd = tok.position.end;
319
+ documentEnd = tok.position.end;
320
+ return;
321
+ }
322
+ ensureForText(tok.position.start);
323
+ if (currentSub) currentSub.value += val;
324
+ updatePositionsToEnd(tok.position.end);
325
+ lastContentEnd = tok.position.end;
326
+ documentEnd = tok.position.end;
327
+ segmentHasContent = true;
328
+ return;
329
+ }
330
+ default: throw new Error(`Unexpected token type: ${tok.type}`);
331
+ }
332
+ };
333
+ const finalize = () => {
334
+ if (lastContentEnd && seg) updatePositionsToEnd(lastContentEnd);
335
+ dropTrailingEmptyFieldIfPresent();
336
+ if (seg && !segmentHasContent) root.children.pop();
337
+ if (root.position && documentEnd) root.position.end = documentEnd;
338
+ else if (!root.position) root.position = {
339
+ end: {
340
+ column: 1,
341
+ line: 1,
342
+ offset: 0
343
+ },
344
+ start: {
345
+ column: 1,
346
+ line: 1,
347
+ offset: 0
348
+ }
349
+ };
350
+ return root;
351
+ };
352
+ function dropTrailingEmptyFieldIfPresent() {
353
+ if (!seg || seg.children.length === 0) return;
354
+ const lastChild = seg.children.at(-1);
355
+ if (lastChild?.type !== "field") return;
356
+ const lastField = lastChild;
357
+ if (isEmptyNode(lastField) && !hasStructuralContent(lastField)) seg.children.pop();
358
+ }
359
+ return {
360
+ finalize,
361
+ processToken,
362
+ root
363
+ };
375
364
  }
376
365
  function parseHL7v2FromIterator(tokens, ctx) {
377
- const core = createParserCore(ctx);
378
- for (const tok of tokens) {
379
- core.processToken(tok);
380
- }
381
- return core.finalize();
366
+ const core = createParserCore(ctx);
367
+ for (const tok of tokens) core.processToken(tok);
368
+ return core.finalize();
382
369
  }
383
-
384
- // src/tokenizer.ts
385
- var MSH_SEGMENT_START = 0;
386
- var MSH_SEGMENT_END = 3;
387
- var MSH_FIELD_SEPERATOR_START = 3;
388
- var MSH_FIELD_SEPERATOR_END = 4;
389
- var MSH_FIELD_DELIMITER_START = 4;
390
- var MSH_FIELD_DELIMITER_END = 8;
370
+ //#endregion
371
+ //#region src/tokenizer.ts
372
+ const MSH_SEGMENT_START = 0;
373
+ const MSH_SEGMENT_END = 3;
374
+ const MSH_FIELD_SEPERATOR_START = 3;
375
+ const MSH_FIELD_SEPERATOR_END = 4;
376
+ const MSH_FIELD_DELIMITER_START = 4;
377
+ const MSH_FIELD_DELIMITER_END = 8;
391
378
  var HL7v2Tokenizer = class {
392
- input = "";
393
- i = 0;
394
- line = 1;
395
- col = 1;
396
- delims;
397
- // Only-run-once MSH bootstrap at the start of the file
398
- didMshBootstrap = false;
399
- pendingBootstrap = null;
400
- // queue to emit TEXT('MSH'), FIELD_DELIM, TEXT('^~\\&')
401
- reset(ctx) {
402
- this.input = ctx.input;
403
- this.delims = ctx.delimiters;
404
- this.i = 0;
405
- this.line = 1;
406
- this.col = 1;
407
- this.didMshBootstrap = false;
408
- this.pendingBootstrap = null;
409
- if (this.input.startsWith("MSH")) {
410
- const msh = this._slice(MSH_SEGMENT_START, MSH_SEGMENT_END);
411
- const msh1 = this._slice(
412
- MSH_FIELD_SEPERATOR_START,
413
- MSH_FIELD_SEPERATOR_END
414
- );
415
- const msh2 = this._slice(
416
- MSH_FIELD_DELIMITER_START,
417
- MSH_FIELD_DELIMITER_END
418
- );
419
- this.pendingBootstrap = [
420
- { advance: msh.length, kind: "TEXT", value: msh },
421
- { advance: 0, kind: "FIELD_DELIM" },
422
- { advance: msh1.length, kind: "TEXT", value: msh1 },
423
- { advance: 0, kind: "FIELD_DELIM" },
424
- { advance: msh2.length, kind: "TEXT", value: msh2 }
425
- ];
426
- }
427
- }
428
- // oxlint-disable-next-line complexity
429
- next() {
430
- const s = this.input;
431
- const n = s.length;
432
- if (this.i >= n && !this.pendingBootstrap?.length) {
433
- return null;
434
- }
435
- const start = { column: this.col, line: this.line, offset: this.i };
436
- if (!this.didMshBootstrap && this.pendingBootstrap) {
437
- const step = this.pendingBootstrap.shift();
438
- if (step) {
439
- if (step.kind === "TEXT") {
440
- const take = Math.min(step.advance, n - this.i);
441
- const out = step.value.slice(0, take);
442
- this._fastAdvance(out);
443
- if (this.pendingBootstrap.length === 0) {
444
- this.pendingBootstrap = null;
445
- this.didMshBootstrap = true;
446
- }
447
- return this._tok("TEXT", out, start);
448
- }
449
- this._advance(Math.min(step.advance, n - this.i));
450
- if (this.pendingBootstrap.length === 0) {
451
- this.pendingBootstrap = null;
452
- this.didMshBootstrap = true;
453
- }
454
- return this._tok("FIELD_DELIM", void 0, start);
455
- }
456
- }
457
- if (this.i >= n) {
458
- return null;
459
- }
460
- const ch = s.charAt(this.i);
461
- if (ch === this.delims.segment) {
462
- this._advance(1, true);
463
- return this._tok("SEGMENT_END", void 0, start);
464
- }
465
- if (ch === this.delims.field) {
466
- this._advance(1);
467
- return this._tok("FIELD_DELIM", void 0, start);
468
- }
469
- if (ch === this.delims.repetition) {
470
- this._advance(1);
471
- return this._tok("REPETITION_DELIM", void 0, start);
472
- }
473
- if (ch === this.delims.component) {
474
- this._advance(1);
475
- return this._tok("COMPONENT_DELIM", void 0, start);
476
- }
477
- if (ch === this.delims.subcomponent) {
478
- this._advance(1);
479
- return this._tok("SUBCOMP_DELIM", void 0, start);
480
- }
481
- let j = this.i;
482
- const cmp = this.delims.component, fld = this.delims.field, rep = this.delims.repetition, seg = this.delims.segment, sub = this.delims.subcomponent;
483
- while (j < s.length) {
484
- const c = s.charAt(j);
485
- if (c === seg || c === fld || c === rep || c === cmp || c === sub) {
486
- break;
487
- }
488
- j += 1;
489
- }
490
- const val = s.slice(this.i, j);
491
- this._fastAdvance(val);
492
- return this._tok("TEXT", val, start);
493
- }
494
- // ---- helpers ----
495
- _slice(start, end) {
496
- return this.input.slice(start, Math.min(end, this.input.length));
497
- }
498
- _advance(n, isNewline = false) {
499
- this.i += n;
500
- if (isNewline) {
501
- this.line += 1;
502
- this.col = 1;
503
- } else {
504
- this.col += n;
505
- }
506
- }
507
- _fastAdvance(chunk) {
508
- const parts = chunk.split(this.delims.segment);
509
- if (parts.length > 1) {
510
- this.line += parts.length - 1;
511
- this.col = (parts.at(-1)?.length ?? 0) + 1;
512
- } else {
513
- this.col += chunk.length;
514
- }
515
- this.i += chunk.length;
516
- }
517
- _tok(type, value, start) {
518
- const end = { column: this.col, line: this.line, offset: this.i };
519
- return { position: { end, start }, type, value };
520
- }
521
- // Iterable protocol (sync)
522
- [Symbol.iterator]() {
523
- return {
524
- next: () => {
525
- const t = this.next();
526
- if (!t) {
527
- return { done: true, value: void 0 };
528
- }
529
- return { done: false, value: t };
530
- }
531
- };
532
- }
379
+ input = "";
380
+ i = 0;
381
+ line = 1;
382
+ col = 1;
383
+ delims;
384
+ didMshBootstrap = false;
385
+ pendingBootstrap = null;
386
+ reset(ctx) {
387
+ this.input = ctx.input;
388
+ this.delims = ctx.delimiters;
389
+ this.i = 0;
390
+ this.line = 1;
391
+ this.col = 1;
392
+ this.didMshBootstrap = false;
393
+ this.pendingBootstrap = null;
394
+ if (this.input.startsWith("MSH")) {
395
+ const msh = this._slice(MSH_SEGMENT_START, MSH_SEGMENT_END);
396
+ const msh1 = this._slice(MSH_FIELD_SEPERATOR_START, MSH_FIELD_SEPERATOR_END);
397
+ const msh2 = this._slice(MSH_FIELD_DELIMITER_START, MSH_FIELD_DELIMITER_END);
398
+ this.pendingBootstrap = [
399
+ {
400
+ advance: msh.length,
401
+ kind: "TEXT",
402
+ value: msh
403
+ },
404
+ {
405
+ advance: 0,
406
+ kind: "FIELD_DELIM"
407
+ },
408
+ {
409
+ advance: msh1.length,
410
+ kind: "TEXT",
411
+ value: msh1
412
+ },
413
+ {
414
+ advance: 0,
415
+ kind: "FIELD_DELIM"
416
+ },
417
+ {
418
+ advance: msh2.length,
419
+ kind: "TEXT",
420
+ value: msh2
421
+ }
422
+ ];
423
+ }
424
+ }
425
+ next() {
426
+ const s = this.input;
427
+ const n = s.length;
428
+ if (this.i >= n && !this.pendingBootstrap?.length) return null;
429
+ const start = {
430
+ column: this.col,
431
+ line: this.line,
432
+ offset: this.i
433
+ };
434
+ if (!this.didMshBootstrap && this.pendingBootstrap) {
435
+ const step = this.pendingBootstrap.shift();
436
+ if (step) {
437
+ if (step.kind === "TEXT") {
438
+ const take = Math.min(step.advance, n - this.i);
439
+ const out = step.value.slice(0, take);
440
+ this._fastAdvance(out);
441
+ if (this.pendingBootstrap.length === 0) {
442
+ this.pendingBootstrap = null;
443
+ this.didMshBootstrap = true;
444
+ }
445
+ return this._tok("TEXT", out, start);
446
+ }
447
+ this._advance(Math.min(step.advance, n - this.i));
448
+ if (this.pendingBootstrap.length === 0) {
449
+ this.pendingBootstrap = null;
450
+ this.didMshBootstrap = true;
451
+ }
452
+ return this._tok("FIELD_DELIM", void 0, start);
453
+ }
454
+ }
455
+ if (this.i >= n) return null;
456
+ const ch = s.charAt(this.i);
457
+ if (ch === this.delims.segment) {
458
+ this._advance(1, true);
459
+ return this._tok("SEGMENT_END", void 0, start);
460
+ }
461
+ if (ch === this.delims.field) {
462
+ this._advance(1);
463
+ return this._tok("FIELD_DELIM", void 0, start);
464
+ }
465
+ if (ch === this.delims.repetition) {
466
+ this._advance(1);
467
+ return this._tok("REPETITION_DELIM", void 0, start);
468
+ }
469
+ if (ch === this.delims.component) {
470
+ this._advance(1);
471
+ return this._tok("COMPONENT_DELIM", void 0, start);
472
+ }
473
+ if (ch === this.delims.subcomponent) {
474
+ this._advance(1);
475
+ return this._tok("SUBCOMP_DELIM", void 0, start);
476
+ }
477
+ let j = this.i;
478
+ const cmp = this.delims.component, fld = this.delims.field, rep = this.delims.repetition, seg = this.delims.segment, sub = this.delims.subcomponent;
479
+ while (j < s.length) {
480
+ const c = s.charAt(j);
481
+ if (c === seg || c === fld || c === rep || c === cmp || c === sub) break;
482
+ j += 1;
483
+ }
484
+ const val = s.slice(this.i, j);
485
+ this._fastAdvance(val);
486
+ return this._tok("TEXT", val, start);
487
+ }
488
+ _slice(start, end) {
489
+ return this.input.slice(start, Math.min(end, this.input.length));
490
+ }
491
+ _advance(n, isNewline = false) {
492
+ this.i += n;
493
+ if (isNewline) {
494
+ this.line += 1;
495
+ this.col = 1;
496
+ } else this.col += n;
497
+ }
498
+ _fastAdvance(chunk) {
499
+ const parts = chunk.split(this.delims.segment);
500
+ if (parts.length > 1) {
501
+ this.line += parts.length - 1;
502
+ this.col = (parts.at(-1)?.length ?? 0) + 1;
503
+ } else this.col += chunk.length;
504
+ this.i += chunk.length;
505
+ }
506
+ _tok(type, value, start) {
507
+ return {
508
+ position: {
509
+ end: {
510
+ column: this.col,
511
+ line: this.line,
512
+ offset: this.i
513
+ },
514
+ start
515
+ },
516
+ type,
517
+ value
518
+ };
519
+ }
520
+ [Symbol.iterator]() {
521
+ return { next: () => {
522
+ const t = this.next();
523
+ if (!t) return {
524
+ done: true,
525
+ value: void 0
526
+ };
527
+ return {
528
+ done: false,
529
+ value: t
530
+ };
531
+ } };
532
+ }
533
533
  };
534
-
535
- // src/parser.ts
534
+ //#endregion
535
+ //#region src/parser.ts
536
536
  function* iterateTokenizerSync(t) {
537
- for (let tok = t.next(); tok; tok = t.next()) {
538
- yield tok;
539
- }
537
+ for (let tok = t.next(); tok; tok = t.next()) yield tok;
540
538
  }
541
539
  function parseHL7v2(input, opts = {}, settings) {
542
- let ctx = {
543
- delimiters: {
544
- ...DEFAULT_DELIMITERS2,
545
- ...settings?.delimiters ?? {}
546
- },
547
- emptyMode: settings?.experimental?.emptyMode,
548
- input
549
- };
550
- ctx = runPreprocessors(ctx, opts.preprocess || defaultPreprocessors);
551
- const tokenizer = new HL7v2Tokenizer();
552
- tokenizer.reset(ctx);
553
- return parseHL7v2FromIterator(iterateTokenizerSync(tokenizer), ctx);
540
+ let ctx = {
541
+ delimiters: {
542
+ ...DEFAULT_DELIMITERS,
543
+ ...settings?.delimiters ?? {}
544
+ },
545
+ emptyMode: settings?.experimental?.emptyMode,
546
+ input
547
+ };
548
+ ctx = runPreprocessors(ctx, opts.preprocess || defaultPreprocessors);
549
+ const tokenizer = new HL7v2Tokenizer();
550
+ tokenizer.reset(ctx);
551
+ return parseHL7v2FromIterator(iterateTokenizerSync(tokenizer), ctx);
554
552
  }
555
- var hl7v2Parser = function hl7v2Parser2(options = {}) {
556
- this.parser = (document) => {
557
- const settings = this.data("settings");
558
- return parseHL7v2(document, options, settings);
559
- };
560
- };
561
- var parser_default = hl7v2Parser;
562
- export {
563
- HL7v2Tokenizer,
564
- defaultPreprocessors,
565
- detectDelimiters,
566
- parser_default as hl7v2Parser,
567
- normalizeNewlines,
568
- parseHL7v2,
569
- parseHL7v2FromIterator,
570
- runPreprocessors,
571
- stripBOM
553
+ const hl7v2Parser = function hl7v2Parser(options = {}) {
554
+ this.parser = (document) => {
555
+ return parseHL7v2(document, options, this.data("settings"));
556
+ };
572
557
  };
558
+ //#endregion
559
+ export { HL7v2Tokenizer, defaultPreprocessors, detectDelimiters, hl7v2Parser, normalizeNewlines, parseHL7v2, parseHL7v2FromIterator, runPreprocessors, stripBOM };
560
+
573
561
  //# sourceMappingURL=index.js.map