@rethinkhealth/hl7v2-parser 0.12.0 → 0.13.2
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 +533 -545
- package/dist/index.js.map +1 -1
- package/dist/processor.d.ts.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,573 +1,561 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
return ctx;
|
|
52
|
+
for (const step of steps) ctx = step(ctx);
|
|
53
|
+
return ctx;
|
|
46
54
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
534
|
+
//#endregion
|
|
535
|
+
//#region src/parser.ts
|
|
536
536
|
function* iterateTokenizerSync(t) {
|
|
537
|
-
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|