@rethinkhealth/hl7v2-parser 0.2.2 → 0.2.4
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/README.md +14 -8
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +325 -534
- package/dist/index.js.map +1 -1
- package/dist/parser.d.ts +6 -13
- package/dist/parser.d.ts.map +1 -1
- package/dist/preprocessor.d.ts +26 -0
- package/dist/preprocessor.d.ts.map +1 -0
- package/dist/processor.d.ts +3 -5
- package/dist/processor.d.ts.map +1 -1
- package/dist/tokenizer.d.ts +18 -0
- package/dist/tokenizer.d.ts.map +1 -0
- package/dist/types.d.ts +29 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +6 -5
- package/dist/constants.d.ts +0 -11
- package/dist/constants.d.ts.map +0 -1
- package/dist/pipeline/core/message.d.ts +0 -16
- package/dist/pipeline/core/message.d.ts.map +0 -1
- package/dist/pipeline/core/parsers/component.d.ts +0 -13
- package/dist/pipeline/core/parsers/component.d.ts.map +0 -1
- package/dist/pipeline/core/parsers/field.d.ts +0 -13
- package/dist/pipeline/core/parsers/field.d.ts.map +0 -1
- package/dist/pipeline/core/parsers/segment-msh.d.ts +0 -14
- package/dist/pipeline/core/parsers/segment-msh.d.ts.map +0 -1
- package/dist/pipeline/core/parsers/segment.d.ts +0 -13
- package/dist/pipeline/core/parsers/segment.d.ts.map +0 -1
- package/dist/pipeline/core/parsers/subcomponent.d.ts +0 -10
- package/dist/pipeline/core/parsers/subcomponent.d.ts.map +0 -1
- package/dist/pipeline/core/registry.d.ts +0 -30
- package/dist/pipeline/core/registry.d.ts.map +0 -1
- package/dist/pipeline/index.d.ts +0 -10
- package/dist/pipeline/index.d.ts.map +0 -1
- package/dist/pipeline/interfaces.d.ts +0 -109
- package/dist/pipeline/interfaces.d.ts.map +0 -1
- package/dist/pipeline/utils/index.d.ts +0 -18
- package/dist/pipeline/utils/index.d.ts.map +0 -1
- package/dist/pipeline/validation/basic-validation.d.ts +0 -17
- package/dist/pipeline/validation/basic-validation.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,582 +1,373 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// src/pipeline/utils/index.ts
|
|
22
|
-
function detectDelimitersFromMSH(raw, segmentDelimiter) {
|
|
23
|
-
const detectedSegmentDelimiter = detectSegmentDelimiter(
|
|
24
|
-
raw,
|
|
25
|
-
segmentDelimiter
|
|
26
|
-
);
|
|
27
|
-
const firstLineEnd = raw.indexOf(detectedSegmentDelimiter);
|
|
28
|
-
const mshSegment = firstLineEnd >= 0 ? raw.slice(0, firstLineEnd) : raw;
|
|
29
|
-
if (!mshSegment.startsWith("MSH")) {
|
|
30
|
-
return {};
|
|
31
|
-
}
|
|
32
|
-
const fieldDelimiter = mshSegment[3];
|
|
33
|
-
if (!fieldDelimiter) {
|
|
34
|
-
return {};
|
|
35
|
-
}
|
|
36
|
-
const encodingChars = mshSegment.slice(4, 8);
|
|
37
|
-
if (encodingChars.length !== 4) {
|
|
38
|
-
return {};
|
|
39
|
-
}
|
|
40
|
-
return {
|
|
41
|
-
field: fieldDelimiter,
|
|
42
|
-
component: encodingChars[0],
|
|
43
|
-
repetition: encodingChars[1],
|
|
44
|
-
escape: encodingChars[2],
|
|
45
|
-
subcomponent: encodingChars[3],
|
|
46
|
-
segment: detectedSegmentDelimiter
|
|
1
|
+
// src/processor.ts
|
|
2
|
+
function createParserCore() {
|
|
3
|
+
const root = { type: "root", children: [] };
|
|
4
|
+
let seg = null;
|
|
5
|
+
let field = null;
|
|
6
|
+
let rep = null;
|
|
7
|
+
let comp = null;
|
|
8
|
+
let currentSub = null;
|
|
9
|
+
let segmentHasContent = false;
|
|
10
|
+
const openSegment = () => {
|
|
11
|
+
seg = { type: "segment", children: [] };
|
|
12
|
+
root.children.push(seg);
|
|
13
|
+
field = null;
|
|
14
|
+
rep = null;
|
|
15
|
+
comp = null;
|
|
16
|
+
currentSub = null;
|
|
17
|
+
segmentHasContent = false;
|
|
47
18
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
let lastIndex = 0;
|
|
52
|
-
let index = text.indexOf(delimiter);
|
|
53
|
-
while (index !== -1) {
|
|
54
|
-
result.push({
|
|
55
|
-
value: text.slice(lastIndex, index),
|
|
56
|
-
start: lastIndex,
|
|
57
|
-
end: index
|
|
58
|
-
});
|
|
59
|
-
lastIndex = index + delimiter.length;
|
|
60
|
-
index = text.indexOf(delimiter, lastIndex);
|
|
61
|
-
}
|
|
62
|
-
result.push({
|
|
63
|
-
value: text.slice(lastIndex),
|
|
64
|
-
start: lastIndex,
|
|
65
|
-
end: text.length
|
|
66
|
-
});
|
|
67
|
-
return result;
|
|
68
|
-
}
|
|
69
|
-
function detectSegmentDelimiter(message, fallback = "\n") {
|
|
70
|
-
const possibleDelimiters = ["\r\n", "\n", "\r"];
|
|
71
|
-
for (const delimiter of possibleDelimiters) {
|
|
72
|
-
if (message.includes(delimiter)) {
|
|
73
|
-
return delimiter;
|
|
19
|
+
const ensureSegment = () => {
|
|
20
|
+
if (!seg) {
|
|
21
|
+
openSegment();
|
|
74
22
|
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
23
|
+
};
|
|
24
|
+
const openField = () => {
|
|
25
|
+
ensureSegment();
|
|
26
|
+
field = { type: "field", children: [] };
|
|
27
|
+
seg.children.push(field);
|
|
28
|
+
rep = { type: "field-repetition", children: [] };
|
|
29
|
+
field.children.push(rep);
|
|
30
|
+
comp = { type: "component", children: [] };
|
|
31
|
+
rep.children.push(comp);
|
|
32
|
+
currentSub = null;
|
|
33
|
+
segmentHasContent = true;
|
|
34
|
+
};
|
|
35
|
+
const openRepetition = () => {
|
|
36
|
+
if (!field) {
|
|
37
|
+
openField();
|
|
87
38
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// src/pipeline/core/parsers/component.ts
|
|
105
|
-
var ComponentParser = class {
|
|
106
|
-
subcomponentParser = new SubcomponentParser();
|
|
107
|
-
canParse(input) {
|
|
108
|
-
return input.type === "component";
|
|
109
|
-
}
|
|
110
|
-
parse(input) {
|
|
111
|
-
if (!this.canParse(input)) {
|
|
112
|
-
return null;
|
|
39
|
+
rep = { type: "field-repetition", children: [] };
|
|
40
|
+
field.children.push(rep);
|
|
41
|
+
comp = { type: "component", children: [] };
|
|
42
|
+
rep.children.push(comp);
|
|
43
|
+
currentSub = null;
|
|
44
|
+
segmentHasContent = true;
|
|
45
|
+
};
|
|
46
|
+
const openComponent = () => {
|
|
47
|
+
if (!field) {
|
|
48
|
+
openField();
|
|
113
49
|
}
|
|
114
|
-
|
|
115
|
-
type: "
|
|
116
|
-
|
|
117
|
-
position: {
|
|
118
|
-
start: { line: input.line, column: input.column, offset: input.start },
|
|
119
|
-
end: {
|
|
120
|
-
line: input.line,
|
|
121
|
-
column: input.column + input.text.length,
|
|
122
|
-
offset: input.end
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
if (input.text.includes(input.context.delimiters.subcomponent)) {
|
|
127
|
-
return {
|
|
128
|
-
...baseNode,
|
|
129
|
-
delimiter: input.context.delimiters.subcomponent,
|
|
130
|
-
children: this.parseSubcomponents(input)
|
|
131
|
-
};
|
|
50
|
+
if (!rep) {
|
|
51
|
+
rep = { type: "field-repetition", children: [] };
|
|
52
|
+
field.children.push(rep);
|
|
132
53
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
for (let i = 0; i < subcomponents.length; i++) {
|
|
142
|
-
const sub = subcomponents[i];
|
|
143
|
-
if (!sub) {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
const subNode = this.subcomponentParser.parse({
|
|
147
|
-
type: "subcomponent",
|
|
148
|
-
text: sub.value,
|
|
149
|
-
start: input.start + sub.start,
|
|
150
|
-
end: input.start + sub.end,
|
|
151
|
-
index: i,
|
|
152
|
-
subcomponentIndex: i,
|
|
153
|
-
line: input.line,
|
|
154
|
-
column: input.column + sub.start,
|
|
155
|
-
context: input.context
|
|
156
|
-
});
|
|
157
|
-
if (subNode) {
|
|
158
|
-
nodes.push(subNode);
|
|
159
|
-
}
|
|
54
|
+
comp = { type: "component", children: [] };
|
|
55
|
+
rep.children.push(comp);
|
|
56
|
+
currentSub = null;
|
|
57
|
+
segmentHasContent = true;
|
|
58
|
+
};
|
|
59
|
+
const ensureForText = () => {
|
|
60
|
+
if (!field) {
|
|
61
|
+
openField();
|
|
160
62
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
splitByDelimiter(text, delimiter) {
|
|
164
|
-
const result = [];
|
|
165
|
-
let lastIndex = 0;
|
|
166
|
-
let index = text.indexOf(delimiter);
|
|
167
|
-
while (index !== -1) {
|
|
168
|
-
result.push({
|
|
169
|
-
value: text.slice(lastIndex, index),
|
|
170
|
-
start: lastIndex,
|
|
171
|
-
end: index
|
|
172
|
-
});
|
|
173
|
-
lastIndex = index + delimiter.length;
|
|
174
|
-
index = text.indexOf(delimiter, lastIndex);
|
|
63
|
+
if (!rep) {
|
|
64
|
+
openRepetition();
|
|
175
65
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
start: lastIndex,
|
|
179
|
-
end: text.length
|
|
180
|
-
});
|
|
181
|
-
return result;
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
// src/pipeline/core/parsers/field.ts
|
|
186
|
-
var FieldParser = class {
|
|
187
|
-
componentParser = new ComponentParser();
|
|
188
|
-
canParse(input) {
|
|
189
|
-
return input.type === "field";
|
|
190
|
-
}
|
|
191
|
-
parse(input) {
|
|
192
|
-
if (!this.canParse(input)) {
|
|
193
|
-
return null;
|
|
66
|
+
if (!comp) {
|
|
67
|
+
openComponent();
|
|
194
68
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
69
|
+
if (!currentSub) {
|
|
70
|
+
currentSub = { type: "subcomponent", value: "" };
|
|
71
|
+
comp.children.push(currentSub);
|
|
72
|
+
segmentHasContent = true;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const processToken = (tok) => {
|
|
76
|
+
switch (tok.type) {
|
|
77
|
+
case "SEGMENT_END": {
|
|
78
|
+
dropTrailingEmptyFieldIfPresent();
|
|
79
|
+
seg = null;
|
|
80
|
+
field = null;
|
|
81
|
+
rep = null;
|
|
82
|
+
comp = null;
|
|
83
|
+
currentSub = null;
|
|
84
|
+
segmentHasContent = false;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
case "FIELD_DELIM": {
|
|
88
|
+
if (!field) {
|
|
89
|
+
openField();
|
|
90
|
+
comp.children.push({ type: "subcomponent", value: "" });
|
|
91
|
+
segmentHasContent = true;
|
|
204
92
|
}
|
|
93
|
+
openField();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
case "REPETITION_DELIM": {
|
|
97
|
+
if (!field) {
|
|
98
|
+
openField();
|
|
99
|
+
}
|
|
100
|
+
openRepetition();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
case "COMPONENT_DELIM": {
|
|
104
|
+
openComponent();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
case "SUBCOMP_DELIM": {
|
|
108
|
+
if (!comp) {
|
|
109
|
+
openComponent();
|
|
110
|
+
}
|
|
111
|
+
currentSub = { type: "subcomponent", value: "" };
|
|
112
|
+
comp.children.push(currentSub);
|
|
113
|
+
segmentHasContent = true;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
case "TEXT": {
|
|
117
|
+
const val = tok.value ?? "";
|
|
118
|
+
ensureForText();
|
|
119
|
+
currentSub.value += val;
|
|
120
|
+
segmentHasContent = true;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
default: {
|
|
124
|
+
throw new Error(`Unexpected token type: ${tok.type}`);
|
|
205
125
|
}
|
|
206
|
-
};
|
|
207
|
-
if (input.isEncodingField) {
|
|
208
|
-
return { ...baseNode, value: input.text };
|
|
209
126
|
}
|
|
210
|
-
|
|
211
|
-
|
|
127
|
+
};
|
|
128
|
+
const finalize = () => {
|
|
129
|
+
dropTrailingEmptyFieldIfPresent();
|
|
130
|
+
if (seg && !segmentHasContent) {
|
|
131
|
+
root.children.pop();
|
|
212
132
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
};
|
|
133
|
+
return root;
|
|
134
|
+
};
|
|
135
|
+
function dropTrailingEmptyFieldIfPresent() {
|
|
136
|
+
if (!seg || seg.children.length === 0) {
|
|
137
|
+
return;
|
|
219
138
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const components = this.splitByDelimiter(
|
|
224
|
-
input.text,
|
|
225
|
-
input.context.delimiters.component
|
|
139
|
+
const lastField = seg.children.at(-1);
|
|
140
|
+
const hasAnySubcomponents = lastField.children.some(
|
|
141
|
+
(r) => r.children.some((c) => c.children.length > 0)
|
|
226
142
|
);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const comp = components[i];
|
|
230
|
-
if (!comp) {
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const componentNode = this.componentParser.parse({
|
|
234
|
-
type: "component",
|
|
235
|
-
text: comp.value,
|
|
236
|
-
start: input.start + comp.start,
|
|
237
|
-
end: input.start + comp.end,
|
|
238
|
-
index: i,
|
|
239
|
-
componentIndex: i,
|
|
240
|
-
line: input.line,
|
|
241
|
-
column: input.column + comp.start,
|
|
242
|
-
context: input.context
|
|
243
|
-
});
|
|
244
|
-
if (componentNode) {
|
|
245
|
-
nodes.push(componentNode);
|
|
246
|
-
}
|
|
143
|
+
if (!hasAnySubcomponents) {
|
|
144
|
+
seg.children.pop();
|
|
247
145
|
}
|
|
248
|
-
return nodes;
|
|
249
146
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
value: text.slice(lastIndex, index),
|
|
257
|
-
start: lastIndex,
|
|
258
|
-
end: index
|
|
259
|
-
});
|
|
260
|
-
lastIndex = index + delimiter.length;
|
|
261
|
-
index = text.indexOf(delimiter, lastIndex);
|
|
262
|
-
}
|
|
263
|
-
result.push({
|
|
264
|
-
value: text.slice(lastIndex),
|
|
265
|
-
start: lastIndex,
|
|
266
|
-
end: text.length
|
|
267
|
-
});
|
|
268
|
-
return result;
|
|
147
|
+
return { processToken, finalize, root };
|
|
148
|
+
}
|
|
149
|
+
function parseHL7v2FromIterator(tokens) {
|
|
150
|
+
const core = createParserCore();
|
|
151
|
+
for (const tok of tokens) {
|
|
152
|
+
core.processToken(tok);
|
|
269
153
|
}
|
|
270
|
-
|
|
154
|
+
return core.finalize();
|
|
155
|
+
}
|
|
271
156
|
|
|
272
|
-
// src/
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
157
|
+
// src/tokenizer.ts
|
|
158
|
+
import {
|
|
159
|
+
DEFAULT_DELIMITERS
|
|
160
|
+
} from "@rethinkhealth/hl7v2-utils";
|
|
161
|
+
var HL7v2Tokenizer = class {
|
|
162
|
+
input = "";
|
|
163
|
+
i = 0;
|
|
164
|
+
line = 1;
|
|
165
|
+
col = 1;
|
|
166
|
+
delims;
|
|
167
|
+
// Only-run-once MSH bootstrap at the start of the file
|
|
168
|
+
didMshBootstrap = false;
|
|
169
|
+
pendingBootstrap = null;
|
|
170
|
+
// queue to emit TEXT('MSH'), FIELD_DELIM, TEXT('^~\\&')
|
|
171
|
+
reset(input, opts) {
|
|
172
|
+
this.input = input;
|
|
173
|
+
this.delims = opts.delimiters || DEFAULT_DELIMITERS;
|
|
174
|
+
this.i = 0;
|
|
175
|
+
this.line = 1;
|
|
176
|
+
this.col = 1;
|
|
177
|
+
this.didMshBootstrap = false;
|
|
178
|
+
this.pendingBootstrap = null;
|
|
179
|
+
if (this.input.startsWith("MSH")) {
|
|
180
|
+
const msh = this._slice(0, 3);
|
|
181
|
+
const msh2 = this._slice(4, 8);
|
|
182
|
+
this.pendingBootstrap = [
|
|
183
|
+
{ kind: "TEXT", value: msh, advance: msh.length },
|
|
184
|
+
{ kind: "FIELD_DELIM", advance: 1 },
|
|
185
|
+
// consume the single field delimiter char at index 3
|
|
186
|
+
{ kind: "TEXT", value: msh2, advance: msh2.length }
|
|
187
|
+
];
|
|
282
188
|
}
|
|
283
|
-
|
|
284
|
-
|
|
189
|
+
}
|
|
190
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: The cognitive complexity of this method is justified because it must handle the HL7v2 MSH segment bootstrap logic and multiple tokenization cases in a single method for performance and maintainability.
|
|
191
|
+
next() {
|
|
192
|
+
const s = this.input;
|
|
193
|
+
const n = s.length;
|
|
194
|
+
if (this.i >= n && !this.pendingBootstrap?.length) {
|
|
285
195
|
return null;
|
|
286
196
|
}
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
197
|
+
const start = { offset: this.i, line: this.line, column: this.col };
|
|
198
|
+
if (!this.didMshBootstrap && this.pendingBootstrap) {
|
|
199
|
+
const step = this.pendingBootstrap.shift();
|
|
200
|
+
if (step) {
|
|
201
|
+
if (step.kind === "TEXT") {
|
|
202
|
+
const take = Math.min(step.advance, n - this.i);
|
|
203
|
+
const out = step.value.slice(0, take);
|
|
204
|
+
this._fastAdvance(out);
|
|
205
|
+
if (this.pendingBootstrap.length === 0) {
|
|
206
|
+
this.pendingBootstrap = null;
|
|
207
|
+
this.didMshBootstrap = true;
|
|
208
|
+
}
|
|
209
|
+
return this._tok("TEXT", out, start);
|
|
299
210
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const children = [];
|
|
307
|
-
for (let i = 0; i < fields.length; i++) {
|
|
308
|
-
const field = fields[i];
|
|
309
|
-
if (!field) {
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
const fieldNode = this.fieldParser.parse({
|
|
313
|
-
type: "field",
|
|
314
|
-
text: field.value,
|
|
315
|
-
start: input.start + field.start,
|
|
316
|
-
end: input.start + field.end,
|
|
317
|
-
index: i,
|
|
318
|
-
fieldIndex: i,
|
|
319
|
-
line: input.line,
|
|
320
|
-
column: field.start + 1,
|
|
321
|
-
context: input.context
|
|
322
|
-
});
|
|
323
|
-
if (fieldNode) {
|
|
324
|
-
children.push(fieldNode);
|
|
211
|
+
this._advance(Math.min(step.advance, n - this.i));
|
|
212
|
+
if (this.pendingBootstrap.length === 0) {
|
|
213
|
+
this.pendingBootstrap = null;
|
|
214
|
+
this.didMshBootstrap = true;
|
|
215
|
+
}
|
|
216
|
+
return this._tok("FIELD_DELIM", void 0, start);
|
|
325
217
|
}
|
|
326
218
|
}
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
// src/pipeline/core/parsers/segment-msh.ts
|
|
332
|
-
var MSHSegmentParser = class {
|
|
333
|
-
segmentType = "MSH";
|
|
334
|
-
fieldParser = new FieldParser();
|
|
335
|
-
canParse(input) {
|
|
336
|
-
return input.type === "segment" && input.segmentType === "MSH";
|
|
337
|
-
}
|
|
338
|
-
parse(input) {
|
|
339
|
-
if (!this.canParse(input)) {
|
|
219
|
+
if (this.i >= n) {
|
|
340
220
|
return null;
|
|
341
221
|
}
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const segmentNode = {
|
|
347
|
-
type: "segment",
|
|
348
|
-
name: "MSH",
|
|
349
|
-
index: input.index,
|
|
350
|
-
delimiter: input.context.delimiters.field,
|
|
351
|
-
children: [],
|
|
352
|
-
position: {
|
|
353
|
-
start: { line: input.line, column: 1, offset: input.start },
|
|
354
|
-
end: {
|
|
355
|
-
line: input.line,
|
|
356
|
-
column: input.text.length + 1,
|
|
357
|
-
offset: input.end
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
segmentNode.children = this.parseFields(fields, input);
|
|
362
|
-
return segmentNode;
|
|
363
|
-
}
|
|
364
|
-
extractMSHFields(segmentText, fieldDelimiter) {
|
|
365
|
-
const msh1 = segmentText[3] ?? "";
|
|
366
|
-
const msh2 = segmentText.slice(4, 8) ?? "";
|
|
367
|
-
const rest = segmentText.slice(9);
|
|
368
|
-
const restFields = splitByString(rest, fieldDelimiter);
|
|
369
|
-
return [
|
|
370
|
-
{ value: "MSH", start: 0, end: 3 },
|
|
371
|
-
{ value: msh1, start: 3, end: 4 },
|
|
372
|
-
{ value: msh2, start: 4, end: 8, isEncodingField: true },
|
|
373
|
-
...restFields.map((f) => ({
|
|
374
|
-
value: f.value ?? "",
|
|
375
|
-
start: 9 + (f.start ?? 0),
|
|
376
|
-
end: 9 + (f.end ?? 0)
|
|
377
|
-
}))
|
|
378
|
-
];
|
|
379
|
-
}
|
|
380
|
-
parseFields(fields, input) {
|
|
381
|
-
const children = [];
|
|
382
|
-
for (let i = 0; i < fields.length; i++) {
|
|
383
|
-
const field = fields[i];
|
|
384
|
-
if (!field) {
|
|
385
|
-
continue;
|
|
386
|
-
}
|
|
387
|
-
const fieldNode = this.fieldParser.parse({
|
|
388
|
-
type: "field",
|
|
389
|
-
text: field.value,
|
|
390
|
-
start: input.start + field.start,
|
|
391
|
-
end: input.start + field.end,
|
|
392
|
-
index: i,
|
|
393
|
-
fieldIndex: i,
|
|
394
|
-
line: input.line,
|
|
395
|
-
column: field.start + 1,
|
|
396
|
-
isEncodingField: field.isEncodingField,
|
|
397
|
-
context: input.context
|
|
398
|
-
});
|
|
399
|
-
if (fieldNode) {
|
|
400
|
-
children.push(fieldNode);
|
|
401
|
-
}
|
|
222
|
+
const ch = s.charAt(this.i);
|
|
223
|
+
if (ch === this.delims.segment) {
|
|
224
|
+
this._advance(1, true);
|
|
225
|
+
return this._tok("SEGMENT_END", void 0, start);
|
|
402
226
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
// src/pipeline/core/registry.ts
|
|
408
|
-
var SegmentParserRegistry = class {
|
|
409
|
-
parsers = /* @__PURE__ */ new Map();
|
|
410
|
-
defaultParser;
|
|
411
|
-
constructor(customParsers) {
|
|
412
|
-
this.defaultParser = new SegmentParser();
|
|
413
|
-
this.register(new MSHSegmentParser());
|
|
414
|
-
if (customParsers) {
|
|
415
|
-
for (const [, parser] of customParsers) {
|
|
416
|
-
this.register(parser);
|
|
417
|
-
}
|
|
227
|
+
if (ch === this.delims.field) {
|
|
228
|
+
this._advance(1);
|
|
229
|
+
return this._tok("FIELD_DELIM", void 0, start);
|
|
418
230
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
*/
|
|
423
|
-
register(parser) {
|
|
424
|
-
this.parsers.set(parser.segmentType, parser);
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Get the appropriate parser for a segment type
|
|
428
|
-
*/
|
|
429
|
-
getParser(segmentType) {
|
|
430
|
-
return this.parsers.get(segmentType) || this.defaultParser;
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* Check if a parser is registered for a segment type
|
|
434
|
-
*/
|
|
435
|
-
hasParser(segmentType) {
|
|
436
|
-
return this.parsers.has(segmentType);
|
|
437
|
-
}
|
|
438
|
-
/**
|
|
439
|
-
* Unregister a parser for a segment type
|
|
440
|
-
*/
|
|
441
|
-
unregister(segmentType) {
|
|
442
|
-
return this.parsers.delete(segmentType);
|
|
443
|
-
}
|
|
444
|
-
/**
|
|
445
|
-
* Get all registered segment types
|
|
446
|
-
*/
|
|
447
|
-
getRegisteredTypes() {
|
|
448
|
-
return Array.from(this.parsers.keys());
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
|
|
452
|
-
// src/pipeline/core/message.ts
|
|
453
|
-
var HL7MessageParser = class {
|
|
454
|
-
segmentRegistry;
|
|
455
|
-
options;
|
|
456
|
-
constructor(options = {}) {
|
|
457
|
-
this.options = options;
|
|
458
|
-
this.segmentRegistry = new SegmentParserRegistry(options.customParsers);
|
|
459
|
-
}
|
|
460
|
-
canParse(input) {
|
|
461
|
-
return input.type === "message" && input.text.trim().length > 0;
|
|
462
|
-
}
|
|
463
|
-
parse(input) {
|
|
464
|
-
if (!this.canParse(input)) {
|
|
465
|
-
return EMPTY_MESSAGE;
|
|
231
|
+
if (ch === this.delims.repetition) {
|
|
232
|
+
this._advance(1);
|
|
233
|
+
return this._tok("REPETITION_DELIM", void 0, start);
|
|
466
234
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
};
|
|
482
|
-
let currentLine = 1;
|
|
483
|
-
for (let i = 0; i < segments.length; i++) {
|
|
484
|
-
const seg = segments[i];
|
|
485
|
-
const hasContent = seg?.value.trim();
|
|
486
|
-
if (!seg) {
|
|
487
|
-
currentLine++;
|
|
488
|
-
continue;
|
|
489
|
-
}
|
|
490
|
-
if (!hasContent) {
|
|
491
|
-
currentLine++;
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
const segmentNode = this.parseSegment(seg, currentLine, i, context);
|
|
495
|
-
if (segmentNode) {
|
|
496
|
-
messageNode.children = messageNode.children || [];
|
|
497
|
-
messageNode.children.push(segmentNode);
|
|
235
|
+
if (ch === this.delims.component) {
|
|
236
|
+
this._advance(1);
|
|
237
|
+
return this._tok("COMPONENT_DELIM", void 0, start);
|
|
238
|
+
}
|
|
239
|
+
if (ch === this.delims.subcomponent) {
|
|
240
|
+
this._advance(1);
|
|
241
|
+
return this._tok("SUBCOMP_DELIM", void 0, start);
|
|
242
|
+
}
|
|
243
|
+
let j = this.i;
|
|
244
|
+
const seg = this.delims.segment, fld = this.delims.field, rep = this.delims.repetition, cmp = this.delims.component, sub = this.delims.subcomponent;
|
|
245
|
+
while (j < s.length) {
|
|
246
|
+
const c = s.charAt(j);
|
|
247
|
+
if (c === seg || c === fld || c === rep || c === cmp || c === sub) {
|
|
248
|
+
break;
|
|
498
249
|
}
|
|
499
|
-
|
|
250
|
+
j++;
|
|
251
|
+
}
|
|
252
|
+
const val = s.slice(this.i, j);
|
|
253
|
+
this._fastAdvance(val);
|
|
254
|
+
return this._tok("TEXT", val, start);
|
|
255
|
+
}
|
|
256
|
+
// ---- helpers ----
|
|
257
|
+
_slice(start, end) {
|
|
258
|
+
return this.input.slice(start, Math.min(end, this.input.length));
|
|
259
|
+
}
|
|
260
|
+
_advance(n, isNewline = false) {
|
|
261
|
+
this.i += n;
|
|
262
|
+
if (isNewline) {
|
|
263
|
+
this.line += 1;
|
|
264
|
+
this.col = 1;
|
|
265
|
+
} else {
|
|
266
|
+
this.col += n;
|
|
500
267
|
}
|
|
501
|
-
return this.applyValidationHooks(messageNode, context);
|
|
502
268
|
}
|
|
503
|
-
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const detected = detectDelimitersFromMSH(
|
|
511
|
-
rawMessage,
|
|
512
|
-
baseDelimiters.segment
|
|
513
|
-
);
|
|
514
|
-
activeDelimiters = { ...baseDelimiters, ...detected };
|
|
269
|
+
_fastAdvance(chunk) {
|
|
270
|
+
const parts = chunk.split("\r");
|
|
271
|
+
if (parts.length > 1) {
|
|
272
|
+
this.line += parts.length - 1;
|
|
273
|
+
this.col = (parts.at(-1)?.length ?? 0) + 1;
|
|
274
|
+
} else {
|
|
275
|
+
this.col += chunk.length;
|
|
515
276
|
}
|
|
516
|
-
|
|
517
|
-
delimiters: activeDelimiters,
|
|
518
|
-
options: this.options,
|
|
519
|
-
currentLine: 1,
|
|
520
|
-
totalOffset: 0
|
|
521
|
-
};
|
|
277
|
+
this.i += chunk.length;
|
|
522
278
|
}
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
return parser.parse({
|
|
527
|
-
type: "segment",
|
|
528
|
-
text: segment.value,
|
|
529
|
-
start: segment.start,
|
|
530
|
-
end: segment.end,
|
|
531
|
-
index,
|
|
532
|
-
segmentType,
|
|
533
|
-
line,
|
|
534
|
-
context
|
|
535
|
-
});
|
|
279
|
+
_tok(type, value, start) {
|
|
280
|
+
const end = { offset: this.i, line: this.line, column: this.col };
|
|
281
|
+
return { type, value, position: { start, end } };
|
|
536
282
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
283
|
+
// Iterable protocol (sync)
|
|
284
|
+
[Symbol.iterator]() {
|
|
285
|
+
return {
|
|
286
|
+
next: () => {
|
|
287
|
+
const t = this.next();
|
|
288
|
+
if (t == null) {
|
|
289
|
+
return { done: true, value: void 0 };
|
|
290
|
+
}
|
|
291
|
+
return { done: false, value: t };
|
|
545
292
|
}
|
|
546
|
-
}
|
|
547
|
-
return node;
|
|
293
|
+
};
|
|
548
294
|
}
|
|
295
|
+
// Async iteration support removed to keep the API synchronous.
|
|
549
296
|
};
|
|
550
297
|
|
|
551
|
-
// src/
|
|
552
|
-
function
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
text: rawMessage,
|
|
557
|
-
start: 0,
|
|
558
|
-
end: rawMessage.length,
|
|
559
|
-
index: 0,
|
|
560
|
-
context: {
|
|
561
|
-
delimiters: options.delimiters || {},
|
|
562
|
-
options,
|
|
563
|
-
currentLine: 1,
|
|
564
|
-
totalOffset: 0
|
|
565
|
-
}
|
|
566
|
-
});
|
|
567
|
-
return result || EMPTY_MESSAGE;
|
|
298
|
+
// src/utils.ts
|
|
299
|
+
function* iterateTokenizerSync(t) {
|
|
300
|
+
for (let tok = t.next(); tok; tok = t.next()) {
|
|
301
|
+
yield tok;
|
|
302
|
+
}
|
|
568
303
|
}
|
|
569
304
|
|
|
570
|
-
// src/
|
|
305
|
+
// src/parser.ts
|
|
306
|
+
function parseHL7v2(input, opts) {
|
|
307
|
+
const tokenizer = new HL7v2Tokenizer();
|
|
308
|
+
tokenizer.reset(input, opts);
|
|
309
|
+
return parseHL7v2FromIterator(iterateTokenizerSync(tokenizer));
|
|
310
|
+
}
|
|
571
311
|
var hl7v2Parser = function(options = {}) {
|
|
572
312
|
const self = this;
|
|
573
|
-
|
|
574
|
-
|
|
313
|
+
self.parser = (value) => parseHL7v2(value, options);
|
|
314
|
+
};
|
|
315
|
+
var parser_default = hl7v2Parser;
|
|
316
|
+
|
|
317
|
+
// src/preprocessor.ts
|
|
318
|
+
var stripBOM = (ctx) => {
|
|
319
|
+
if (ctx.input.charCodeAt(0) === 65279) {
|
|
320
|
+
ctx.input = ctx.input.slice(1);
|
|
575
321
|
}
|
|
576
|
-
|
|
322
|
+
return ctx;
|
|
577
323
|
};
|
|
578
|
-
var
|
|
324
|
+
var normalizeNewlines = (ctx) => {
|
|
325
|
+
ctx.input = ctx.input.replace(/\r?\n/g, "\r");
|
|
326
|
+
return ctx;
|
|
327
|
+
};
|
|
328
|
+
var detectDelimiters = (ctx) => {
|
|
329
|
+
if (ctx.input.startsWith("MSH")) {
|
|
330
|
+
const fieldDelim = ctx.input.charAt(3) || "|";
|
|
331
|
+
const enc = ctx.input.slice(4, 8);
|
|
332
|
+
const component = enc.charAt(0) || "^";
|
|
333
|
+
const repetition = enc.charAt(1) || "~";
|
|
334
|
+
const _escape = enc.charAt(2) || "\\";
|
|
335
|
+
const subcomponent = enc.charAt(3) || "&";
|
|
336
|
+
ctx.options.delimiters = {
|
|
337
|
+
field: fieldDelim,
|
|
338
|
+
component,
|
|
339
|
+
repetition,
|
|
340
|
+
escape: _escape,
|
|
341
|
+
subcomponent,
|
|
342
|
+
segment: "\r"
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return ctx;
|
|
346
|
+
};
|
|
347
|
+
var defaultPreprocessors = [
|
|
348
|
+
stripBOM,
|
|
349
|
+
normalizeNewlines,
|
|
350
|
+
detectDelimiters
|
|
351
|
+
];
|
|
352
|
+
function runPreprocessors(raw, opts = {}, steps = defaultPreprocessors) {
|
|
353
|
+
let ctx = {
|
|
354
|
+
input: raw,
|
|
355
|
+
options: opts,
|
|
356
|
+
metadata: {}
|
|
357
|
+
};
|
|
358
|
+
for (const step of steps) {
|
|
359
|
+
ctx = step(ctx);
|
|
360
|
+
}
|
|
361
|
+
return ctx;
|
|
362
|
+
}
|
|
579
363
|
export {
|
|
580
|
-
|
|
364
|
+
HL7v2Tokenizer,
|
|
365
|
+
defaultPreprocessors,
|
|
366
|
+
detectDelimiters,
|
|
367
|
+
parser_default as hl7v2Parser,
|
|
368
|
+
normalizeNewlines,
|
|
369
|
+
parseHL7v2FromIterator,
|
|
370
|
+
runPreprocessors,
|
|
371
|
+
stripBOM
|
|
581
372
|
};
|
|
582
373
|
//# sourceMappingURL=index.js.map
|